Ansible y Terraform no compiten, y tratarlos como si compitieran te cuesta dinero
Terraform crea la máquina, Ansible decide qué hay dentro. Dónde va la frontera, por qué los provisioners son una trampa y qué cambia cuando tu entorno es casi todo contenedores.
"¿Ansible o Terraform?" es una pregunta que nos siguen haciendo, y tiene la misma respuesta que "¿martillo o destornillador?". Resuelven mitades distintas del mismo trabajo, y la decisión interesante es dónde trazas la línea entre ellos.
La diferencia de fondo
Terraform es declarativo y con estado: describes la infraestructura que quieres, él la compara con un fichero de estado y crea, modifica o destruye recursos hasta que coincidan. Sabe lo que creó, así que puede retirarlo.
Ansible es procedimental y sin estado: se conecta a los hosts y ejecuta tareas, cada una escrita para ser idempotente. No hay fichero de estado, así que no sabe qué hizo la vez anterior y no puede limpiar detrás de sí.
Esa diferencia lo decide todo. Cualquier cosa cuyo ciclo de vida necesites gestionado —creada, modificada y sobre todo destruida— pertenece a Terraform. Cualquier cosa que sea una secuencia de operaciones contra algo que ya existe pertenece a Ansible.
La frontera
Terraform: redes, subredes, grupos de seguridad, balanceadores, bases de datos gestionadas, IAM, DNS, clústeres de Kubernetes, almacenamiento de objetos y la propia máquina virtual. Si tiene API en el proveedor de nube, es de Terraform.
Ansible: lo que pasa dentro de la máquina virtual. Instalación de paquetes, configuración de servicios, usuarios, certificados, despliegue de aplicaciones sobre instancias y procedimientos operativos puntuales sobre una flota: parchear, recoger logs, rotar una credencial en 200 máquinas.
La costura es el momento en que la máquina arranca. Terraform entrega una IP y deja de preocuparse.
No uses los provisioners de Terraform
remote-exec y local-exec son la forma más habitual de equivocarse aquí, y la propia documentación de Terraform los llama último recurso.
El problema es que un provisioner se ejecuta exactamente una vez, al crear, y sus efectos son invisibles para el estado. Si falla a mitad, el recurso queda marcado como corrupto y tu siguiente apply destruye y recrea una máquina porque la instalación de un paquete tuvo un error de red pasajero. Si cambias el script, a las máquinas existentes no les pasa nada. Si necesitas volver a ejecutarlo, destruyes la instancia.
Los dos patrones correctos:
Hornea la imagen. Packer construye una AMI o imagen con todo instalado y Terraform la referencia. El arranque baja a segundos, la máquina es idéntica siempre y la imagen tiene una versión a la que volver. Es la respuesta correcta para todo lo que autoescala: no puedes tener una instancia nueva configurándose durante cuatro minutos en mitad de un pico de tráfico.
Configura después, por fuera. Terraform saca el inventario y Ansible corre contra él como una etapa aparte del pipeline:
terraform output -json instances | \
jq -r '.value[] | "\(.name) ansible_host=\(.private_ip)"' > inventory
ansible-playbook -i inventory site.yml
O usa el plugin de inventario dinámico para que Ansible consulte al proveedor por etiqueta y no se pase ningún fichero:
# inventory_aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions: [eu-west-1]
filters:
tag:Role: app
instance-state-name: running
keyed_groups:
- key: tags.Environment
cloud-init en user_data queda a medio camino: bien para un puñado de líneas (instalar el agente, unirse al sistema de gestión de configuración), doloroso como sitio donde guardar configuración de verdad, porque depurarlo significa leer logs en una máquina que quizá ni arrancó.
Qué cambia con contenedores
Si tus cargas son contenedores sobre Kubernetes, casi todo lo que hacía Ansible se ha mudado a la construcción de la imagen y al manifiesto. La imagen es la configuración, kubectl o Argo CD es el despliegue, y no hay máquina que configurar porque los nodos los gestiona un grupo en el que nunca entras.
Ansible no desaparece, encoge: el entorno que no está en contenedores (siempre queda algo), la personalización a nivel de nodo que un grupo gestionado no sabe expresar, los equipos de red y el hardware en local, y los procedimientos operativos. Esta última categoría está infravalorada: un playbook es mucho mejor guía de incidente que una página de wiki, porque es ejecutable, revisable y deja registro.
Ejecutar ambos en CI
Terraform necesita estado remoto con bloqueo, plan en la PR y apply al integrar. Ansible necesita menos ceremonia, pero le viene bien la misma disciplina: ejecutarlo desde CI y no desde portátiles, con --check --diff en la pull request como equivalente de un plan.
Dale a cada uno sus propias credenciales. El rol de Terraform puede crear y destruir infraestructura; el de Ansible solo debería poder conectarse a instancias y leer los secretos que distribuye. Son radios de impacto distintos y no hay razón para fundirlos.