Anatomia do Terraform apply

Image: Infra as Code

Um dia desses, enquanto trabalhava com o Terraform, fui executar o ‘terraform apply’ e vi a seguinte mensagem: você tem 5 recursos para criar e 3 para destruir. Fiquei me perguntando, como ele faz isso? Como sabe exatamente o que destruir?

Claro, sabemos que existe um arquivo chamado TFstate, onde o estado atual é armazenado, e que existe um plano que define o que queremos construir ou destruir. Essas são as partes clássicas da ferramenta, explicadas no início de qualquer curso ou livro sobre o tema. Mas o que realmente me intrigava era o mecanismo de comparação entre o estado desejado e o estado atual, como exatamente isso acontece por baixo dos panos? Este post é justamente sobre essa curiosidade, e ao investigá-la descobri alguns detalhes internos do Terraform bastante interessantes.

Quando você executa o comando ‘terraform apply’, o Terraform realiza três leituras fundamentais, lê o que você deseja fazer (criar, modificar ou destruir), que podemos chamar de Estado Desejado (ED), lê o arquivo TF-state, que podemos chamar de Último Estado Salvo (UES) e por fim, verifica o estado atual dos recursos no destino, que podemos chamar de Estado Atual (EA).

É aqui que começa a parte mais interessante, de posse dos três dados ED, UES e EA, o Terraform desencadeia uma sequência de transformações e comparações para realizar uma etapa fundamental a reconciliação em 3 fases.

As fases da reconciliação

Quando você executa terraform apply, o Terraform vai construir um algoritmo de reconciliação em 3 fases, vejamos quais são elas: Refresh leitura do estado real, Diff / Plan realiza o cálculo do delta e por fim gera a Graph Walk execução com DAG, não é irado?!? Vamos dissecar cada uma dessas fases.

Fase 1: Refresh

Antes de tomar qualquer decisão, é preciso coletar informações sobre as partes envolvidas, e saber o que realmente existe na infraestrutura. Não dá para confiar cegamente no state file (terraform.tfstate), porque alguém pode ter alterado algo manualmente (drift).

O que acontece:

  1. O Terraform lê o state file e identifica todos os recursos que ele gerencia
  2. Para cada recurso, ele chama a API do provider (ex: AWS API) perguntando: “esse recurso ainda existe? quais são seus atributos atuais?”
  3. Atualiza o state file em memória com os valores reais

Fase 2: Diff e Plan

Essa é a etapa central da reconciliação. Neste momento o Terrafom já tem em memória as três entidades: ED, UES e ED para realizar uma comparação de 3 vias (three-way merge).

Para cada recurso, ele compara atributo por atributo e decide uma ação:

Situação Ação
Existe no código .tf, não existe no state create
Existe em ambos, mas atributos diferem update (in-place) ou replace (destroy+create)
Não existe no código, mas existe no state destroy
Código e state são idênticos no-op (nada a fazer)

A lógica do three-way merge para cada atributo, para quem já teve aulas de eletrônica, vai lembrar de uma tabela verdade em ação. O Three-Way Merge compara 3 fontes para cada atributo de cada recurso:

  • UES o último valor conhecido no state file (o que o Terraform “lembra”)
  • ED o valor desejado no código .tf (o que você quer)
  • EA o valor real na infraestrutura, obtido no refresh via API (o que existe de fato)
UES (tf state) ED (.tf) EA (API) Decisão
“a” “a” “a” nada muda
“a” “b” “a” update, você mudou o código
“a” “a” “b” drift detectado, reconverge para o desejado
“a” “b” “c” conflito, o config (.tf) vence, porque o desired state tem prioridade

O resultado dessa fase é o plano de execução, uma lista ordenada de ações (create, update, replace, destroy) que o Terraform mostra para você aprovar.

Fase 3: Graph Walk e DAG

Agora o Terraform precisa executar as ações do plano, mas na ordem correta. Recursos têm dependências entre si, por exemplo, você não pode criar uma subnet antes da VPC existir, certo?

Para seguirmos precisamos conhecer o conceito de DAG

DAG é um grafo em que as conexões entre nós possuem direção (A → B não implica B → A) e não existem ciclos ou seja, é impossível partir de um nó e retornar a ele mesmo seguindo as arestas. No Terraform, cada recurso é representado como um nó e cada dependência como uma aresta direcionada, formando um DAG que determina a ordem de criação e destruição dos recursos. Essa estrutura permite ao Terraform identificar o que pode ser executado em paralelo (nós sem dependências entre si) e o que precisa aguardar (nós cujas dependências ainda não foram resolvidas). Caso um ciclo seja detectado, o Terraform interrompe a execução antes de realizar qualquer operação, pois seria impossível determinar uma ordem válida de processamento.

Ao criar seus códigos de Terrafom você pode influenciar nesse processo se utilizar Dependências implícitas ou Dependências explícitas (depends_on) veja como:

resource "aws_subnet" "web" {
  vpc_id = aws_vpc.main.id  # Terraform sabe que a subnet depende da VPC
}
resource "aws_instance" "app" {
  subnet_id = aws_subnet.web.id

  depends_on = [aws_iam_role_policy.s3_access]  # sem referência direta, mas precisa existir antes
}

Você pode visualiza o DAG completo do seu projeto e identificar as dependências inesperadas ou ciclos o seu projeto, basta fazer isso.

terraform graph | dot -Tpng > graph.png

Você vai obter uma imagem semelhante a essa

Terraform plan Graphiz - Image: Infra as Code

3. Graph walk

O Graph Walk é o mecanismo que o Terraform usa para determinar em que ordem executar as operações nos recursos.

Construção do grafo

O Terraform analisa todos os arquivos .tf e monta um DAG (Directed Acyclic Graph) onde:

  • Cada é um recurso, data source, variable, output, provider, etc.
  • Cada aresta é uma dependência entre eles
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "web" {
  vpc_id = aws_vpc.main.id        # ← dependência implícita
}

resource "aws_instance" "app" {
  subnet_id = aws_subnet.web.id   # ← dependência implícita
  depends_on = [aws_s3_bucket.logs] # ← dependência explícita
}

O grafo resultante:

aws_s3_bucket.logs ──┐
                     ↓
aws_vpc.main → aws_subnet.web → aws_instance.app

Caminhando (Walk)

O Terraform percorre o grafo usando ordenação topológica:

  1. Identifica os nós sem dependências pendentes (raízes do grafo)
  2. Executa esses nós em paralelo
  3. Quando um nó termina, libera os nós que dependiam dele
  4. Repete até todos os nós serem processados
Passo 1: executa em paralelo → aws_vpc.main + aws_s3_bucket.logs
Passo 2: ambos terminaram   → aws_subnet.web (depende só da VPC)
Passo 3: subnet pronta      → aws_instance.app (depende de subnet + s3)

Na destruição, a ordem é invertida, destrói primeiro quem depende, depois as dependências.

Destroy passo 1: aws_instance.app
Destroy passo 2: aws_subnet.web + aws_s3_bucket.logs
Destroy passo 3: aws_vpc.main

Detecção de ciclos

Nós vimos que em uma DAG, ciclos são proibidos. Se o Terraform detectar uma dependência circular, ele falha antes de executar qualquer coisa:

Error: Cycle: aws_security_group.a, aws_security_group.b

O reconciliation loop é o modelo mental por trás do Terraform. Não é um loop contínuo é um loop sob demanda que roda a cada plan ou apply.

O Ciclo

Image: Infra as Code

Cada etapa em detalhe

  1. Read desired state parseia os arquivos .tf e resolve variáveis, módulos, expressões
  2. Read actual state faz refresh: consulta as APIs dos providers para cada recurso no state file
  3. Diff compara atributo por atributo de cada recurso:
  4. Plan gera a lista de ações respeitando as regras do provider
  5. Apply executa as ações via graph walk
  6. Update state grava o novo estado no tfstate após cada operação individual

Propriedade de convergência

O reconciliation loop é idempotente e convergente: se você rodar terraform apply N vezes sem mudar o código, após a primeira execução as subsequentes não fazem nada (No changes. Infrastructure is up-to-date.). Isso é o que torna o modelo declarativo confiável.

O fluxo completo

Vamos amarrar alguns pontos importantes aqui, não há rollback automático. se o apply falha no meio, o state fica parcialmente aplicado. Você precisa rodar terraform apply novamente para convergir, o modelo é declarativo e convergente: você declara o estado desejado, e o Terraform calcula o caminho para chegar lá, não importa o estado atual. O state file é o source of truth para o Terraform saber o que ele gerencia. Recursos criados fora do Terraform são invisíveis até serem importados terraform import. Resultando esse fluxo final.

Image: Infra as Code

Resumão

Curtiu conhecer o que acontece quando você roda terraform apply? O Terraform segue um caminho bem estruturado para decidir o que fazer com a sua infraestrutura e tudo começa com uma pergunta simples, o que existe de verdade lá fora? Parece o lema do ArquivoX , “a verdade tá lá fora”.

Conhecer as três fases do apply é fundamental, faz toda diferença na prática. Você passa a saber por que o Terraform quer destruir e recriar um recurso em vez de só atualizar, entende o que é um drift, sabe que não existe rollback, conhece o mecanismo de convergência. Essas são as diferenças entre usar o Terraform e realmente dominar a ferramenta.

Quer se aprofundar? Comece pelas referências no final do post, rode terraform graph nos seus projetos e acompanhe os próximos conteúdos aqui no blog. Compartilha com quem também trabalha com IaC, conhecimento bom é conhecimento compartilhado.

Referências

Abraços!

Vida longa e próspera a todos!!


Entre em contato:

NewsLetter - https://engineeringmanager.com.br/
Linkdin - linkedin.com/in/leonardoml/
Twitter: @infraascode_br

Te convido a ver os outros posts do blog Infra-as-Code garanto que tem coisas legais lá!!


--- --- IMPORTANTE --- ---
As opiniões aqui expressas são pessoais e de responsabilidade única e exclusiva do autor, elas não refletem necessariamente a posição das empresas que eu trabalho(ei) e/ou presto(ei) serviço.


bio_banner_test