Quando o terraform e a AWS não interagem como você espera

April 10, 2025    portugues devops terraform aws

Contexto

Em algumas situações o terraform vai aplicar mudanças na AWS e o resultado não será exatamente o que você espera. Vou apresentar um caso abaixo para que você possa entender como isso funciona.

Usarei o exemplo do autoscaling group e launch template e apresentarei que mesmo usando os valores oferecidos pela AWS como o esperado, o funcionamento dos serviços poderão não atender a sua expectativa.

Introdução

Autoscaling Group (ASG) da AWS é um recurso que ajusta automaticamente o número de instâncias EC2 em execução, com base em regras pré-definidas. É um serviço da AWS para que você possa criar um cluster de máquinas para atender uma determinada demanda. Normalmente é usado junto com balanceadores de carga, que não serão tratados aqui pra evitar a mistura de assuntos.

Launch Template da AWS é um modelo que define configurações padronizadas para iniciar instâncias EC2, como tipo, AMI, rede e armazenamento, permitindo consistência, versionamento e automação no provisionamento de recursos. O Autoscaling Group usa o Launch Template para criar o seu conjunto de máquinas.

Criando um Autoscaling Group com Launch Template

Segue abaixo o código do Launch Template e Autoscaling Group. Eu vou ignorar os outros recursos e data que normalmente são usados nesse código, para evitar muito código pra ler. O importante aqui é entender o problema e não o código inteiro.

Primeiro o Launch Template:

resource "aws_launch_template" "this" {
  name_prefix   = "${var.name}-lt-"
  image_id      = data.aws_ami.this.id
  instance_type = var.instance_type

  iam_instance_profile {
    name = local.iam_instance_profile_name
  }

  network_interfaces {
    associate_public_ip_address = var.subnet_tier == "public" ? true : false
  }

  tag_specifications {
    resource_type = "instance"

    tags = merge({ "Name" = var.name })
  }
}

Importante atentar para essa parte do código, que é onde especificamos qual a AMI ID usaremos nesse Launch Template:

resource "aws_launch_template" "this" {
  ...
  image_id      = data.aws_ami.this.id
  ...

O data que coleta a ami está usando essa configuração:

data "aws_caller_identity" "current" {}
data "aws_ami" "this" {
  most_recent = true

  filter {
    name   = "name"
    values = ["imagem-base-*"]
  }

  owners = [data.aws_caller_identity.current.account_id]
}

Como pode ver no código acima, ele pega a AMI mais recente, fazendo filtro por uma AMI que comece com “imagem-base” da sua conta, pois no owners estamos usando o data que coleta o account id da sessão atual.

Tendo o Launch Template: pronto, agora vamos para o Autoscaling Group:

resource "aws_autoscaling_group" "this" {
  name_prefix         = "${var.name}-asg-"
  max_size            = var.max_size
  min_size            = var.min_size
  vpc_zone_identifier = data.aws_subnets.this.ids
  launch_template {
    id      = aws_launch_template.this.id
    version = "$Latest"
  }

  instance_refresh {
    strategy = "Rolling"

    preferences {
      min_healthy_percentage = 50
      instance_warmup        = 30
    }
  }

  tag {
    key                 = "Name"
    value               = var.name
    propagate_at_launch = true
  }

  lifecycle {
    create_before_destroy = true
  }
}

É importante atentar para esse parte do código:

  launch_template {
    id      = aws_launch_template.this.id
    version = "$Latest"
  }

Esse bloco usa como base a documentação oficial desse recurso.

O conflito

Ao especificar o valor "$Latest" o Autoscaling Group será criado na primeira execução, mas caso você gere uma imagem nova ou troque qualquer configuração no Launch Template o Autoscaling Group não será alterado, e dessa forma ele não fará o que você espera, que seria reciclar as instâncias atuais, usando o instance refresh com a estratégia Rolling como especificado nessa parte do código do Autoscaling Group:

  instance_refresh {
    strategy = "Rolling"

    preferences {
      min_healthy_percentage = 50
      instance_warmup        = 30
    }
  }

Após a criação de todos os recursos, ao executar o terraform plan, esse seria o resultado:

Terraform will perform the following actions:

  # module.ec2.aws_launch_template.this will be updated in-place
  ~ resource "aws_launch_template" "this" {
        id                      = "lt-0737f082513450ac"
      ~ image_id                = "ami-0311c1f1234d3743" -> "ami-07d391347313902"
      ~ latest_version          = 11 -> (known after apply)
        name                    = "image_base-lt-20250410135927734600000001"
        # (9 unchanged attributes hidden)

      + iam_instance_profile {}

        # (2 unchanged blocks hidden)
    }

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

Perceba que apenas o Launch Template é modificado e por conta disso o Autoscaling Group não fará nada com a instância. Eu esperava que existisse algum processo interno na AWS que percebesse a mudança de versão e assim executasse o instance refresh com a estratégia Rolling, que criaria uma maquina nova, com a AMI nova, esperaria 30 segundos, que foi configurado no instance_warmup e desligaria a instancia com a AMI antiga, mas nada acontece.

Para corrigir esse problema forçaremos o Autoscaling Group ser modificado.

No bloco launch_template do Autoscaling Group faremos a modificação desse código:

  launch_template {
    id      = aws_launch_template.this.id
    version = "$Latest"
  }

Para esse:

  launch_template {
    id      = aws_launch_template.this.id
    version = aws_launch_template.this.latest_version
  }

E essa será o plano do terraform que você verá toda vez que a AMI mudar:

Terraform will perform the following actions:

  # module.ec2.aws_autoscaling_group.this will be updated in-place
  ~ resource "aws_autoscaling_group" "this" {
        id                               = "base_image-asg-20250410145900574600000002"
        name                             = "base_image-asg-20250410145900574600000002"
        # (27 unchanged attributes hidden)

      ~ launch_template {
            id      = "lt-0737f0825b14de0ac"
            name    = "base_image-lt-20250410135927734600000001"
          ~ version = "12" -> (known after apply)
        }

        # (2 unchanged blocks hidden)
    }

  # module.ec2.aws_launch_template.this will be updated in-place
  ~ resource "aws_launch_template" "this" {
        id                      = "lt-0737f0825b14de0ac"
      ~ image_id                = "ami-0311c1f1234d3743" -> "ami-07d391347313902"
      ~ latest_version          = 12 -> (known after apply)
        name                    = "base_image-lt-20250410135927734600000001"
        tags                    = {}
        # (9 unchanged attributes hidden)

      + iam_instance_profile {}

        # (2 unchanged blocks hidden)
    }

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

Como pode ver acima, o terraform agora controla esse versionamento e garante que toda vez que uma versão nova do Launch Template é criada, será atualizado o seu número no Autoscaling Group e assim forçando a AWS a iniciar o processo de Instance Refresh.

Conclusão

As vezes é importante avaliar se o controle não pode estar no seu código terraform e assim você ter um retorno mais garantido de como sua infraestrutura vai funcionar.

Agradecimentos

Obrigado a somatorio que sempre revisa tudo que escrevo.

Obrigado a Flávia e Roberta que leram e me deram feedback.