Ansible es una herramienta de automatización de código abierto que configura servidores a través de SSH. No usa agentes: en los servidores gestionados solo hace falta Python, que Ubuntu ya incluye. En este tutorial instalarás Ansible en un nodo de control con Ubuntu 24.04, le darás acceso SSH a tus servidores, los describirás en un inventario, ejecutarás comandos ad-hoc y escribirás un primer playbook que instala y arranca Nginx.
Requisitos previos
Para seguir esta guía necesitas:
- Un nodo de control: una máquina con Ubuntu 24.04 desde la que se ejecutará Ansible. Puede ser tu equipo de trabajo o un VPS pequeño de CubePath.
- Uno o más nodos gestionados: servidores con Ubuntu 24.04 que Ansible configurará. Esta guía usa dos,
web1(web1_server_ip) yweb2(web2_server_ip). - Un usuario no root con privilegios
sudoen cada nodo gestionado, llamadoyour_useren esta guía. - Acceso SSH (puerto 22) desde el nodo de control a todos los nodos gestionados.
Paso 1: Instalar Ansible en el nodo de control
Los repositorios de Ubuntu incluyen Ansible, pero con una versión antigua. El proyecto Ansible publica las versiones actuales en el PPA oficial ppa:ansible/ansible, que es compatible con Ubuntu 24.04.
En el nodo de control, añade el PPA e instala el paquete ansible:
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
El paquete ansible contiene ansible-core (el motor y los módulos integrados ansible.builtin) y un conjunto de colecciones como community.general y ansible.posix.
Comprueba la instalación:
ansible --version
ansible [core 2.18.6]
config file = None
configured module search path = ['/home/your_user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.12.3 (main, ...) [GCC 13.2.0]
jinja version = 3.1.2
Los números de versión serán distintos en tu caso. En los nodos gestionados no hay que instalar nada.
Paso 2: Configurar la autenticación con claves SSH
Ansible se conecta a cada servidor por SSH, así que el nodo de control necesita acceso con clave, sin contraseña, a todos los nodos gestionados.
Si todavía no tienes una clave en el nodo de control, créala:
ssh-keygen -t ed25519 -C "ansible-control"
Pulsa ENTER para aceptar la ruta por defecto (~/.ssh/id_ed25519). Es recomendable usar una frase de paso; si la pones, carga la clave en el agente antes de usar Ansible con eval "$(ssh-agent)" y ssh-add.
Copia la clave pública a cada nodo gestionado:
ssh-copy-id your_user@web1_server_ip
ssh-copy-id your_user@web2_server_ip
Prueba la conexión. Debe iniciar sesión e imprimir el nombre del servidor sin pedir contraseña:
ssh your_user@web1_server_ip hostname
web1
Esta primera conexión también guarda la clave de host de cada servidor en ~/.ssh/known_hosts. Ansible verifica las claves de host por defecto, y conviene mantenerlo así.
Paso 3: Crear el directorio del proyecto y el inventario
El inventario le dice a Ansible qué servidores existen, cómo llegar a ellos y cómo se agrupan. Guárdalo en un directorio de proyecto junto con la configuración y los playbooks, para que todo el proyecto pueda vivir en Git.
Crea el directorio del proyecto:
mkdir ~/ansible-first-steps
cd ~/ansible-first-steps
Crea el archivo de inventario:
nano inventory.ini
Añade tus servidores a un grupo llamado webservers:
[webservers]
web1 ansible_host=web1_server_ip
web2 ansible_host=web2_server_ip
[webservers:vars]
ansible_user=your_user
web1 y web2 son los nombres que Ansible usa en su salida, ansible_host es la dirección a la que se conecta y ansible_user fija el usuario SSH para todos los hosts del grupo. Además, cada host pertenece al grupo implícito all.
Muestra lo que Ansible ve en el inventario:
ansible-inventory -i inventory.ini --graph
@all:
|--@ungrouped:
|--@webservers:
| |--web1
| |--web2
Paso 4: Añadir un archivo ansible.cfg
En lugar de pasar -i inventory.ini en cada comando, guarda los valores por defecto en un archivo ansible.cfg. Ansible lee el ansible.cfg del directorio actual cuando ejecutas los comandos desde la carpeta del proyecto.
nano ansible.cfg
[defaults]
inventory = inventory.ini
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
inventoryapunta al archivo de inventario.interpreter_python = auto_silentdeja que Ansible descubra Python en cada servidor sin mostrar un aviso.pipeliningreduce el número de operaciones SSH por tarea, lo que acelera bastante las ejecuciones.
Confirma que Ansible usa el archivo:
ansible --version | grep "config file"
config file = /home/your_user/ansible-first-steps/ansible.cfg
Paso 5: Ejecutar comandos ad-hoc
Los comandos ad-hoc ejecutan un único módulo sobre un conjunto de hosts sin escribir un playbook. Son útiles para comprobaciones rápidas y tareas puntuales. La sintaxis es ansible <patrón> -m <módulo> -a "<argumentos>".
Comprueba la conectividad con el módulo ping. No envía paquetes ICMP: inicia sesión por SSH y verifica que Python funciona.
ansible all -m ping
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false,
"ping": "pong"
}
web2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false,
"ping": "pong"
}
Ejecuta un comando en todos los servidores web:
ansible webservers -m ansible.builtin.command -a "uptime"
web1 | CHANGED | rc=0 >>
10:14:02 up 3 days, 2:11, 1 user, load average: 0.00, 0.01, 0.00
web2 | CHANGED | rc=0 >>
10:14:02 up 3 days, 2:09, 1 user, load average: 0.02, 0.03, 0.00
Las tareas que necesitan root usan --become (-b). Añade -K (--ask-become-pass) para que Ansible te pida la contraseña de sudo una sola vez:
ansible webservers -b -K -m ansible.builtin.apt -a "update_cache=yes"
Recoge los facts, la información del sistema que Ansible obtiene de cada host, y fíltralos:
ansible web1 -m ansible.builtin.setup -a "filter=ansible_distribution*"
web1 | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "Ubuntu",
"ansible_distribution_file_parsed": true,
"ansible_distribution_file_path": "/etc/os-release",
"ansible_distribution_file_variety": "Debian",
"ansible_distribution_major_version": "24",
"ansible_distribution_release": "noble",
"ansible_distribution_version": "24.04"
},
"changed": false
}
Paso 6: Escribir tu primer playbook
Los comandos ad-hoc son imperativos: indicas qué ejecutar. Un playbook es declarativo: describes el estado que quieres y Ansible solo cambia lo que sea distinto. Los playbooks son archivos YAML que se pueden revisar, versionar y volver a ejecutar sin riesgo.
Crea un playbook que instala Nginx, se asegura de que está en marcha, abre HTTP en UFW y publica una página de inicio sencilla:
nano webserver.yml
---
- name: Configurar los servidores web
hosts: webservers
become: true
tasks:
- name: Instalar Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Arrancar Nginx y activarlo en el arranque
ansible.builtin.systemd_service:
name: nginx
state: started
enabled: true
- name: Permitir SSH en UFW
community.general.ufw:
rule: allow
name: OpenSSH
- name: Permitir HTTP en UFW
community.general.ufw:
rule: allow
port: "80"
proto: tcp
- name: Activar UFW
community.general.ufw:
state: enabled
- name: Publicar la página de inicio
ansible.builtin.copy:
dest: /var/www/html/index.html
content: "<h1>Desplegado por Ansible en {{ inventory_hostname }}</h1>\n"
owner: www-data
group: www-data
mode: "0644"
Algunos detalles importantes:
hosts: webserversapunta al grupo del inventario ybecome: trueejecuta todas las tareas con sudo.- Cada tarea llama a un módulo por su nombre completo (
ansible.builtin.apt), lo que evita ambigüedades entre colecciones. cache_valid_time: 3600solo actualiza la caché de apt si tiene más de una hora.- La regla de OpenSSH va antes de
state: enabled, así que activar el cortafuegos nunca te deja fuera. {{ inventory_hostname }}es una variable Jinja2 con el nombre del host en el inventario.
Comprueba la sintaxis antes de ejecutarlo:
ansible-playbook webserver.yml --syntax-check
playbook: webserver.yml
Paso 7: Ejecutar el playbook
Empieza con una ejecución de prueba. --check informa de lo que cambiaría sin cambiar nada, y --diff muestra las diferencias en los archivos:
ansible-playbook webserver.yml -K --check --diff
En modo check, algunas tareas posteriores pueden dar error porque las anteriores no se ejecutaron de verdad (por ejemplo, el servicio de Nginx todavía no existe). Es normal en la primera ejecución.
Ahora aplica el playbook:
ansible-playbook webserver.yml -K
Introduce tu contraseña de sudo en el aviso BECOME password:. La ejecución termina con un resumen por host:
PLAY RECAP *********************************************************************
web1 : ok=7 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ok cuenta las tareas que se ejecutaron correctamente (incluida la recogida de facts) y changed las que modificaron algo.
Verifica el resultado desde el nodo de control:
curl http://web1_server_ip
<h1>Desplegado por Ansible en web1</h1>
Paso 8: Comprobar la idempotencia
Ejecuta de nuevo el mismo playbook:
ansible-playbook webserver.yml -K
PLAY RECAP *********************************************************************
web1 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
changed=0 significa que los servidores ya coinciden con el estado descrito, así que Ansible no ha hecho nada. Esta propiedad, la idempotencia, es la que permite ejecutar los playbooks una y otra vez sin riesgo, por ejemplo de forma programada o después de añadir un servidor nuevo al inventario. También es la razón para preferir módulos específicos (apt, copy, systemd_service) en lugar de command o shell, que siempre informan de changed porque Ansible no sabe qué han hecho.
Solución de problemas
UNREACHABLE! ... Permission denied (publickey,password): la clave SSH no está instalada para el usuario que usa Ansible. Revisa ansible_user en el inventario y prueba con ssh your_user@web1_server_ip. Ejecuta Ansible con -vvv para ver el comando SSH exacto que lanza.
Missing sudo password: la tarea usa become y tu usuario necesita contraseña para sudo. Añade -K al comando.
Host key verification failed: la clave del servidor no está en ~/.ssh/known_hosts o ha cambiado (por ejemplo, tras reinstalar el servidor). Conéctate una vez con ssh para aceptarla, o elimina la entrada antigua con ssh-keygen -R web1_server_ip.
couldn't resolve module/action 'community.general.ufw': solo has instalado ansible-core. Instala la colección con ansible-galaxy collection install community.general, o instala el paquete completo ansible como en el paso 1.
Conclusión
Has instalado Ansible en un nodo de control, configurado el acceso con claves SSH, descrito tus servidores en un inventario, ejecutado comandos ad-hoc y aplicado un playbook idempotente que configura Nginx y el cortafuegos en varios servidores a la vez.
A partir de aquí, guarda el proyecto en Git, aprende a organizar configuraciones más grandes con plantillas y handlers en playbooks prácticos, y usa variables, bucles y condicionales para que el mismo playbook funcione en entornos distintos.
