Terraform Associate study materials
Terraform Associate study resources

Renaming Twelve Instances Without a Midnight Outage

Terraform Associate · Put your knowledge to work in context.

All case studies

Renaming Twelve Instances Without a Midnight Outage

A web tier is twelve identical EC2 instances declared as resource "aws_instance" "web" { count = 12 ... }, behind an ALB whose target group attachments also use count = 12 and element(aws_instance.web[*].id, count.index). Three changes are queued: rename the resource block from web to app, give each instance a Name tag that includes its availability zone, and shrink the fleet to eleven for cost reasons. lifecycle { prevent_destroy = true } is set on the instances after an earlier incident. The engineer who attempted the rename reported 12 to destroy, 12 to add and stopped. A second engineer proposed deleting prevent_destroy temporarily so the plan would run.

1. Explain the 12/12 plan and the correct mechanism to do the rename, including every reference that must change.

Read the worked answer

A resource's address is its identity. Renaming the block changes aws_instance.web[0] to aws_instance.app[0], so Terraform sees twelve deletions and twelve unrelated creations - even though nothing about the instances themselves changed.

Mechanism: a moved block, committed alongside the rename:

moved {
  from = aws_instance.web
  to   = aws_instance.app
}

For a count-based resource a single block covers all instances; Terraform maps them by index. terraform state mv aws_instance.web aws_instance.app also works, but leaves no record for other branches or environments.

Also update:

  • the target group attachments - element(aws_instance.web[*].id, count.index) becomes aws_instance.app[*].id, otherwise the config fails validation;
  • any outputs, depends_on entries, and IAM policy resources referencing the old address;
  • data sources or locals built from aws_instance.web.

Expected plan after the fix: no changes at all for the instances. Keep the moved block in the configuration - deleting it after a successful apply in one environment means every other environment and every stale branch replans a full replacement.

2. For the Name tag, one engineer wants `tags = { Name = aws_instance.app[count.index].availability_zone }`; another wants a data source. Who is right, and what actually breaks?

Read the worked answer

The self-reference is wrong. Referencing a resource's own attribute inside its own block is a self-reference cycle; Terraform rejects it. Even if it were legal, the availability zone of a count-based instance is not known until apply, so the tag would be (known after apply) on the first plan and then produce a diff on every subsequent plan.

The data source is right, but only if you actively control the mapping. data "aws_availability_zones" gives plan-time names, and Terraform's allocation for count does not guarantee index n lands in name n. If you want the tag and the placement to agree, set them together:

resource "aws_instance" "app" {
  count             = 12
  availability_zone = data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)]
  tags              = { Name = "web-${data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)]}-${count.index}" }
}

Better still: if AZs matter to identity, switch to for_each over a map keyed by AZ so the tag is derived from the key and is stable and readable. Note that changing tags alone is an in-place update - it does not force replacement - so this change is safe to make independently of the rename.

3. Now shrink from twelve to eleven while `prevent_destroy` is set. Give the sequence and say why the obvious shortcut is a bad idea.

Read the worked answer

count = 11 destroys index 11. With prevent_destroy = true, Terraform errors during plan and nothing applies - this is the guard working, not a bug.

Sequence:

  1. Deregister the instance at index 11 from the target group (the attachment list drops to 11 too) and let connections drain.
  2. In one reviewed commit, set prevent_destroy = false and count = 11 together, so the plan shows exactly one destroy and zero replacements.
  3. Apply during a window, then confirm the ALB has no unhealthy targets.
  4. Restore prevent_destroy = true in a follow-up commit.

Why not the shortcut: terraform state rm aws_instance.app[11] makes plan clean instantly, but the instance keeps running untracked and untagged by the code - a silent cost and security leak that nobody will find for months. Deleting prevent_destroy and leaving it deleted removes the guard for every future change.

Judgement note: do not try to drive prevent_destroy from a variable. Lifecycle guard rails are meant to be static and reviewable; a variable that turns them off at apply time makes the protection worthless. Two small commits are the honest version.