Lifecycle block throws error

Hi,
I recently upgrade my terraform version to 0.12.21. I followed the official guide in this process. The syntax changes were implemented correctly. In my code, I use the official AWS modules to provision the infrastructure.
The AMI ids are dynamically generated. Before the upgrade, whenever I wanted to add another server to our infrastructure, terraform plan would output that it wanted to delete the already existing servers due to the change in AMI id. To address this issue, I would modify the module to ignore AMI changes as shown below and it would fix the problem.

     resource "aws_instance" "this_t2" {
      count = "${var.instance_count * local.is_t_instance_type}"

      ami                    = "${var.ami}"
      instance_type          = "${var.instance_type}"
      user_data              = "${var.user_data}"
      subnet_id              = "${element(distinct(compact(concat(list(var.subnet_id), var.subnet_ids))),count.index)}"
      key_name               = "${var.key_name}"
      monitoring             = "${var.monitoring}"
      vpc_security_group_ids = ["${var.vpc_security_group_ids}"]
      iam_instance_profile   = "${var.iam_instance_profile}"

      associate_public_ip_address = "${var.associate_public_ip_address}"
      private_ip                  = "${var.private_ip}"
      ipv6_address_count          = "${var.ipv6_address_count}"
      ipv6_addresses              = "${var.ipv6_addresses}"

      ebs_optimized          = "${var.ebs_optimized}"
      volume_tags            = "${var.volume_tags}"
      root_block_device      = "${var.root_block_device}"
      ebs_block_device       = "${var.ebs_block_device}"
      ephemeral_block_device = "${var.ephemeral_block_device}"

      source_dest_check                    = "${var.source_dest_check}"
      disable_api_termination              = "${var.disable_api_termination}"
      instance_initiated_shutdown_behavior = "${var.instance_initiated_shutdown_behavior}"
      placement_group                      = "${var.placement_group}"
      tenancy                              = "${var.tenancy}"

      credit_specification {
        cpu_credits = "${var.cpu_credits}"
      }

      tags = "${merge(map("Name", (var.instance_count > 1) || (var.use_num_suffix == "true") ? format("%s-%d", var.name, count.index+1) : var.name), var.tags)}"

      lifecycle {
        # Due to several known issues in Terraform AWS provider related to arguments of aws_instance:
        # (eg, https://github.com/terraform-providers/terraform-provider-aws/issues/2036)
        # we have to ignore changes in the following arguments
        ignore_changes = ["private_ip", "root_block_device", "ebs_block_device", "ami"]
      }
    }

However, after the upgrade when I run terraform plan, the output shows that all the servers in the environment will be deleted and recreated. I have tried to make similar changes like fore by adding lifecycle block, but I get an error when I run terraform plan.
resource "aws_instance" "this" {
  count = var.instance_count

  ami              = var.ami
  instance_type    = var.instance_type
  user_data        = var.user_data
  user_data_base64 = var.user_data_base64
  subnet_id = length(var.network_interface) > 0 ? null : element(
    distinct(compact(concat([var.subnet_id], var.subnet_ids))),
    count.index,
  )
  key_name               = var.key_name
  monitoring             = var.monitoring
  get_password_data      = var.get_password_data
  vpc_security_group_ids = var.vpc_security_group_ids
  iam_instance_profile   = var.iam_instance_profile

  associate_public_ip_address = var.associate_public_ip_address
  private_ip                  = length(var.private_ips) > 0 ? element(var.private_ips, count.index) : var.private_ip
  ipv6_address_count          = var.ipv6_address_count
  ipv6_addresses              = var.ipv6_addresses

  ebs_optimized = var.ebs_optimized
  
  dynamic "root_block_device" {
    for_each = var.root_block_device
    content {
      delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null)
      encrypted             = lookup(root_block_device.value, "encrypted", null)
      iops                  = lookup(root_block_device.value, "iops", null)
      kms_key_id            = lookup(root_block_device.value, "kms_key_id", null)
      volume_size           = lookup(root_block_device.value, "volume_size", null)
      volume_type           = lookup(root_block_device.value, "volume_type", null)
    }
  }

  dynamic "ebs_block_device" {
    for_each = var.ebs_block_device
    content {
      delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null)
      device_name           = ebs_block_device.value.device_name
      encrypted             = lookup(ebs_block_device.value, "encrypted", null)
      iops                  = lookup(ebs_block_device.value, "iops", null)
      kms_key_id            = lookup(ebs_block_device.value, "kms_key_id", null)
      snapshot_id           = lookup(ebs_block_device.value, "snapshot_id", null)
      volume_size           = lookup(ebs_block_device.value, "volume_size", null)
      volume_type           = lookup(ebs_block_device.value, "volume_type", null)
    }
  }

  dynamic "ephemeral_block_device" {
    for_each = var.ephemeral_block_device
    content {
      device_name  = ephemeral_block_device.value.device_name
      no_device    = lookup(ephemeral_block_device.value, "no_device", null)
      virtual_name = lookup(ephemeral_block_device.value, "virtual_name", null)
    }
  }

  dynamic "network_interface" {
    for_each = var.network_interface
    content {
      device_index          = network_interface.value.device_index
      network_interface_id  = lookup(network_interface.value, "network_interface_id", null)
      delete_on_termination = lookup(network_interface.value, "delete_on_termination", false)
    }
  }

  source_dest_check                    = length(var.network_interface) > 0 ? null : var.source_dest_check
  disable_api_termination              = var.disable_api_termination
  instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior
  placement_group                      = var.placement_group
  tenancy                              = var.tenancy

  lifecyle {
    ignore_changes = all
  }

  tags = merge(
    {
      "Name" = var.instance_count > 1 || var.use_num_suffix ? format("%s-%d", var.name, count.index + 1) : var.name
    },
    var.tags,
  )

  volume_tags = merge(
    {
      "Name" = var.instance_count > 1 || var.use_num_suffix ? format("%s-%d", var.name, count.index + 1) : var.name
    },
    var.volume_tags,
  )

  credit_specification {
    cpu_credits = local.is_t_instance_type ? var.cpu_credits : null
  }
}

Any help will be highly appreciated. I have checked terraform resources here

Hi @mohammadasim!

Could you please share the output from terraform plan when it’s proposing to replace the instances? Seeing exactly what Terraform is proposing should help to narrow down what’s causing this different behavior.

Thanks, below is the output.
module.jenkins.aws_instance.this[0] will be created
+ resource “aws_instance” “this” {
+ ami = “ami-090e07f7fc8bcfc3a”
+ arn = (known after apply)
+ associate_public_ip_address = true
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_termination = true
+ ebs_optimized = false
+ get_password_data = false
+ host_id = (known after apply)
+ iam_instance_profile = “update-dns”
+ id = (known after apply)
+ instance_state = (known after apply)
+ instance_type = “t2.small”
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = “_key”
+ monitoring = false
+ network_interface_id = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = “subnet-0a7b24e8280cf71f9”
+ tags = {
+ “Terraform” = “true”
}
+ tenancy = “default”
+ volume_tags = {
+ “Name” = “Jenkins”
}
+ vpc_security_group_ids = [
+ “sg-03b5bc0f9d8323f60”,
+ “sg-0af9aaae25e071b7e”,
+ “sg-0bc6e8a9b93d63d9a”,
]

      + credit_specification {
          + cpu_credits = "standard"
        }

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
      + delete_on_termination = (known after apply)
      + encrypted             = (known after apply)
      + iops                  = (known after apply)
      + kms_key_id            = (known after apply)
      + volume_id             = (known after apply)
      + volume_size           = (known after apply)
      + volume_type           = (known after apply)
    }
}

  # module.jenkins.aws_instance.this_t2 will be destroyed
  - resource "aws_instance" "this_t2" {
  - ami                          = "ami-001dae151248753a2" -> null
  - arn                          = "arn:aws:ec2:ap-southeast-2:185772430353:instance/i-0e9545a86ca46179d" -> null
  - associate_public_ip_address  = true -> null
  - availability_zone            = "ap-southeast-2a" -> null
  - cpu_core_count               = 1 -> null
  - cpu_threads_per_core         = 1 -> null
  - disable_api_termination      = true -> null
  - ebs_optimized                = false -> null
  - get_password_data            = false -> null
  - hibernation                  = false -> null
  - iam_instance_profile         = "update-dns" -> null
  - id                           = "i-0e954378986ca46179d" -> null
  - instance_state               = "running" -> null
  - instance_type                = "t2.small" -> null
  - ipv6_address_count           = 0 -> null
  - ipv6_addresses               = [] -> null
  - key_name                     = "<our>_key" -> null
  - monitoring                   = false -> null
  - network_interface_id         = "eni-09053d21bfc7b8c06" -> null
  - primary_network_interface_id = "eni-09053d21bfc7b8c06" -> null
  - private_dns                  = "ip-10-0-3-243.ap-<region>-2.compute.internal" -> null
  - private_ip                   = "10.0.3.243" -> null
  - public_dns                   = "ec2-<removed-by-user>-2.compute.amazonaws.com" -> null
  - public_ip                    = "<removed-by-user>" -> null
  - security_groups              = [] -> null
  - source_dest_check            = true -> null
  - subnet_id                    = "subnet-0a7b24e8280cf71f9" -> null
  - tags                         = {
      - "Terraform"       = "true"
    } -> null
  - tenancy                      = "default" -> null
  - volume_tags                  = {} -> null
  - vpc_security_group_ids       = [
      - "sg-03b5bc0f9d8323f60",
      - "sg-0af9aaae25e071b7e",
      - "sg-0bc6e8a9b93d63d9a",
    ] -> null

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

  - root_block_device {
      - delete_on_termination = true -> null
      - encrypted             = false -> null
      - iops                  = 100 -> null
      - volume_id             = "vol-0209e529a57b1f714" -> null
      - volume_size           = 8 -> null
      - volume_type           = "gp2" -> null
    }

  - timeouts {}
}

Hi @mohammadasim,

The plan you’ve shared is showing actions against two different resources:

  • module.jenkins.aws_instance.this_t2 will be destroyed
  • module.jenkins.aws_instance.this[0] will be created

If these two resources are representing the same conceptual pool of servers in EC2 then it seems like the problem is that the resource has been renamed in Terraform, not that the ami value is changing. Terraform uses the name to correlate resources from one run to the next, so Terraform can’t tell the difference between you renaming a resource in-place or you deleting one resource and adding another one.

You could potentially avoid this change by changing the resource name back to this_t2, rather than this. If you do want to change the name of this resource without recreating all of its instances, you’d need to use terraform mv to change the resource names in the state too, and then Terraform should see that there’s nothing to change on the next terraform plan.

1 Like

Thanks @apparentlymart for pointing me at the right direction. The module resource name had indeed changed, by changing the resource name back to what it was and also by adding
lifecycle { ignore_changes = [ami] }
I managed to solve the problem. Really appreciate your help.

1 Like