AWS EC2 Instance Replacement with No Modifications (Terraform)

During my learning of AWS and its deployment via Terraform, I came across an issue where my EC2 instances kept getting destroyed, even when no changes had been made to them. For example in the plan output:

# aws_instance.spoke2-ec2 must be replaced
-/+ resource "aws_instance" "spoke2-ec2" {
      ~ arn                                  = "arn:aws:ec2:eu-west-2:97123148471:instance/i-04c0e1bc6b05b8b57" -> (known after apply)
      ~ associate_public_ip_address          = false -> (known after apply)
      ~ disable_api_stop                     = false -> (known after apply)
      ~ disable_api_termination              = false -> (known after apply)
      ~ ebs_optimized                        = false -> (known after apply)
      + enable_primary_ipv6                  = (known after apply)
      - hibernation                          = false -> null
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      ~ id                                   = "i-04c0e1bc6b05b8b57" -> (known after apply)
      ~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
      + instance_lifecycle                   = (known after apply)
      ~ instance_state                       = "running" -> (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-048c35632b8cb0cca" -> (known after apply)
      ~ private_dns                          = "ip-10-192-4-11.eu-west-2.compute.internal" -> (known after apply)
      ~ private_ip                           = "10.192.4.11" -> (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      ~ secondary_private_ips                = [] -> (known after apply)
      ~ security_groups                      = [ # forces replacement
          + "sg-0a015bbb1dc6abac9",
        ]
      + spot_instance_request_id             = (known after apply)
        tags                                 = {
            "Name" = "spoke2-ec2"
        }
      + user_data_base64                     = (known after apply)
      ~ vpc_security_group_ids               = [
          - "sg-0a015bbb1dc6abac9",
        ] -> (known after apply)

Notice the “# forces replacement” on the security_groups directive.

Turns it it was quite a simple fix, although there are lifecycle directives you can add to avoid things being destroyed (or destroyed when you don’t expect them to be), in my case I was using the wrong directive for adding security groups. Instead of “security_groups”, I should have been using “vpc_security_groups_ids” instead. Swapping these directives in the Terraform allowed me to plan and apply without destroying my EC2 instances (when no modifications has been made).

resource "aws_instance" "spoke1-ec2" {
  provider             = aws.spoke1
  ami                  = "ami-008ea0202116dbc56"
  instance_type        = "t2.micro"
  tenancy              = "default"
  availability_zone    = "eu-west-2a"
  key_name             = ""
  subnet_id            = aws_subnet.spoke1.id
  #security_groups      = [aws_security_group.spoke1-ec2-sg.id] 
  vpc_security_group_ids = [aws_security_group.spoke1-ec2-sg.id] # avoids replacement each time.
  iam_instance_profile = aws_iam_instance_profile.ec2_profile1.name

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

I should be using vpc_security_group_ids, instead of security_groups. The latter is only for default VPC and EC2-Classic.

Leave a comment