意図しない置換(-/+)の原因と対処法

1. 概要

  • 「意図しない置換(replace)」とは
  • planで-/+(destroy then create)が表示されるケース
  • 主な原因(ForceNew属性の変更・identifierの変更)
  • 対処法(ignore_changescreate_before_destroyimportによる回避)

terraform plan-/+(destroy then create)が表示されたとき、意図しない場合はリソースの停止・データ消失につながります。原因を理解して正しく対処する必要があります。


2. planで-/+が表示される例

Terraform will perform the following actions:

  # aws_instance.web must be replaced
-/+ resource "aws_instance" "web" {
      ~ id                   = "i-0123456789abcdef0" -> (known after apply)
    -/+ ami                  = "ami-old" -> "ami-new" # forces replacement
        ...
    }

# forces replacementというコメントがどの属性変更が置換を引き起こすかを示しています。


3. よくある原因

原因1: AMI IDの変更

terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

variable "environment" {
  description = "環境名"
  type        = string
  default     = "dev"
}

data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  # most_recent = true のAMIはApplyのたびに新しいIDになる場合がある
  # → 新しいAMI IDに変わると EC2インスタンスが置換される
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  lifecycle {
    # ✅ AMIの変更による置換を無視する(既存インスタンスを維持)
    ignore_changes = [ami]
  }

  tags = {
    Name        = "${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

原因2: RDSのidentifier変更

# ❌ identifierを変更するとRDSが置換される(データ消失リスク)
resource "aws_db_instance" "main" {
  identifier        = "${var.environment}-postgres-v2"  # 変更前: -v1
  engine            = "postgres"
  engine_version    = "16"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  storage_encrypted = true
  db_name           = "myapp"
  username          = "admin"
  password          = "CHANGE_ME"
  skip_final_snapshot = true

  tags = {
    Name        = "${var.environment}-main-db"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

4. 対処法1: ignore_changes で変更を無視

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  lifecycle {
    # AMIの変更・タグ変更による置換を防ぐ
    ignore_changes = [
      ami,
      tags["LastDeployedAt"],
    ]
  }

  tags = {
    Name        = "${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

5. 対処法2: create_before_destroy で無停止置換

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  lifecycle {
    # 置換が必要な場合: 先に新しいインスタンスを作り、その後古いのを削除
    create_before_destroy = true
  }

  tags = {
    Name        = "${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

6. 対処法3: replace/taint を使った意図的な置換

# 特定のリソースを意図的に置換する場合
terraform apply -replace=aws_instance.web

# ❌ terraform taint は非推奨(-replace を使う)

7. 関連記事


8. まとめ

  • planで-/+が表示されたら# forces replacementコメントで原因の属性を確認する
  • AMIのIDが変わる・リソース名や識別子を変えると多くのリソースが置換される
  • データ消失リスクがある置換(RDS・ElastiCache等)はignore_changesで防ぐ
  • 無停止にしたい置換create_before_destroy = trueで先に新しいものを作る
  • 意図的な置換にはterraform apply -replace=resource.nameを使う(taintは非推奨)

動作確認バージョン: Terraform >= 1.9 公式ドキュメント: https://developer.hashicorp.com/terraform/language/resources/lifecycle