Dos personas, un estado
El plan que quería crearlo todo
Jon, del equipo de la app, necesita una caché para las sesiones de la API: que Ana no tenga que iniciar sesión cada vez que abre la app. La infraestructura de la API ya está en código, así que clona el repositorio, añade la caché y ejecuta terraform plan. El plan quiere crearlo todo: la VPC, las subredes, la base de datos. Ocho recursos.
El código era el mismo; lo que Jon no tenía era el estado. Seguía en el portátil de Marta, en un fichero terraform.tfstate junto al código, como deja Terraform las cosas si nadie dice otra cosa. Para el Terraform de Jon, la nube estaba vacía.
Mientras Marta trabajaba sola, el estado era un fichero suyo. Con dos personas, el estado es una base de datos compartida, con todo lo que eso trae: dónde vive, quién la lee, qué pasa si dos escriben a la vez. Este episodio va de quién lo toca.
Marta y Jon, sobre la misma VPC
Cada panel tiene dos líneas de tiempo, que corre hacia abajo: Marta a la izquierda y Jon a la derecha, y en medio el estado, con su versión (el serial, que sube con cada escritura), cuántos recursos tiene y quién tiene el lock. Es la misma pieza que la de la serie de consistencia, con un fichero en lugar de una fila. A la izquierda, el estado está en un bucket, pero cada uno aplica desde su portátil y nadie espera a nadie. A la derecha, el bucket tiene lock y quien aplica es un pipeline: planifica en cada pull request y aplica al fusionarla.
Cada botón es un escenario que empieza de cero con la VPC aplicada (versión 7, siete recursos). Arriba de cada panel, si la nube y el estado cuadran. Mira el marcador Manos, que cuenta quién puede aplicar, y el fichero de estado que hay debajo.
Qué ha pasado
El estado, fuera del portátil
El primer escenario empieza como empezó Jon: sin el estado de Marta, su plan es +8, todo. Lo que falta es un sitio compartido para el estado, y lo habitual es un bucket en la propia nube, declarado en el código como backend. Terraform lo descarga antes de cada plan y lo sube al terminar cada apply. Con el estado en el bucket, el plan de Jon es el suyo: +1, la caché.
Dos applies a la vez: el último que escribe gana
Ahora Marta añade una alarma y Jon, la caché, a la vez. A la izquierda, los dos leen la versión 7. Marta aplica, su alarma tarda un segundo en crearse y escribe la versión 8. Jon aplica, su caché tarda unos minutos y escribe también la versión 8: la 7 que leyó más la caché. La alarma de Marta sigue en la nube, pero ya no está en el estado. Nadie ha hecho nada mal: los dos han leído, calculado y escrito, y el último ha pisado al primero. Es un lost update, el de los dos cajeros a la vez de la serie de consistencia, con un fichero en lugar de un saldo.
El daño llega después. La alarma existe, pero Terraform no la conoce: el siguiente plan de Marta querrá crearla otra vez, y si su nombre no puede repetirse, fallará. Alguien tendrá que importarla a mano, o borrarla desde la consola. El marcador lo cuenta como 1 fuera del estado.
A la derecha, el apply de Marta coge un lock sobre el estado antes de leerlo y lo suelta después de escribirlo. El pipeline de Jon llega, ve el lock y espera. Cuando lo coge, vuelve a leer, ya la versión 8, vuelve a planificar y aplica solo lo suyo. Los dos cambios quedan en el estado. El lock no hace más rápido a nadie: pone a uno detrás del otro.
terraform {
backend "nube_bucket" {
bucket = "bcps-tfstate"
key = "api/terraform.tfstate"
use_lockfile = true
}
}
El plan de ayer no vale hoy
Tercer escenario. Ayer Jon planificó +1, la caché, y alguien lo revisó. Hoy Marta cambia una regla del grupo de seguridad y la aplica. Luego Jon aplica. A la izquierda, desde su portátil: su rama no tiene el cambio de Marta, así que el plan de hoy no es el de ayer, es +1 ~1: la caché y deshacer la regla de Marta. Jon escribe yes porque la revisión decía +1, y la regla desaparece.
A la derecha, el pipeline guardó el plan de la PR junto con la versión del estado sobre la que se calculó, la 7. Al fusionar, intenta aplicar ese plan exacto, ve que el estado ya va por la 8 y se niega: Saved plan is stale. Vuelve a planificar desde la rama principal, que ya tiene el cambio de Marta, y aplica solo la caché. Un plan aprobado es una promesa sobre un estado concreto: si el estado cambia, la promesa ya no vale.
El pipeline cambia también quién tiene las manos. A la izquierda, pueden aplicar Marta, Jon y cualquiera con permisos y el repositorio clonado. A la derecha, solo el pipeline: las personas proponen, revisan y fusionan, pero nadie aplica desde su portátil. Es lo que cuenta el marcador Manos.
Cuándo hace daño: el lock que nadie suelta
Cuarto escenario. El runner del pipeline se cae justo después de escribir el estado y antes de soltar el lock. El lock se queda puesto, a nombre de un proceso que ya no existe, y el pipeline de Jon espera. Y todos los demás. Hasta que alguien comprueba que de verdad no hay ningún apply en marcha y lo rompe a mano con terraform force-unlock. Romperlo sin comprobar es volver a la izquierda: si el apply seguía vivo, dos escriben a la vez.
A la izquierda no se queda nada puesto, así que Jon aplica sin esperar. Pero el apply de Marta se cortó a mitad, y la alarma que llegó a crearse no llegó al estado. Sin lock, nadie sabe si hay otro apply a medias; con lock, al menos se sabe que algo pasó.
Cuándo hace daño: el estado es un secreto
Activa el bucket legible por toda la empresa y mira el fichero de estado. Terraform guarda los atributos de cada recurso, y entre ellos está la contraseña de la base de datos, en claro. Marcarla como sensitive en el código solo la oculta en la salida del plan; en el estado sigue ahí. Quien pueda leer el bucket, la tiene.
El estado hay que tratarlo como un secreto aunque nadie lo haya decidido así: cifrado, con versiones para poder volver atrás si se corrompe, y con acceso solo para quien aplica, que con un pipeline es el propio pipeline. Y los secretos, fuera del estado cuando se pueda: que la base de datos los guarde en el gestor de secretos de la nube y Terraform solo sepa dónde están. La gestión de secretos es un bloque entero del catálogo; aquí solo aparece como daño.
Criterio
| Estado local | Bucket sin lock | Bucket con lock | Lock y pipeline | |
|---|---|---|---|---|
| Quién ve el estado | Quien tiene el portátil | Todos los que aplican | Todos los que aplican | El pipeline y quien lo administra |
| Dos applies a la vez | Cada uno el suyo: duplicados | Lost update | El segundo espera | El segundo espera |
| Plan revisado ≠ plan aplicado | Posible | Posible | Posible | Se detecta (plan guardado) |
| Lo que añade | Nada | Un bucket | Un lock que puede quedarse puesto | Un pipeline que mantener; todo cambio pasa por una PR |
| Bueno para | Una prueba de una tarde | Nada: si hay bucket, que tenga lock | Un equipo pequeño | Producción, o más de un equipo |
-
¿Lo va a aplicar alguien más, alguna vez?Incluida tú dentro de seis meses, desde otro portátil.estado remoto, con lock y cifrado
- Estado local.
-
¿Es producción, o lo tocan varias personas cada semana?O guarda secretos que no todos deberían ver.pipeline: plan en la PR, apply al fusionar, y nadie aplica desde su portátil
- Estado remoto con lock, aplicando a mano. Y un procedimiento escrito para el lock que se queda puesto.
En el mundo real
Cada nube tiene su backend: en AWS, un bucket de S3, que durante años necesitó una tabla de DynamoDB para el lock y desde Terraform 1.10 puede usar un fichero de lock en el propio bucket (use_lockfile); en Azure, un contenedor de Blob Storage, con el lock como un lease sobre el fichero; en Google Cloud, un bucket de Cloud Storage. HCP Terraform (antes Terraform Cloud) guarda el estado, el lock y el historial por su cuenta, y ejecuta los applies.
El flujo de plan en la PR y apply al fusionar tiene nombre propio desde que lo popularizó Atlantis, una herramienta que comenta el plan en cada pull request y aplica con un comentario. Spacelift, env0 o HCP Terraform hacen lo mismo como servicio. Todos resuelven el plan obsoleto de la misma forma: si el estado ha cambiado desde el plan, no se aplica.
Sobre los secretos: OpenTofu cifra el estado antes de subirlo desde su versión 1.7, y Terraform añadió en la 1.10 valores efímeros, que se usan durante el apply y no se escriben en el estado. En los dos casos sigue haciendo falta lo básico: versiones activadas en el bucket, cifrado en reposo y permisos de lectura tan estrechos como los de escritura.
Siguiente episodio
Con un solo estado, el lock pone a Marta y a Jon en fila. Pero la infraestructura crece: ya no es solo la VPC, son la red, los datos y la app, en tres entornos. Todo sigue en el mismo estado, y un cambio en la memoria de la app de desarrollo planifica 2.000 recursos, entre ellos la red de producción.
Episodio 4 Cuánto rompe un apply Partir el estado por capa y por entorno, y los contratos entre las partes.← Todos los episodios · Episodio 2: El orden importa · Cuentas en el mapa de BCPS Bank