Anatomía del Terraform apply

Anatomía del Terraform apply

Image: Infra as Code

Un día de estos, mientras trabajaba con Terraform, fui a ejecutar el ’terraform apply’ y vi el siguiente mensaje: tienes 5 recursos para crear y 3 para destruir. Me quedé preguntándome, ¿cómo hace esto? ¿Cómo sabe exactamente qué destruir?

Claro, sabemos que existe un archivo llamado TFstate, donde se almacena el estado actual, y que existe un plan que define lo que queremos construir o destruir. Esas son las partes clásicas de la herramienta, explicadas al inicio de cualquier curso o libro sobre el tema. Pero lo que realmente me intrigaba era el mecanismo de comparación entre el estado deseado y el estado actual, ¿cómo sucede exactamente eso por debajo? Este post es justamente sobre esa curiosidad, y al investigarla descubrí algunos detalles internos de Terraform bastante interesantes.

Cuando ejecutas el comando ’terraform apply’, Terraform realiza tres lecturas fundamentales: lee lo que deseas hacer (crear, modificar o destruir), que podemos llamar Estado Deseado (ED); lee el archivo TF-state, que podemos llamar Último Estado Guardado (UES); y por último, verifica el estado actual de los recursos en el destino, que podemos llamar Estado Actual (EA).

Es aquí donde comienza la parte más interesante: con los tres datos ED, UES y EA, Terraform desencadena una secuencia de transformaciones y comparaciones para realizar una etapa fundamental, la reconciliación en 3 fases.

Las fases de la reconciliación

Cuando ejecutas terraform apply, Terraform va a construir un algoritmo de reconciliación en 3 fases, veamos cuáles son: Refresh lectura del estado real, Diff / Plan realiza el cálculo del delta y por último genera el Graph Walk, ejecución con DAG, ¿no es genial? Vamos a diseccionar cada una de esas fases.

Fase 1: Refresh

Antes de tomar cualquier decisión, es necesario recolectar información sobre las partes involucradas, y saber lo que realmente existe en la infraestructura. No se puede confiar ciegamente en el state file (terraform.tfstate), porque alguien puede haber alterado algo manualmente (drift).

Lo que sucede:

  1. Terraform lee el state file e identifica todos los recursos que gestiona
  2. Para cada recurso, llama a la API del provider (ej: AWS API) preguntando: “¿ese recurso todavía existe? ¿cuáles son sus atributos actuales?”
  3. Actualiza el state file en memoria con los valores reales

Fase 2: Diff y Plan

Esta es la etapa central de la reconciliación. En este momento Terraform ya tiene en memoria las tres entidades: ED, UES y EA para realizar una comparación de 3 vías (three-way merge).

Para cada recurso, compara atributo por atributo y decide una acción:

Situación Acción
Existe en el código .tf, no existe en el state create
Existe en ambos, pero los atributos difieren update (in-place) o replace (destroy+create)
No existe en el código, pero existe en el state destroy
Código y state son idénticos no-op (nada que hacer)

La lógica del three-way merge para cada atributo, para quien ya tuvo clases de electrónica, recordará una tabla de verdad en acción. El Three-Way Merge compara 3 fuentes para cada atributo de cada recurso:

  • UES el último valor conocido en el state file (lo que Terraform “recuerda”)
  • ED el valor deseado en el código .tf (lo que quieres)
  • EA el valor real en la infraestructura, obtenido en el refresh vía API (lo que existe de hecho)
UES (tf state) ED (.tf) EA (API) Decisión
“a” “a” “a” nada cambia
“a” “b” “a” update, cambiaste el código
“a” “a” “b” drift detectado, reconverge hacia el deseado
“a” “b” “c” conflicto, el config (.tf) gana, porque el desired state tiene prioridad

El resultado de esta fase es el plan de ejecución, una lista ordenada de acciones (create, update, replace, destroy) que Terraform te muestra para que apruebes.

Fase 3: Graph Walk y DAG

Ahora Terraform necesita ejecutar las acciones del plan, pero en el orden correcto. Los recursos tienen dependencias entre sí; por ejemplo, no puedes crear una subnet antes de que exista la VPC, ¿verdad?

Para seguir necesitamos conocer el concepto de DAG

DAG es un grafo en el que las conexiones entre nodos poseen dirección (A → B no implica B → A) y no existen ciclos, es decir, es imposible partir de un nodo y retornar a él mismo siguiendo las aristas. En Terraform, cada recurso se representa como un nodo y cada dependencia como una arista dirigida, formando un DAG que determina el orden de creación y destrucción de los recursos. Esa estructura permite a Terraform identificar lo que puede ejecutarse en paralelo (nodos sin dependencias entre sí) y lo que necesita esperar (nodos cuyas dependencias todavía no fueron resueltas). En caso de que se detecte un ciclo, Terraform interrumpe la ejecución antes de realizar cualquier operación, pues sería imposible determinar un orden válido de procesamiento.

Al crear tus códigos de Terraform puedes influir en ese proceso si utilizas Dependencias implícitas o Dependencias explícitas (depends_on), mira cómo:

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

  depends_on = [aws_iam_role_policy.s3_access]  # sin referencia directa, pero necesita existir antes
}

Puedes visualizar el DAG completo de tu proyecto e identificar las dependencias inesperadas o ciclos de tu proyecto, basta con hacer esto.

terraform graph | dot -Tpng > graph.png

Vas a obtener una imagen similar a esta

Terraform plan Graphiz - Image: Infra as Code

3. Graph walk

El Graph Walk es el mecanismo que Terraform usa para determinar en qué orden ejecutar las operaciones en los recursos.

Construcción del grafo

Terraform analiza todos los archivos .tf y monta un DAG (Directed Acyclic Graph) donde:

  • Cada nodo es un recurso, data source, variable, output, provider, etc.
  • Cada arista es una dependencia entre ellos
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

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

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

El grafo resultante:

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

Caminando (Walk)

Terraform recorre el grafo usando ordenación topológica:

  1. Identifica los nodos sin dependencias pendientes (raíces del grafo)
  2. Ejecuta esos nodos en paralelo
  3. Cuando un nodo termina, libera los nodos que dependían de él
  4. Repite hasta que todos los nodos sean procesados
Paso 1: ejecuta en paralelo → aws_vpc.main + aws_s3_bucket.logs
Paso 2: ambos terminaron    → aws_subnet.web (depende solo de la VPC)
Paso 3: subnet lista        → aws_instance.app (depende de subnet + s3)

En la destrucción, el orden es invertido: destruye primero a quien depende, después las dependencias.

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

Detección de ciclos

Vimos que en un DAG, los ciclos están prohibidos. Si Terraform detecta una dependencia circular, falla antes de ejecutar cualquier cosa:

Error: Cycle: aws_security_group.a, aws_security_group.b

El reconciliation loop es el modelo mental detrás de Terraform. No es un loop continuo, es un loop bajo demanda que corre en cada plan o apply.

El Ciclo

Image: Infra as Code

Cada etapa en detalle

  1. Read desired state parsea los archivos .tf y resuelve variables, módulos, expresiones
  2. Read actual state hace refresh: consulta las APIs de los providers para cada recurso en el state file
  3. Diff compara atributo por atributo de cada recurso:
  4. Plan genera la lista de acciones respetando las reglas del provider
  5. Apply ejecuta las acciones vía graph walk
  6. Update state graba el nuevo estado en el tfstate después de cada operación individual

Propiedad de convergencia

El reconciliation loop es idempotente y convergente: si corres terraform apply N veces sin cambiar el código, después de la primera ejecución las siguientes no hacen nada (No changes. Infrastructure is up-to-date.). Eso es lo que hace que el modelo declarativo sea confiable.

El flujo completo

Vamos a amarrar algunos puntos importantes aquí: no hay rollback automático. Si el apply falla a la mitad, el state queda parcialmente aplicado. Necesitas correr terraform apply nuevamente para converger; el modelo es declarativo y convergente: declaras el estado deseado, y Terraform calcula el camino para llegar allí, no importa el estado actual. El state file es el source of truth para que Terraform sepa lo que gestiona. Los recursos creados fuera de Terraform son invisibles hasta que sean importados con terraform import. Resultando en este flujo final.

Image: Infra as Code

Resumen

¿Te gustó conocer lo que sucede cuando corres terraform apply? Terraform sigue un camino bien estructurado para decidir qué hacer con tu infraestructura y todo comienza con una pregunta simple: ¿qué existe de verdad allá afuera? Parece el lema de Expediente X, “la verdad está allá afuera”.

Conocer las tres fases del apply es fundamental, hace toda la diferencia en la práctica. Pasas a saber por qué Terraform quiere destruir y recrear un recurso en vez de solo actualizar, entiendes qué es un drift, sabes que no existe rollback, conoces el mecanismo de convergencia. Esas son las diferencias entre usar Terraform y realmente dominar la herramienta.

¿Quieres profundizar? Comienza por las referencias al final del post, corre terraform graph en tus proyectos y acompaña los próximos contenidos aquí en el blog. Comparte con quien también trabaja con IaC, el conocimiento bueno es conocimiento compartido.

Referencias

¡Un abrazo!

¡Larga y próspera vida 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