Ansible frente a Terraform: ¿cuál necesitas?
Terraform crea el VPS y Ansible lo configura. Compara sus responsabilidades, el traspaso entre ambos, los provisioners y cuándo basta con usar sólo Ansible.
Ansible frente a Terraform en una frase
Ansible frente a Terraform no es una elección entre dos herramientas que hacen el mismo trabajo. Terraform declara qué infraestructura existe: servidores, discos, redes y registros DNS. Ansible declara qué debe cumplirse dentro de una máquina que ya existe: paquetes, usuarios, archivos de configuración y servicios en ejecución. Terraform crea el VPS. Ansible convierte ese VPS en un servidor web.
Ambos son declarativos y ambos se consideran infraestructura como código (IaC). La diferencia real es lo que recuerdan. Terraform escribe un archivo de estado que relaciona cada recurso del código con un objeto real que creó mediante una API. Así puede determinar que eliminar cinco líneas implica destruir un servidor. Ansible no conserva información entre ejecuciones. Se conecta mediante SSH, inspecciona la máquina y cambia sólo lo que aún no coincide con el playbook.
Esa única diferencia explica el resto de esta guía, incluido el motivo por el que mezclar ambos trabajos en una sola herramienta suele causar problemas.
Qué hace realmente Terraform
Terraform se comunica con una API mediante un complemento provider. La página del registro de tu provider define los tipos de recursos que puedes declarar, por lo que un servidor en un host y un servidor en otro host son nombres de recursos distintos con argumentos diferentes.
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}Sustituye cloud_server por el tipo de recurso que documenta tu provider. El bloque output es la parte importante de esta guía, porque así es como la dirección sale de Terraform.
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanterraform init descarga el provider y escribe un archivo de bloqueo. terraform plan muestra la diferencia entre tu código y el archivo de estado y termina con una línea como Plan: 1 to add, 0 to change, 0 to destroy. Lee esa línea cada vez. Algunos argumentos no se pueden cambiar sin recrear el recurso. El plan lo indica con # forces replacement junto al atributo, seguido de 1 to add, 0 to change, 1 to destroy. Aplicar ese plan elimina el servidor y crea uno nuevo y vacío. Así es como se pierden datos que se creían protegidos.
Guardar el plan en un archivo y aplicar el archivo, en lugar de ejecutar un terraform apply sin más, garantiza que se ejecuta exactamente lo que se revisó. Entre ambos comandos, otra persona puede haber cambiado la infraestructura.
terraform.tfstate es la memoria. Si se pierde, Terraform deja de saber que esos servidores son tuyos y el siguiente apply intenta crear duplicados. Guárdalo en un backend remoto en cuanto más de una persona ejecute los comandos, porque dos personas aplicando cambios al mismo tiempo producen esto:
Error: Error acquiring the state lockOpenTofu es un fork de Terraform con los mismos comandos y el mismo formato de archivo. En julio de 2026, todo lo indicado en esta guía funciona si escribes tofu en lugar de terraform.
Qué hace realmente Ansible
Ansible no necesita un agente ni una API. Abre una conexión SSH, copia un módulo pequeño de Python al destino, lo ejecuta y lo elimina. Todo lo que pueda alcanzar mediante SSH y una contraseña de sudo, Ansible puede configurarlo.
- name: Base web server
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running at boot
ansible.builtin.service:
name: nginx
state: started
enabled: trueansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.ymlEl módulo ping comprueba SSH, Python y sudo antes de empezar a depurar un playbook. Un resultado correcto es web1 | SUCCESS => {"ping": "pong"}. La ejecución de --check --diff es lo más parecido a un plan en Ansible: informa de lo que cambiaría sin cambiarlo, aunque las tareas que dependen de tareas anteriores pueden informar de forma incorrecta en modo check, porque el cambio anterior nunca se ha aplicado realmente.
Cada ejecución termina con un resumen como ok=6 changed=2 unreachable=0 failed=0. Ejecute el mismo playbook dos veces. La segunda ejecución debería informar de changed=0. Una tarea que informa de cambios en cada ejecución no es idempotente. Normalmente es una tarea command o shell que debería haberse implementado con un módulo real. Si este tema es nuevo para usted, empiece con su primer playbook de Ansible en un VPS y amplíelo a partir de ahí.
Dónde se solapan las dos herramientas y dónde entran en conflicto
Terraform puede ejecutar comandos en un servidor nuevo con el provisioner remote-exec. La documentación de HashiCorp recomienda usar los provisioners sólo como último recurso. Hay buenas razones para ello.
Un provisioner se ejecuta únicamente cuando se crea el recurso. Si edita el script, no sucede nada en el servidor existente, porque, desde el punto de vista de Terraform, el recurso ya coincide con el código. Los pasos del provisioner nunca aparecen en terraform plan, por lo que la revisión no muestra ninguna señal de ellos. Si el script falla, Terraform marca el recurso como tainted y el siguiente apply destruye y vuelve a crear un servidor que probablemente funcionaba correctamente.
El fallo también se produce en un momento poco adecuado. El provider informa de que el servidor se ha creado en cuanto la API lo indica, aunque el sistema operativo todavía está arrancando y sshd aún no está escuchando.
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible presenta la tentación opuesta. Los módulos para la nube pueden crear servidores, y esto funciona cuando se trata de unas pocas máquinas. Lo que se pierde es el grafo de dependencias y el archivo de estado. Ansible creará un recurso sin problemas, pero si elimina la tarea del playbook, el recurso seguirá ejecutándose y generando costes, porque nada registró que alguna vez le perteneciera.
La regla que se desprende de esto es la siguiente: deje que Terraform gestione los objetos que una API crea y destruye, y deje que Ansible gestione todo lo que está dentro de un sistema operativo ya iniciado.
La transferencia, en la práctica
La transferencia es un límite, no una integración. Terraform termina, publica una dirección y se detiene. Ansible comienza a partir de esa dirección.
terraform apply -auto-approve
terraform output -raw web_ip
printf '[web]\n%s ansible_user=root\n' "$(terraform output -raw web_ip)" > inventory.ini
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.ymlterraform output -raw imprime un valor sin comillas ni envoltorio JSON. Es lo que se necesita dentro de una sustitución de shell. Para varios servidores, use terraform output -json y construya el inventario a partir de ese resultado, porque -raw sólo gestiona una cadena, un número o un booleano.
Conviene mantener el paso ping entre las dos herramientas. Permite distinguir entre «Terraform me proporcionó la dirección incorrecta» y «mi playbook tiene un error». Ambos problemas parecen idénticos cuando el playbook es lo primero que toca el servidor nuevo.
Lectura del estado de Terraform como inventario de Ansible
Si prefiere no escribir ningún archivo de inventario, la colección cloud.terraform lee el estado directamente.
ansible-galaxy collection install cloud.terraformEscriba terraform.yml junto a su playbook:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.ymlDebe conocer dos aspectos antes de depender de este método. El plugin ejecuta terraform show sobre project_path, por lo que ese directorio debe estar inicializado previamente; de lo contrario, el plugin falla. Tampoco crea hosts a partir de los recursos de sus servidores: lee los recursos ansible_host y ansible_group, que debe declarar en el código de Terraform mediante el proveedor de Ansible. No aparece nada en ansible-inventory --graph hasta que los añada.
Un archivo de inventario generado sin más es más fácil de depurar y funciona con cualquier proveedor. El plugin resulta útil cuando el inventario supera unas pocas máquinas y la edición manual empieza a producir errores tipográficos. Ese es también el punto en el que administrar varios servidores Linux desde una sola máquina de control se convierte en un flujo de trabajo real y no sólo en una costumbre.
¿Realmente necesita Terraform?
La mayoría de las personas que leen esto no lo necesitan, al menos todavía. Terraform compensa su coste cuando crear y destruir infraestructura es una tarea repetida. Si ha contratado un VPS mediante un panel de control y pretende conservarlo durante dos años, Terraform describe algo que ocurre una sola vez y añade un archivo de estado que no debe perder.
Use Terraform cuando reconstruya entornos con frecuencia, cuando el entorno de staging deba coincidir exactamente con producción, cuando varias personas cambien la infraestructura y quiera un plan revisable antes de eliminar algo, o cuando lo que administra incluya algo más que servidores: registros DNS, balanceadores de carga y reglas de firewall que residen en la API de un proveedor.
Use sólo Ansible cuando los servidores tengan una vida útil larga y sean pocos, y cuando la pregunta diaria sea «¿está este servidor configurado correctamente?» en lugar de «¿existe este servidor?». Un único playbook que refuerce la seguridad de un servidor nuevo cubre lo mismo que los primeros diez minutos en un VPS nuevo, con la ventaja de que se ejecuta de la misma forma en el servidor siguiente.
El orden de aprendizaje se deriva de esto. Ansible resulta útil desde el primer servidor que administra. Terraform resulta útil a partir del tercer entorno que reconstruye.
Qué falla en la transferencia
El servidor no está listo. Terraform termina correctamente, pero Ansible falla de inmediato.
fatal: [web1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.10 port 22: Connection refused", "unreachable": true}La API devolvió una dirección antes de que sshd estuviera escuchando. Espere a que el puerto esté disponible en lugar de añadir una espera fija. Ansible tiene ansible.builtin.wait_for_connection para este caso exacto; ejecútelo como la primera tarea del play. Cuando el mismo playbook se dirija a un grupo en lugar de a un único equipo recién creado, decida de antemano qué debe ocurrir cuando un host siga sin estar disponible, porque Ansible excluye ese host del resto de la ejecución y la línea de resumen es el único lugar donde lo indica.
La clave del host ha cambiado. Destruyó y volvió a crear el servidor, y el nuevo responde en la misma dirección con una clave distinta.
Host key verification failed.Elimine la entrada obsoleta con ssh-keygen -R 203.0.113.10. Esto ocurre constantemente cuando Terraform se encarga de reconstruir servidores. Por eso conviene evitar las reconstrucciones frecuentes en equipos que almacenan datos.
Sudo falla. fatal: [web1]: FAILED! => {"msg": "Missing sudo password"} significa que become: true necesita una contraseña en ese host. Configure sudo sin contraseña para el usuario de despliegue o pase --ask-become-pass.
Terraform quiere destruir algo que no modificó. El plan muestra cambios que nunca escribió. Esto significa que la infraestructura real se desvió del código, normalmente porque alguien cambió una configuración en el panel web del proveedor. Ejecute terraform plan -refresh-only para ver únicamente esa diferencia y decida si el código o el recurso activo es incorrecto. Nunca aplique un plan destructivo que no pueda explicar línea por línea.
Ansible informa de cambios en todas las ejecuciones. Una tarea shell sin una condición creates o when se ejecuta siempre. No es un problema meramente visual, porque ya no podrá usar changed=0 como indicación de que un servidor se encuentra en el estado solicitado.
FAQ
¿Puede Terraform sustituir a Ansible?
No para la configuración dentro de un servidor. Terraform puede ejecutar scripts con el provisioner remote-exec, pero estos sólo se ejecutan al crear el recurso, nunca aparecen en terraform plan y marcan el recurso como afectado cuando fallan. Esto programa su destrucción y reconstrucción en la siguiente ejecución de apply. Terraform no tiene un equivalente de un módulo que compruebe si nginx ya está instalado y no haga nada en ese caso. Use Terraform para crear la máquina y transfiera después la configuración a Ansible.
¿Puede Ansible sustituir a Terraform?
Para un número reducido de servidores de larga duración, sí. Ansible tiene módulos de nube que crean servidores, y si solicita dos instancias VPS y las conserva, es suficiente. Lo que pierde es el archivo de estado y el grafo de dependencias: si elimina una tarea del playbook, el recurso sigue ejecutándose y generando costes, porque Ansible nunca registró que lo había creado. Terraform habría planificado su destrucción.
¿Cuál debería aprender primero?
Ansible, si actualmente administra servidores. Resulta útil desde el primer equipo, sólo necesita SSH y sus conocimientos se aplican a un servidor que haya solicitado manualmente. Terraform resulta útil más adelante, cuando reconstruye entornos de forma repetida o administra recursos del proveedor además de servidores, como registros DNS y reglas de firewall.
¿Cómo paso la IP del servidor nuevo de Terraform a Ansible?
Declare un output en el código de Terraform y léalo después de ejecutar apply. terraform output -raw web_ip muestra el valor sin formato para usarlo en una sustitución del shell, y terraform output -json proporciona todas las salidas a la vez cuando hay varios hosts. Escriba ese valor en un archivo de inventario o instale la colección cloud.terraform y apunte ansible-inventory -i terraform.yml --graph al directorio del proyecto.
¿Por qué falla mi playbook justo después de que Terraform termina?
El proveedor informa de que el servidor se ha creado en cuanto su API lo indica, aunque el sistema operativo todavía se esté iniciando. Por eso SSH rechaza la conexión durante los primeros segundos. El error es UNREACHABLE! con Connection refused. Haga que ansible.builtin.wait_for_connection sea la primera tarea del play en lugar de calcular una duración fija para sleep, porque el tiempo de arranque varía según la imagen y el plan.