Configurar un servidor manualmente funciona cuando tienes uno. Cuando tienes cinco, es tedioso. Cuando tienes veinte, es insostenible. Y lo peor: cada servidor configurado a mano es ligeramente diferente — alguien olvidó habilitar el firewall en uno, otro tiene una versión vieja de un paquete, un tercero tiene permisos diferentes. Esas diferencias invisibles son las que causan problemas en producción que nadie puede reproducir.
Ansible resuelve esto de raíz: defines la configuración deseada de tus servidores como código, la ejecutas y Ansible se encarga de que todos los servidores lleguen al mismo estado — sin importar cuántos sean.
Requisitos previos
Una máquina de control con Ubuntu 24.04 (puede ser tu laptop o un servidor de administración) y servidores objetivo con acceso SSH configurado con llaves.
Paso 1: Instalar Ansible
Ansible se instala solo en la máquina de control — no en los servidores objetivo:
sudo apt update
sudo apt install ansible -y
ansible --version
Verifica la conexión SSH a tus servidores:
ssh usuario@10.0.1.10 "hostname"
ssh usuario@10.0.1.11 "hostname"
Si puedes conectarte sin contraseña (con llaves SSH), Ansible también puede.
Paso 2: Inventario de servidores
El inventario le dice a Ansible qué servidores administrar. Crea un directorio de proyecto:
mkdir -p ~/ansible && cd ~/ansible
# inventory.ini
[webservers]
web1 ansible_host=10.0.1.10
web2 ansible_host=10.0.1.11
[dbservers]
db1 ansible_host=10.0.1.20
[monitoring]
monitor1 ansible_host=10.0.1.30
[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
Verifica que Ansible puede conectarse:
ansible all -i inventory.ini -m ping
Deberías ver SUCCESS para cada servidor con un "pong" como respuesta.
Paso 3: Tu primer playbook
Un playbook es un archivo YAML que describe el estado deseado de tus servidores. Empecemos con uno básico que actualiza paquetes e instala herramientas esenciales:
# playbooks/base.yml
---
- name: Configuración base de servidores
hosts: all
become: true # Ejecutar como root (sudo)
tasks:
- name: Actualizar caché de paquetes
apt:
update_cache: true
cache_valid_time: 3600
- name: Actualizar todos los paquetes
apt:
upgrade: safe
register: upgrade_result
- name: Instalar paquetes esenciales
apt:
name:
- curl
- wget
- git
- htop
- vim
- unzip
- net-tools
- chrony
- jq
state: present
- name: Configurar timezone
timezone:
name: America/Mexico_City
- name: Habilitar sincronización NTP
systemd:
name: chrony
state: started
enabled: true
- name: Mostrar resumen de actualización
debug:
msg: "Paquetes actualizados: {{ upgrade_result.stdout_lines | default(['Ninguno']) }}"
Ejecútalo:
ansible-playbook -i inventory.ini playbooks/base.yml
Ansible muestra cada tarea con su resultado: ok (ya estaba como se pidió), changed (se aplicó un cambio) o failed. Eso es idempotencia — si lo ejecutas otra vez, todo saldrá ok porque ya no hay cambios pendientes.
Paso 4: Playbook de hardening
Ahora automaticemos los pasos de hardening de servidores que hicimos manualmente:
# playbooks/hardening.yml
---
- name: Hardening de servidores Linux
hosts: all
become: true
vars:
ssh_port: 2222
allowed_users: "deploy admin"
fail2ban_maxretry: 3
fail2ban_bantime: 3600
tasks:
# --- SSH ---
- name: Configurar SSH seguro
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: '^#?Port', line: 'Port {{ ssh_port }}' }
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
- { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
- { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
- { regexp: '^#?AllowUsers', line: 'AllowUsers {{ allowed_users }}' }
notify: Reiniciar SSH
# --- Firewall ---
- name: Instalar UFW
apt:
name: ufw
state: present
- name: Configurar política por defecto
ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
- name: Permitir SSH
ufw:
rule: limit
port: "{{ ssh_port }}"
proto: tcp
comment: "SSH con rate limit"
- name: Habilitar UFW
ufw:
state: enabled
# --- fail2ban ---
- name: Instalar fail2ban
apt:
name: fail2ban
state: present
- name: Configurar fail2ban
template:
src: templates/jail.local.j2
dest: /etc/fail2ban/jail.local
mode: '0644'
notify: Reiniciar fail2ban
# --- Actualizaciones automáticas ---
- name: Instalar unattended-upgrades
apt:
name: unattended-upgrades
state: present
- name: Habilitar actualizaciones automáticas
copy:
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
dest: /etc/apt/apt.conf.d/20auto-upgrades
mode: '0644'
# --- Kernel hardening ---
- name: Aplicar parámetros de kernel seguros
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_set: true
reload: true
loop:
- { key: 'net.ipv4.conf.all.accept_redirects', value: '0' }
- { key: 'net.ipv4.conf.all.send_redirects', value: '0' }
- { key: 'net.ipv4.tcp_syncookies', value: '1' }
- { key: 'net.ipv4.icmp_echo_ignore_broadcasts', value: '1' }
- { key: 'kernel.dmesg_restrict', value: '1' }
- { key: 'kernel.randomize_va_space', value: '2' }
# --- Auditoría ---
- name: Instalar auditd
apt:
name: auditd
state: present
- name: Habilitar auditd
systemd:
name: auditd
state: started
enabled: true
handlers:
- name: Reiniciar SSH
systemd:
name: sshd
state: restarted
- name: Reiniciar fail2ban
systemd:
name: fail2ban
state: restarted
Crea el template de fail2ban:
mkdir -p templates
# templates/jail.local.j2
[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = 600
maxretry = {{ fail2ban_maxretry }}
banaction = ufw
[sshd]
enabled = true
port = {{ ssh_port }}
filter = sshd
logpath = /var/log/auth.log
maxretry = {{ fail2ban_maxretry }}
Handlers
Los handlers solo se ejecutan si la tarea que los notifica hizo un cambio. Si SSH ya estaba configurado correctamente, el handler Reiniciar SSH no se ejecuta — evitando reinicios innecesarios.
Paso 5: Roles para organización
Cuando tus playbooks crecen, organízalos en roles — módulos reutilizables para cada función:
~/ansible/
├── inventory.ini
├── playbooks/
│ └── site.yml # Playbook principal que llama roles
├── roles/
│ ├── base/
│ │ └── tasks/
│ │ └── main.yml
│ ├── hardening/
│ │ ├── tasks/
│ │ │ └── main.yml
│ │ ├── templates/
│ │ │ └── jail.local.j2
│ │ └── defaults/
│ │ └── main.yml # Variables por defecto del rol
│ ├── docker/
│ │ └── tasks/
│ │ └── main.yml
│ └── monitoring/
│ └── tasks/
│ └── main.yml
El playbook principal aplica los roles según el grupo del inventario:
# playbooks/site.yml
---
- name: Configuración base para todos los servidores
hosts: all
become: true
roles:
- base
- hardening
- name: Servidores web con Docker
hosts: webservers
become: true
roles:
- docker
- name: Servidores de monitoreo
hosts: monitoring
become: true
roles:
- monitoring
# Ejecutar todo
ansible-playbook -i inventory.ini playbooks/site.yml
# Ejecutar solo en webservers
ansible-playbook -i inventory.ini playbooks/site.yml --limit webservers
# Ejecutar solo el rol de hardening
ansible-playbook -i inventory.ini playbooks/site.yml --tags hardening
Paso 6: Playbook de Docker
Un ejemplo práctico — instalar Docker en todos los webservers:
# roles/docker/tasks/main.yml
---
- name: Instalar dependencias
apt:
name:
- ca-certificates
- curl
- gnupg
state: present
- name: Agregar GPG key de Docker
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Agregar repositorio de Docker
apt_repository:
repo: "deb https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Instalar Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
- name: Agregar usuario al grupo docker
user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: Habilitar Docker
systemd:
name: docker
state: started
enabled: true
- name: Verificar instalación
command: docker --version
register: docker_version
changed_when: false
- name: Mostrar versión
debug:
msg: "Docker instalado: {{ docker_version.stdout }}"
Paso 7: Playbook de monitoreo
Instalar node_exporter en todos los servidores:
# roles/monitoring/tasks/main.yml
---
- name: Crear usuario node_exporter
user:
name: node_exporter
system: true
shell: /bin/false
create_home: false
- name: Descargar node_exporter
get_url:
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
dest: /tmp/node_exporter.tar.gz
- name: Extraer binario
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp/
remote_src: true
- name: Instalar binario
copy:
src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
dest: /usr/local/bin/node_exporter
mode: '0755'
remote_src: true
- name: Crear servicio systemd
template:
src: node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
notify: Reiniciar node_exporter
- name: Habilitar y arrancar
systemd:
name: node_exporter
state: started
enabled: true
daemon_reload: true
- name: Abrir puerto en firewall
ufw:
rule: allow
from_ip: "{{ prometheus_server_ip }}"
port: '9100'
proto: tcp
comment: "Prometheus scraping"
Comandos ad-hoc
No todo necesita un playbook. Ansible puede ejecutar comandos rápidos en todos tus servidores:
# Ver espacio en disco de todos los servidores
ansible all -i inventory.ini -a "df -h /"
# Reiniciar un servicio en todos los webservers
ansible webservers -i inventory.ini -b -m systemd -a "name=nginx state=restarted"
# Ver uptime de todos
ansible all -i inventory.ini -a "uptime"
# Copiar un archivo a todos los servidores
ansible all -i inventory.ini -b -m copy -a "src=./mi-config.conf dest=/etc/mi-config.conf mode=0644"
# Verificar qué paquetes se pueden actualizar
ansible all -i inventory.ini -b -a "apt list --upgradable"
Buenas prácticas
Usa --check antes de ejecutar — el modo dry-run muestra qué cambiaría sin hacer nada:
ansible-playbook -i inventory.ini playbooks/hardening.yml --check --diff
Versiona tus playbooks en git — tus configuraciones de infraestructura son código y merecen control de versiones, code review y historial.
Usa variables por entorno — diferentes configuraciones para desarrollo, staging y producción:
ansible-playbook -i inventory_produccion.ini playbooks/site.yml
ansible-playbook -i inventory_staging.ini playbooks/site.yml
Cifra secretos con Ansible Vault — contraseñas, llaves y tokens cifrados dentro del repositorio:
ansible-vault encrypt vars/secretos.yml
ansible-playbook -i inventory.ini playbooks/site.yml --ask-vault-pass
Prueba en staging primero — siempre ejecuta playbooks nuevos en un ambiente de pruebas antes de producción.
Siguientes pasos
Con Ansible dominado, puedes escalar tu automatización:
- AWX / Semaphore — interfaz web para ejecutar playbooks con historial, permisos y programación
- Ansible + Proxmox — aprovisionar VMs automáticamente con el módulo
community.general.proxmox_kvm - Ansible + CI/CD — ejecutar playbooks desde pipelines de GitLab
- Molecule — testing automatizado de roles de Ansible antes de aplicarlos en producción
- Consultoría de infraestructura — diseñamos tu estrategia de automatización con Ansible adaptada a tu operación
Infraestructura como código
¿Quieres que tus servidores se configuren solos?
Implementamos Ansible para que cada servidor nuevo de tu infraestructura nazca configurado, seguro y monitoreado — en minutos, no en horas.



