El Modelo Mental de Infraestructura Declarativa
Cada herramienta cubierta en esta sección —Ansible, Pulumi, Terraform envuelto en Python, o un script de boto3 hecho a mano— se juzga según el mismo modelo subyacente, ya sea que su documentación lo diga explícitamente o no.
Ese modelo es infraestructura declarativa: describes lo que debería existir, un proceso de reconciliación compara esa descripción con lo que realmente existe, y solo la diferencia es la que se actúa.
Esta es una forma de programar diferente a la que la mayoría de los ingenieros aprenden primero, donde el código es una secuencia de pasos ejecutados en orden, y acostumbrarse al cambio es lo que realmente hace que los playbooks de Ansible, los programas de Pulumi y los planes de Terraform sean predecibles en lugar de misteriosos.
Resumen
- Las herramientas de infraestructura declarativa toman una descripción del estado deseado y reconcilian la infraestructura real hacia él, en lugar de ejecutar una secuencia fija de pasos imperativos.
- Por Qué Importa: Los scripts imperativos ("crea esto, luego aquello") fallan de forma impredecible en las reejecuciones y después de fallos parciales; la reconciliación declarativa está diseñada para ser reejecutada de forma segura desde cualquier punto de partida.
- Conceptos Clave: estado deseado, idempotencia, bucle de reconciliación, deriva (drift), archivo de estado, planificar/aplicar.
- Cuándo Usar: Cualquier infraestructura que se creará más de una vez, que será tocada por más de una persona, o que necesite converger después de un fallo parcial —lo que en la práctica significa casi toda la infraestructura real más allá de un prototipo individual.
- Limitaciones / Compensaciones: Las herramientas declarativas intercambian el control procesal detallado por la predecibilidad, e introducen su propia superficie de fallo: deriva del archivo de estado, bloqueos y obsolescencia del plan/aplicación.
- Temas Relacionados: gestión de configuración, GitOps, aprovisionamiento en la nube, política como código.
Fundamentos
Un script imperativo le dice al ordenador exactamente qué hacer, en orden: crear este bucket, luego adjuntar esta política, luego crear esta cola.
Ejecútalo dos veces y la segunda ejecución generalmente falla, porque el bucket ya existe y la mayoría de las APIs rechazan una llamada de creación duplicada directamente.
Una herramienta declarativa en cambio te pide que describas el estado final que deseas —"este bucket debería existir, con el versionado habilitado"— y deja la secuenciación y el manejo de duplicados a la propia herramienta.
La idempotencia es la propiedad que hace que esto sea seguro: una operación idempotente produce el mismo resultado final ya sea que se ejecute una o diez veces, por lo que las reejecuciones después de un bloqueo, un problema de red o una reintentación de CI nunca son destructivas por defecto.
El mecanismo subyacente a la idempotencia es un bucle de reconciliación: leer el estado real actual, compararlo con el estado deseado declarado, calcular la diferencia y actuar solo sobre esa diferencia.
Si el bucket ya existe con el versionado habilitado, el bucle de reconciliación no ve ninguna diferencia y no hace nada —ese comportamiento de "no-op en la reejecución" es la firma práctica de un sistema correctamente idempotente.
Una analogía simple: un termostato no ejecuta "enciende la calefacción durante exactamente 4 minutos" como un script fijo.
Compara continuamente la temperatura real de la habitación con la temperatura objetivo y actúa solo sobre la brecha, que es por lo que funciona correctamente independientemente de cuál haya sido la temperatura inicial de la habitación.
Ansible, Pulumi y Terraform (con o sin un envoltorio de Python) son todas implementaciones de esta misma idea de reconciliación, difiriendo principalmente en dónde guardan su registro del estado actual y cómo expresan el estado deseado en código.
Mecánicas e Interacciones
El bucle de reconciliación necesita dos entradas para calcular una diferencia: el estado deseado (tu código) y un registro del estado actual.
Las herramientas difieren drásticamente en cómo obtienen la segunda entrada, y esta diferencia explica la mayoría de sus peculiaridades de comportamiento.
Terraform y Pulumi mantienen un archivo de estado explícito —un registro serializado de cada recurso que la herramienta cree que creó y sus últimos atributos conocidos— y comparan tu código contra ese archivo, no necesariamente contra la infraestructura en vivo, a menos que se ejecute un paso de actualización primero.
Ansible, por el contrario, es en su mayoría sin estado: cada tarea consulta directamente el objetivo en vivo (¿está presente este archivo, está instalado este paquete?) y solo entonces decide si actuar, que es por lo que los playbooks de Ansible tienden a ser naturalmente idempotentes por tarea sin un archivo de estado separado que gestionar.
Esta distinción es la fuente más común de confusión para los ingenieros que se mueven entre herramientas: la "deriva" (drift) en Terraform significa que el archivo de estado difiere de la infraestructura real (alguien cambió algo en la consola), mientras que la "deriva" en Ansible significa que el objetivo en vivo difiere del estado declarado en el playbook, verificado de nuevo en cada ejecución.
Planificar antes de aplicar existe porque una diferencia contra un registro de estado obsoleto o incompleto puede ser incorrecta, y el paso de planificar es el punto de revisión humana donde una acción destructiva propuesta (eliminar un recurso con estado, por ejemplo) se detecta antes de que se ejecute.
# La forma de la reconciliación, independiente de la API de cualquier herramienta específica
def reconcile(desired: dict, get_current: callable, apply_change: callable) -> dict:
current = get_current() # leer el estado del mundo real
diff = {k: v for k, v in desired.items() if current.get(k) != v}
if diff:
apply_change(diff) # actuar solo sobre la diferencia
return {"changed": bool(diff), "delta": diff}Esta es la forma que implementa internamente cada herramienta declarativa, ya sea un binario de Go de 500 líneas (Terraform) o un proveedor de recursos de Python (Pulumi) —el punto es el patrón de comparación-luego-solo-diferencia, no ninguna implementación específica.
El bloqueo (locking) es el otro detalle mecánico que vale la pena interiorizar: dado que el archivo de estado (o el objetivo en vivo, para Ansible) es compartido, las aplicaciones concurrentes de dos ingenieros o dos trabajos de CI compitiendo contra la misma infraestructura corromperán o entrarán en conflicto con ese registro compartido a menos que la herramienta imponga un bloqueo durante la aplicación.
Consideraciones y Aplicaciones Avanzadas
A escala, el modelo de reconciliación tiene que manejar un caso que la simple analogía del termostato no aborda: fallo parcial durante la aplicación, donde algunos recursos en un lote tienen éxito y otros fallan.
Una herramienta declarativa bien comportada deja el archivo de estado reflejando exactamente lo que realmente se creó, por lo que la diferencia de la siguiente ejecución solo se dirige al trabajo restante, en lugar de reintentar recursos ya creados o ignorar silenciosamente el fallo.
La detección de deriva (drift detection) se convierte en una preocupación operativa de primera clase una vez que la infraestructura es gestionada por más de un equipo o más de una herramienta —un cambio manual en la consola, un script separado que usa boto3 directamente, o un recurso caducado pueden causar que los estados declarados y reales diverjan silenciosamente, sin que se genere ningún error hasta que el próximo plan lo muestre como un cambio inesperado.
La política como código extiende la puerta de planificar/aplicar: en lugar de que un humano revise una diferencia de texto, una comprobación de política automatizada inspecciona la diferencia planificada y bloquea la aplicación si viola una regla (etiquetas requeridas faltantes, un grupo de seguridad demasiado permisivo, un bucket S3 público), convirtiendo el paso de revisión en una puerta de acceso comprobable y repetible en lugar de una decisión manual.
Las configuraciones multi-entorno (desarrollo, staging, producción) llevan el modelo más allá: cada entorno necesita su propio registro de estado y a menudo sus propios parámetros de estado deseado, y la disciplina que evita una aplicación accidental en producción se trata casi por completo de mantener el estado aislado por entorno, no de que el código sea fundamentalmente diferente.
Python entra en este panorama en dos roles distintos que vale la pena distinguir: como un tiempo de ejecución de IaC de primera clase (Pulumi, donde Python es el lenguaje de estado deseado), o como una capa de orquestación alrededor del ciclo de planificar/aplicar de otra herramienta (un script que ejecuta terraform plan, verifica la política y ejecuta condicionalmente terraform apply) —el modelo de reconciliación subyacente es idéntico de cualquier manera.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Script imperativo con boto3 | Control total, sin herramientas adicionales | No es idempotente por defecto, sin detección de deriva, sin paso de planificar | Tareas únicas pequeñas, scripts de pegamento |
| Ansible | Sin agente, naturalmente idempotente por tarea, sin archivo de estado que gestionar | Gráfico de dependencias entre recursos más débil incorporado que Terraform/Pulumi | Gestión de configuración, convergencia ad-hoc |
| Terraform (HCL o a través de envoltorio de Python) | Modelo de estado maduro, enorme ecosistema de proveedores, fuertes diferencias de planificar | HCL es un lenguaje separado para aprender (a menos que se envuelva) | IaC multi-nube, estándar de la organización con estrictas puertas de planificar |
| Pulumi (Python nativo) | Lenguaje de programación real: bucles, funciones, pruebas | Ecosistema de proveedores más pequeño que Terraform en algunas áreas | Equipos que quieren IaC como código Python genuino y comprobable |
Conceptos Erróneos Comunes
- "Declarativo solo significa usar YAML o un archivo de configuración en lugar de un script." El formato del archivo es incidental; lo que importa es si un bucle de reconciliación compara el estado deseado con el estado actual, en lugar de un script que simplemente ejecuta pasos en orden sin importar lo que ya exista.
- "Idempotente significa que la operación es de solo lectura o inofensiva." Las operaciones idempotentes pueden absolutamente crear, modificar o eliminar recursos —la garantía es solo que repetir la misma operación no produce un resultado diferente o duplicado.
- "Si el plan de Terraform no muestra cambios, la infraestructura definitivamente coincide con el código." El plan solo compara el código con el archivo de estado; si alguien cambió el recurso real fuera de Terraform y no se ha ejecutado una actualización, el propio archivo de estado puede estar obsoleto aunque el plan parezca limpio.
- "Ansible no tiene estado, ¿entonces no puede detectar la deriva?" Ansible verifica el estado en vivo en cada ejecución de tarea en lugar de un registro en caché, que es un mecanismo diferente para el mismo objetivo, no una ausencia de conciencia del estado.
- "Planificar y aplicar son solo un diálogo de confirmación." El paso de planificar es una computación completa de la diferencia real contra una instantánea de estado específica; tratarlo como un sello de goma omite el único lugar donde los cambios destructivos son visibles antes de que se ejecuten.
Preguntas Frecuentes
¿Qué significa exactamente "idempotente" para el código de infraestructura?
- Ejecutar la misma declaración dos veces produce el mismo estado final ambas veces.
- La segunda ejecución debería reportar "sin cambios" si nada cambió realmente.
- No significa que la operación sea segura o reversible — solo que la repetición no duplica ni corrompe el resultado.
¿Por qué Terraform y Pulumi necesitan un archivo de estado en absoluto?
Las APIs de la nube generalmente no pueden responder "¿qué recursos creó mi código?", solo "¿qué existe?". El archivo de estado es el propio registro de la herramienta que vincula las declaraciones de recursos de tu código con los IDs de recursos reales, que es lo que hace posibles las diferencias precisas y las actualizaciones dirigidas.
¿Ansible tiene un equivalente a un archivo de estado de Terraform?
No por defecto — la mayoría de los módulos de Ansible consultan directamente el objetivo en vivo en cada ejecución en lugar de consultar un registro en caché, que es por lo que los playbooks de Ansible son naturalmente idempotentes por tarea sin gestión de estado separada.
¿Qué es la deriva de infraestructura, mecánicamente?
La deriva es cualquier brecha entre el estado que una herramienta cree que existe (su archivo de estado, para Terraform/Pulumi, o el objetivo en vivo, para Ansible) y lo que está realmente desplegado —comúnmente causado por cambios manuales en la consola o por una segunda herramienta que modifica los mismos recursos.
¿Por qué se considera tan importante "planificar antes de aplicar"?
El paso de planificar calcula y muestra la diferencia exacta sobre la que actuará la aplicación, que es el único punto donde un humano o una verificación de política automatizada puede detectar un cambio destructivo inesperado (como la eliminación accidental de un recurso) antes de que ocurra.
¿En qué se diferencia Pulumi de Terraform si ambos son declarativos?
Pulumi expresa el estado deseado como Python nativo (u otro lenguaje de propósito general) con bucles, condicionales y funciones reales; Terraform usa su propio lenguaje HCL, aunque CDKTF te permite generar configuración de Terraform desde Python como una capa superior.
¿Puedo escribir código de infraestructura idempotente sin ninguna herramienta de IaC?
Sí — un script de boto3 que verifica la existencia antes de crear, y verifica la configuración actual antes de actualizar, es idempotente bajo la misma definición, solo que sin un archivo de estado compartido o un paso de planificar.
¿Qué sucede si una aplicación falla a mitad de camino?
Un modelo de estado correctamente implementado registra exactamente qué recursos tuvieron éxito antes del fallo, por lo que la diferencia de la siguiente aplicación solo se dirige a los recursos restantes, aún no creados — este es uno de los principales beneficios prácticos del modelo de reconciliación sobre un script lineal.
¿Por qué los equipos añaden comprobaciones de políticas además de planificar/aplicar?
Porque un humano que revisa una gran diferencia de plan puede pasar por alto un problema sutil pero importante (un bucket público, una etiqueta faltante); una comprobación de política automatizada inspecciona la misma diferencia programáticamente y bloquea la aplicación ante violaciones específicas y definidas.
¿Es el bloqueo de estado realmente necesario para un equipo pequeño?
Se vuelve necesario tan pronto como dos personas o dos trabajos de CI puedan ejecutar la aplicación contra la misma infraestructura concurrentemente — sin un bloqueo, ambos pueden leer el mismo estado obsoleto y aplicar cambios conflictivos.
¿Cómo se relaciona este modelo específicamente con las herramientas de gestión de configuración?
La gestión de configuración (Ansible) aplica el mismo modelo declarativo/idempotente a nivel de la configuración de una sola máquina (paquetes, archivos, servicios) en lugar de a nivel de aprovisionamiento de recursos completos en la nube, pero el modelo mental de reconciliación es idéntico.
¿Cuál es el mayor riesgo práctico del modelo declarativo?
Tratar el archivo de estado (o la verificación del objetivo en vivo, para Ansible) como infalible — si se desincroniza de la realidad y nadie se da cuenta, la diferencia del próximo plan puede ser peligrosamente incorrecta, proponiendo "arreglar" algo que un humano cambió intencionalmente fuera de banda.
Relacionados
- Fundamentos de Automatización de Infraestructura - ejemplos prácticos de idempotencia y planificación antes de aplicar
- Ansible - el modelo de convergencia sin estado, por tarea
- Pulumi (IaC con Python) - infraestructura declarativa como Python nativo
- Terraform con Python - CDKTF y envolviendo la CLI de Terraform
- Aprovisionamiento de Recursos en la Nube - aplicando el modelo a recursos reales en la nube
- Pruebas de Código de Infraestructura - validando planes antes de que se apliquen
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.