
Si todavía configuras servidores a mano, entrando por SSH, editando archivos, instalando paquetes y repitiendo todo en la siguiente máquina, el Ansible es la herramienta que jubilas ese ritual. Describes en archivos YAML cómo debe quedar cada servidor, y Ansible lo aplica por SSH, sin instalar ningún agente en las máquinas. Puedes ejecutarlo mil veces: el resultado es el mismo. En esta guía vas a instalar Ansible en Ubuntu, montar un proyecto con inventario, ejecutar comandos puntuales y escribir un playbook con variable, template y handler. Todos los comandos se probaron con ansible-core 2.21 en un laboratorio con tres servidores Ubuntu 24.04.
Cómo nació Ansible
El primer commit de Ansible, con el mensaje “Genesis.”, es del 23 de febrero de 2012, hecho por Michael DeHaan. Él conocía el problema de cerca: en Red Hat había creado el Cobbler, de aprovisionamiento de servidores, y había participado en el Func, de ejecución remota de comandos. Las herramientas de gestión de la época, como Puppet y Chef, exigían un agente instalado en cada servidor y un lenguaje propio. Ansible nació con tres principios que siguen vigentes hoy: sin agente (solo SSH y Python, que ya existen en cualquier Linux), YAML legible en lugar de lenguaje de programación y curva de aprendizaje corta.
El nombre proviene de la ciencia ficción. Ansíble es el comunicador instantáneo entre planetas que Ursula K. Le Guin inventó en 1966, luego popularizado por El juego de Ender, de Orson Scott Card. La metáfora encaja: comandar máquinas remotas al instante, sin intermediarios.
El proyecto se convirtió en empresa, AnsibleWorks, después Ansible Inc., y fue comprado por Red Hat en octubre de 2015, que lo mantiene open source. Hoy el motor está en el paquete ansible-core, y el paquete ansible reúne el motor con decenas de collections de la comunidad, los conjuntos de módulos para cada tecnología.
Cómo trabaja Ansible
Ansible se instala en una sola máquina, la máquina de control: tu portátil o un servidor de administración. Los servidores gestionados solo necesitan SSH y Python 3, que Ubuntu y Debian ya incluyen. Nada de agente, nada de servicio extra ejecutándose en ellos.

Para cada tarea, Ansible abre SSH en los servidores en paralelo (cinco a la vez, por defecto), envía un pequeño programa Python, el módulo, lo ejecuta, recibe el resultado en JSON y borra el archivo temporal. El módulo compara el estado actual con lo que pediste y solo actúa si es necesario.

Instalación en Ubuntu
Hay tres caminos, y la diferencia es la versión:
- Repositorio de Ubuntu: el más sencillo. En Ubuntu 24.04 incluye el paquete
ansible9.2, con ansible-core 2.16. Sirve para aprender. - PPA oficial del proyecto: versión actual. En septiembre de 2026, el PPA ofrece
ansible14.4.0, con ansible-core 2.21.4, para Ubuntu 24.04 y 26.04. Fue lo que usamos en las pruebas. - pipx: aísla Ansible del Python del sistema y funciona en cualquier distribución.
# Opção 1: repositório do Ubuntu
sudo apt update
sudo apt install -y ansible
# Opção 2: PPA oficial (versão atual)
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# Opção 3: pipx
sudo apt install -y pipx
pipx install --include-deps ansible
pipx ensurepath # abra um novo terminal depois deste comando
ansible --version
ansible [core 2.21.4]
config file = /etc/ansible/ansible.cfg
...
python version = 3.12.3 (main, Aug 31 2026, 10:18:26) [GCC 13.3.0] (/usr/bin/python3)
jinja version = 3.1.2
La estructura de un proyecto
Ansible se ejecuta desde cualquier directorio, pero conviene adoptar la estructura convencional desde el primer día:
meu-projeto/
├── ansible.cfg # configuração do projeto
├── inventory.ini # quais máquinas gerenciar
├── site.yml # playbook principal
├── group_vars/ # variáveis por grupo de hosts
│ └── webservers.yml
├── host_vars/ # variáveis de um host específico
│ └── web01.yml
└── roles/ # partes reutilizáveis
└── nginx/
├── tasks/main.yml
├── handlers/main.yml
├── templates/
├── files/
└── defaults/main.yml
- ansible.cfg: configuraciones del proyecto. Ansible usa el archivo del directorio actual antes que el global, en
/etc/ansible/ansible.cfg. - Inventario: la lista de máquinas, organizada en grupos.
- Playbook: el guion en YAML, “en estos hosts, garantiza estas tareas”.
- group_vars y host_vars: valores que cambian por grupo o por máquina, separados de la lógica.
- Roles: playbooks empaquetados para reutilizar en otros proyectos.
Inventario, clave SSH y el primer ping
Crea el proyecto con tres servidores en dos grupos. Cambia las IPs por las tuyas:
mkdir meu-projeto && cd meu-projeto
cat > inventory.ini <<'EOF'
[webservers]
web01 ansible_host=192.168.64.2
web02 ansible_host=192.168.64.3
[dbservers]
db01 ansible_host=192.168.64.4
[all:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
EOF
cat > ansible.cfg <<'EOF'
[defaults]
inventory = inventory.ini
EOF
ansible-inventory --graph
@all:
|--@ungrouped:
|--@webservers:
| |--web01
| |--web02
|--@dbservers:
| |--db01
La línea ansible_python_interpreter fija el Python usado en los servidores. Sin ella, ansible-core 2.21 funciona, pero imprime un aviso en cada host diciendo que usó el Python “descubierto” y que esto puede cambiar si se instala otro Python.
Ansible entra en los servidores con tu clave SSH. Si aún no tienes una, genérala y cópiala a cada servidor:
ssh-keygen -t ed25519
for ip in 192.168.64.2 192.168.64.3 192.168.64.4; do ssh-copy-id ubuntu@$ip; done
O ssh-copy-id también te pide que confirmes la clave de cada servidor en la primera conexión. Si usas otra forma de distribuir la clave, Ansible se detendrá con Host key verification failed, y es bueno que lo haga: es la protección contra un servidor falso. Para registrar las claves de una vez, sin desactivar la comprobación:
ssh-keyscan -H 192.168.64.2 192.168.64.3 192.168.64.4 >> ~/.ssh/known_hosts
Comprueba las claves si el servidor no es de laboratorio. Muchos tutoriales dicen que pongas host_key_checking = False no ansible.cfg; evite en producción. Y si coloca cualquier opción, cuidado con los comentarios: no ansible.cfg, el # solo vale al principio de la línea. En nuestra prueba, la línea host_key_checking = True # comentário se interpretó como False, porque Ansible tomó todo el texto como valor, no lo reconoció como verdadero y desactivó la comprobación sin avisar. Comentario en la misma línea solo con ;, o en una línea propia.
Ahora la prueba de comunicación:
ansible all -m ansible.builtin.ping
web01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
web02 | SUCCESS => { ... }
db01 | SUCCESS => { ... }
El módulo ping no es el ping de red (ICMP): confirma que Ansible entra por SSH y ejecuta Python en el otro lado, que es lo que importa. ¿Sin servidor a mano? Todo funciona contra la propia máquina con ansible localhost -m ansible.builtin.ping, que usa conexión local.
Comandos ad-hoc
Antes de escribir playbooks, se puede resolver mucho con comandos sueltos. -m elige el módulo, -a pasa los argumentos y --become ejecuta con sudo al otro lado:
ansible all -a "uptime -p" # comando em todos
ansible webservers -m ansible.builtin.apt \
-a "name=htop state=present update_cache=true" --become # instala pacote
ansible db01 -m ansible.builtin.setup -a "filter=ansible_distribution*" # fatos
ansible-doc ansible.builtin.apt # documentação do módulo
Ejecuta el comando del htop dos veces. En la primera, los hosts aparecen como CHANGED; en la segunda, como SUCCESS, porque el paquete ya estaba allí. Si tu usuario pide contraseña en el sudo, añade -K (--ask-become-pass) para que Ansible pregunte. Y prefiere los módulos específicos al shell: el apt sabe decir si cambió algo, y un comando suelto no lo sabe.
El primer playbook
Ahora un guion completo: instalar Nginx en los servidores web, publicar una página generada a partir de una plantilla con variable, mantener el servicio activo y recargarlo solo cuando la página cambie. Primero la variable del grupo, en group_vars/webservers.yml:
---
site_titulo: "Servidor gerenciado pelo Ansible"
La plantilla, en templates/index.html.j2, usa la sintaxis Jinja2. inventory_hostname e ansible_facts son rellenados por el propio Ansible:
<!doctype html>
<html lang="pt-br">
<head><meta charset="utf-8"><title>{{ site_titulo }}</title></head>
<body>
<h1>{{ site_titulo }}</h1>
<p>Host: {{ inventory_hostname }} ({{ ansible_facts["distribution"] }} {{ ansible_facts["distribution_version"] }})</p>
</body>
</html>
Y el playbook, site.yml:
---
- name: Configurar servidores web
hosts: webservers
become: true
tasks:
- name: Instalar o Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Publicar a página inicial
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: root
group: root
mode: "0644"
notify: Recarregar o Nginx
- name: Garantir o Nginx ativo e habilitado no boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Recarregar o Nginx
ansible.builtin.service:
name: nginx
state: reloaded
Los nombres de los módulos aparecen completos, como ansible.builtin.apt. Es el formato recomendado desde Ansible 2.10: deja claro de qué colección proviene cada módulo. El cache_valid_time evita ejecutar apt update de nuevo si se hizo en la última hora.
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml
TASK [Instalar o Nginx] ********************************************************
changed: [web01]
changed: [web02]
TASK [Publicar a página inicial] ***********************************************
changed: [web01]
changed: [web02]
TASK [Garantir o Nginx ativo e habilitado no boot] *****************************
changed: [web02]
changed: [web01]
RUNNING HANDLER [Recarregar o Nginx] *******************************************
changed: [web02]
changed: [web01]
PLAY RECAP *********************************************************************
web01 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 ...
web02 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 ...
Idempotencia: ejecútalo otra vez
Ejecute el mismo comando otra vez y observe el resumen:
web01 : ok=4 changed=0 unreachable=0 failed=0 ...
web02 : ok=4 changed=0 unreachable=0 failed=0 ...
changed=0. Esa es la idempotencia, el corazón de Ansible: no “ejecuta comandos”, garantiza estados. Nginx ya estaba instalado, la página ya era la correcta, el servicio ya estaba activo, y no se tocó nada. El handler tampoco se ejecutó, porque ninguna tarea cambió.
Simular antes de aplicar: --check e --diff
Cambie el título en group_vars/webservers.yml por “Servidor web de LinuxPro” y simule, solo en el web01:
ansible-playbook site.yml --check --diff --limit web01
TASK [Publicar a página inicial] ***********************************************
--- before: /var/www/html/index.html
+++ after: /root/.ansible/tmp/ansible-local-586tfwktpl6/tmp5owga2ot/index.html.j2
@@ -1,8 +1,8 @@
<!doctype html>
<html lang="pt-br">
-<head><meta charset="utf-8"><title>Servidor gerenciado pelo Ansible</title></head>
+<head><meta charset="utf-8"><title>Servidor web do LinuxPro</title></head>
<body>
- <h1>Servidor gerenciado pelo Ansible</h1>
+ <h1>Servidor web do LinuxPro</h1>
<p>Host: web01 (Ubuntu 24.04)</p>
</body>
</html>
changed: [web01]
O --check no altera nada, y el --diff muestra exactamente lo que cambiaría. Aplicándolo de verdad, solo la página y el handler aparecen como changed, y el Nginx se recarga una sola vez.
Dos límites del --check, vistos en las pruebas:
- En un servidor limpio, puede fallar a mitad de camino. En la simulación, el Nginx no se instala de verdad, así que la siguiente tarea, la del servicio, termina con
Could not find the requested service nginx. Es lo esperado: la simulación vale más para revisar cambios en servidores que ya están configurados. - El módulo
aptnecesita el paquetepython3-aptpara simular. Ubuntu Server ya lo trae; las imágenes mínimas, como las de contenedor, no, y el error dicepython3-apt must be installed to use check mode. En una ejecución normal, el propio módulo instala el paquete.
Convirtiendo el playbook en role
Cuando el playbook crece, las tareas se convierten en un role, que se puede usar en cualquier proyecto:
ansible-galaxy role init roles/nginx
El comando crea la estructura en roles/nginx/. Mueve las tareas a roles/nginx/tasks/main.yml, el handler a roles/nginx/handlers/main.yml y la plantilla a roles/nginx/templates/. Dentro del rol, el src de la plantilla pasa a ser solo index.html.j2, porque Ansible busca en la carpeta templates/ del propio rol. El site.yml queda corto:
---
- name: Configurar servidores web
hosts: webservers
become: true
roles:
- nginx
Ejecutando de nuevo, las tareas aparecen con el prefijo del rol (nginx : Instalar o Nginx) y el resultado sigue changed=0: cambió la organización, no el estado de los servidores.
Próximos pasos
Con esta base, los caminos naturales son:
- Continuación: en Usuarios en Linux con Ansible: claves SSH, sudo e historial en Git, el proyecto va a Git y empieza a crear, bloquear y eliminar cuentas, con inicio de sesión mediante clave SSH sin contraseña y acceso sudo. El código completo, probado en Ubuntu 24.04 y en Debian 13, está en el repositorio jniltinho/ansible-linux-users.
- Ir a un escenario real: la guía Automatizando servidores Linux con Ansible muestra firewall, secretos con
ansible-vaulty actualización en oleadas. - Explorar los módulos en la documentación oficial o con
ansible-doc -l. - Carrera: si la automatización se convierte en profesión, Ansible es una pieza central de la certificación LPI DevOps Tools Engineer.
Instala, haz ping, ejecuta el playbook dos veces y mira el changed=0: en ese momento queda claro por qué tanta gente dejó de configurar servidores a mano.