Handle changed image AMI on AWS

I am creating instances using AMI filter for Ubuntu Linux based on Hashicorp Certified: Terraform Associate course on Udemy.

I have created two servers using this filter, but today the AMI was updated and now when I create Terraform plan it wants to destroy my old servers and create new ones based on this change.

Is there a way to prevent the removal of already provisioned servers? Ideally I would like to use the latest AMI when scaling to more instances and keep the old ones intact.

This is the module I have created:

resource "aws_instance" "ubuntu_linux" {
  ami = data.aws_ami.ubuntu_linux_ami.id
  instance_type = var.instance_type
  key_name = var.key_name

  availability_zone = var.availability_zone
  root_block_device {
    volume_size = var.root_volume_size
  }
  tags = {
    Name = var.instance_name
  }

  vpc_security_group_ids = var.ssh_security_group_ids
}

# AMI Filter for Ubuntu Linux 20.04
data "aws_ami" "ubuntu_linux_ami" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"]
}

Hi @Alko89,

The behavior you’ve outlined here – of making a change that only applies to new instances of a resource rather than immediately replacing the existing ones – is what the ignore_changes lifecycle setting is aimed at:

resource "aws_instance" "ubuntu_linux" {
  ami = data.aws_ami.ubuntu_linux_ami.id
  # ...

  lifecycle {
    ignore_changes = [ami]
  }
}

This tells Terraform to ignore the fact that you’ve (in effect) changed the configuration of this resource when comparing to already-existing instances. With that done, you’ll then need to define some separate process by which the updated AMI will eventually be used, which will ultimately require replacing this instance, but you can make that a separate explicit step rather than it just happening immediately after a new AMI is available.

One direct way to arrange for that replacement would be to use the -replace option to terraform plan, which essentially forces Terraform to generate a “replace” plan for a particular resource instance even though there isn’t a configuration change calling for that:

terraform apply -replace='aws_instance.ubuntu_linux'

This last part is more of an AWS thing than a Terraform thing, but I also wanted to note that when it comes to managing fleets of EC2 instances that all run the same software for the purposes of scaling out, it can be helpful to use AWS autoscaling rather than managing each instance individually in Terraform.

If you use autoscaling then the AMI will be part of the launch configuration or launch template, and so you can get an effect similar to what you described here by using Terraform to change the launch settings such that EC2 autoscaling will use the new settings for any new instances it creates, but will leave the others untouched.

For fleets of instances that are all interchangeable, this model can make it easier to roll out changes such as a new AMI in a way that doesn’t cause downtime, with strategies such as gradually terminating the old instances one by one (waiting each time for autoscaling to automatically launch a replacement in order to retain the desired amount) or by temporarily increasing the desired instance count and then decreasing it again once autoscaling has responded, at which point autoscaling will gradually terminate instances to return to the previous count.

(You can use the termination_policies argument to influence which instances autoscaling will terminate first when it detects that there are more instances than desired.)