🤪

Terraformで作成したEC2を停止したら差分が!instance_stateをignore_changesしたるでぇ!←無駄無駄ァ!

2024/02/07に公開

ことの始まり

ある日、ちょっとした一時的な作業用にパブリックIPアドレスを付与したEC2インスタンスを立ち上げる必要があり、以下のようなTerraformコードを用意しました。

main.tf
resource "aws_instance" "this" {
  ami                         = "ami-01ff47a07ed5e00bf"
  instance_type               = "t4g.micro"
  subnet_id                   = "subnet-xxxxxxxxxxxxxxxxx"
  associate_public_ip_address = true

  root_block_device {
    volume_size           = 8
    volume_type           = "gp3"
    iops                  = 3000
    delete_on_termination = true
    encrypted             = true
    throughput            = 125
  }

  iam_instance_profile    = "work-ec2-role"
  disable_api_termination = false

  tags = {
    Name = "work-ec2"
  }
}

terraform applyを実行してEC2インスタンスを作成した後、念のためterraform planを実行してみると、以下の通り差分はない状態でした。

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

ところが、AWSマネジメントコンソールでEC2インスタンスを停止した後、再度terraform planを実行してみると、EC2インスタンスを再作成しようとする差分が表示されました。どうして・・・。

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_instance.this must be replaced
-/+ resource "aws_instance" "this" {
      ~ arn                                  = "arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/i-xxxxxxxxxxxxxxxxx" -> (known after apply)
      ~ associate_public_ip_address          = false -> true # forces replacement
      ~ availability_zone                    = "ap-northeast-1a" -> (known after apply)
      ~ cpu_core_count                       = 2 -> (known after apply)
      ~ cpu_threads_per_core                 = 1 -> (known after apply)
      ~ disable_api_stop                     = false -> (known after apply)
      ~ ebs_optimized                        = false -> (known after apply)
      - hibernation                          = false -> null
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      ~ id                                   = "i-xxxxxxxxxxxxxxxxx" -> (known after apply)
      ~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
      + instance_lifecycle                   = (known after apply)
      ~ instance_state                       = "stopped" -> (known after apply)
      ~ ipv6_address_count                   = 0 -> (known after apply)
      ~ ipv6_addresses                       = [] -> (known after apply)
      + key_name                             = (known after apply)
      ~ monitoring                           = false -> (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      ~ placement_partition_number           = 0 -> (known after apply)
      ~ primary_network_interface_id         = "eni-xxxxxxxxxxxxxxxxx" -> (known after apply)
      ~ private_dns                          = "ip-xx-xx-xx-xx.ap-northeast-1.compute.internal" -> (known after apply)
      ~ private_ip                           = "xx.xx.xx.xx" -> (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      ~ secondary_private_ips                = [] -> (known after apply)
      ~ security_groups                      = [] -> (known after apply)
      + spot_instance_request_id             = (known after apply)
        tags                                 = {
            "Name" = "work-ec2"
        }
      ~ tenancy                              = "default" -> (known after apply)
      + user_data                           

 = (known after apply)
      + user_data_base64                     = (known after apply)
        # (10 unchanged attributes hidden)

      - capacity_reservation_specification {
          - capacity_reservation_preference = "open" -> null
        }

      - cpu_options {
          - core_count       = 2 -> null
          - threads_per_core = 1 -> null
        }

      - credit_specification {
          - cpu_credits = "unlimited" -> null
        }

      - enclave_options {
          - enabled = false -> null
        }

      - maintenance_options {
          - auto_recovery = "default" -> null
        }

      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_protocol_ipv6          = "disabled" -> null
          - http_put_response_hop_limit = 2 -> null
          - http_tokens                 = "required" -> null
          - instance_metadata_tags      = "disabled" -> null
        }

      - private_dns_name_options {
          - enable_resource_name_dns_a_record    = false -> null
          - enable_resource_name_dns_aaaa_record = false -> null
          - hostname_type                        = "ip-name" -> null
        }

      ~ root_block_device {
          ~ device_name           = "/dev/xvda" -> (known after apply)
          ~ kms_key_id            = "arn:aws:kms:ap-northeast-1:xxxxxxxxxxxx:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -> (known after apply)
          - tags                  = {} -> null
          ~ volume_id             = "vol-xxxxxxxxxxxxxxxxx" -> (known after apply)
            # (6 unchanged attributes hidden)
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

原因の調査

AWSマネジメントコンソールでEC2インスタンスを停止した後に差分が生じたんだから、停止状態であることが原因なのでは?と考え、まずは以下の部分に焦点を当てました。

      ~ instance_state                       = "stopped" -> (known after apply)

とりあえずignore_changesに入れてしまえば検知しなくなって万事解決!!ということで、以下の通りTerraformコードを修正。

main.tf
resource "aws_instance" "this" {
  # (省略)

  # lifecycle ブロックを追加
  lifecycle {
    ignore_changes = [
      instance_state
    ]
  }

  # (省略)
}

ところが、この状態で terraform plan を実行すると、差分は依然として生じたままで、さらには以下のWarningまで表示されてしまいました。

Adding an attribute name to ignore_changes tells Terraform to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured.

The attribute instance_state is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.

instance_state はプロバイダによってのみ決定される属性だから、Terraformの構成と比較する設定された値は存在しないよ〜。ignore_changes に入れても無意味だよ〜」みたいなことが書いてあるようです。

えー・・・それじゃどうしようもないじゃん・・・と思いながらなんとなく差分を見ていた私。しばらくしてようやく本当の原因に気づきました。

本当の原因

・・・まてよ?こっちか!?

      ~ associate_public_ip_address          = false -> true # forces replacement

なぜすぐに気づかなかったのでしょう。冒頭の terraform plan 実行結果を見れば一目瞭然ですが、EC2インスタンスに自動割り当てされたパブリックIPアドレスは停止時に解放されるため、ここが差分の原因となっているようでした。

よって、以下の通りコードを修正しました。

main.tf
resource "aws_instance" "this" {
  # (省略)

  lifecycle {
    ignore_changes = [
      # instance_state から associate_public_ip_address に変更
      associate_public_ip_address
    ]
  }

  # (省略)
}

やったー!!!!

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

結論

terraform plan実行時の差分の内容は、心を落ち着かせてじっくり読みましょう。

Discussion