Ansible¶
Ansible es una herramienta clave para la gestión de configuración y automatización de infraestructuras, que permite aprovisionar servidores, desplegar servicios y mantener sistemas Linux de forma reproducible y sin necesidad de instalar agentes en los nodos gestionados. En el contexto del Proyecto Intermodular (PI), Ansible permite pasar de un despliegue manual a un despliegue automatizado, idempotente y versionable en Git, asegurando que cualquier integrante del equipo pueda reproducir el entorno completo en minutos.
Propuesta didáctica¶
Esta herramienta se enmarca dentro del módulo de Proyecto Intermodular del CFGS ASIR, y contribuye a los siguientes Resultados de Aprendizaje (RA) definidos en la programación:
RA3. Desarrolla y valida la solución (iteraciones, pruebas y criterios de aceptación).
RA4. Documenta, versiona y despliega la solución y sus evidencias.
Criterios de evaluación (relacionados)¶
- CE-RA3b: Se han configurado entornos de prueba utilizando herramientas de automatización.
- CE-RA4a: Se ha documentado el procedimiento de instalación y despliegue.
- CE-RA4b: Se ha utilizado un sistema de control de versiones para gestionar la configuración como código.
Relación con otros módulos del ciclo ASIR¶
Ansible es una herramienta transversal especialmente útil para:
- ASO (Administración de Sistemas Operativos): automatización de tareas administrativas, instalación de paquetes, gestión de usuarios y servicios.
- Servicios de Red: configuración automática de servidores DNS (Bind9), DHCP, proxy y firewall.
- SAD (Seguridad y Alta Disponibilidad): aplicación de políticas de hardening, copia de claves SSH, gestión centralizada de configuraciones de seguridad.
- IAW (Implantación de Aplicaciones Web): despliegue de stacks LAMP/LEMP, WordPress, Laravel y aplicaciones PHP/MySQL.
- ASGBD: instalación y configuración automatizada de motores de base de datos (MySQL, MariaDB, PostgreSQL).
Contenidos¶
Bloque 1 — Introducción a Ansible (Sesión 1)
- ¿Qué es Ansible? Diferencias frente a scripts y otras herramientas (Puppet, Chef, Salt).
- Arquitectura agentless basada en SSH.
- Instalación en GNU/Linux (Ubuntu/Debian) y macOS.
- Primer comando ad-hoc:
ansible -m ping all.
Bloque 2 — Inventarios y comandos ad-hoc (Sesión 2)
- Inventarios estáticos (
.iniy.yml). - Inventarios dinámicos para AWS (
amazon.aws.aws_ec2). - Comandos ad-hoc para administración rápida (
ansible -m apt,-m service,-m copy). - Configuración SSH con claves públicas.
Bloque 3 — Playbooks YAML (Sesión 3)
- Estructura de un playbook: plays, tasks, modules.
- Idempotencia y modo
--check. - Variables (
vars,group_vars,host_vars). - Bucles (
loop) y condicionales (when).
Bloque 4 — Roles, plantillas y secretos (Sesión 4)
- Estructura estándar de un rol.
- Plantillas Jinja2 (
.j2). - Handlers y notificación de cambios.
- Cifrado de secretos con
ansible-vault.
Bloque 5 — Integración con el Proyecto Intermodular
- Aprovisionamiento de servidores Linux para los retos.
- Integración con Terraform (línea AWS).
- Automatización del stack del proyecto Docker.
- Documentación técnica del despliegue.
Actividades iniciales
- Comprueba la versión instalada de Ansible con
ansible --version. - Crea un inventario
hosts.iniconlocalhosty ejecutaansible localhost -m ping -c local. - Lanza un comando ad-hoc para listar paquetes instalados (
ansible all -m package_facts). - Crea tu primer playbook que actualice los paquetes del sistema.
- Genera un par de claves SSH y prepara un host gestionado al que conectarte sin contraseña.
Programación de Aula¶
| Sesión | Contenidos | Actividades | Criterio trabajado |
|---|---|---|---|
| 1 | Introducción a Ansible, arquitectura agentless, instalación | PR104. Primer playbook | CE-RA3b |
| 2 | Inventarios, comandos ad-hoc, módulos básicos | PR105. Aprovisionamiento de servidor Linux | CE-RA3b, CE-RA4a |
| 3 | Playbooks, variables, plantillas Jinja2 | PR106. Despliegue del stack del proyecto | CE-RA3b, CE-RA4a |
| 4 | Roles, handlers, Ansible Vault | PR107. Despliegue Terraform + Ansible (AWS) | CE-RA3b, CE-RA4a, CE-RA4b |
Definición¶
Ansible es una herramienta de software libre (licencia GPLv3) creada por Michael DeHaan en 2012 y adquirida por Red Hat en 2015. Su propósito es automatizar la configuración de sistemas, el despliegue de aplicaciones y la orquestación de tareas sobre uno o miles de nodos a la vez.
- Está escrita en Python y utiliza YAML como lenguaje de definición de tareas, lo que la hace muy legible.
- Su arquitectura es agentless: no requiere instalar ningún demonio o agente en los nodos gestionados; se comunica por SSH (Linux/Unix) o WinRM (Windows).
- Es declarativa e idempotente: describes el estado deseado y Ansible aplica únicamente los cambios necesarios para alcanzarlo.
- Es multipropósito: aprovisionamiento, gestión de configuración, despliegue continuo, orquestación, seguridad y cumplimiento.
Nota
Ansible compite directamente con herramientas como Puppet, Chef y SaltStack. Su principal ventaja diferencial es no necesitar agentes y usar YAML, dos características que reducen la barrera de entrada y han hecho que sea la herramienta de gestión de configuración más usada del mundo según los últimos State of DevOps Reports.
¿Por qué Ansible y no scripts Bash?¶
Cuando un administrador de sistemas necesita configurar varias máquinas, su primera opción suele ser escribir un script en Bash. Esta solución funciona para 1 o 2 nodos, pero rápidamente se vuelve insostenible:
| Problema con scripts Bash | Cómo lo resuelve Ansible |
|---|---|
| Repetir el script no es seguro (puede duplicar usuarios, sobrescribir ficheros, fallar a la mitad) | Idempotencia nativa: cada módulo comprueba el estado antes de actuar |
| Difícil de leer y mantener | YAML legible, estructurado en plays y tasks |
| Falta gestión de errores y de variables | Variables jerárquicas, register, failed_when, block/rescue |
| Hay que copiar el script a cada nodo | Ansible se ejecuta en el controlador y empuja los cambios por SSH |
| No hay manera de probar antes de aplicar | --check y --diff simulan los cambios sin tocar nada |
| El script se vuelve un monstruo de 500 líneas | Roles reutilizables, organizados en carpetas estándar |
Tip
No se trata de "no usar Bash nunca". Ansible te permite ejecutar scripts Bash como una task cuando es necesario (módulo ansible.builtin.shell), pero esto se reserva para casos puntuales donde no existe un módulo específico.
Arquitectura agentless¶
Ansible distingue dos tipos de máquinas:
- Nodo controlador (control node): la máquina donde está instalado Ansible. Suele ser tu portátil o un servidor bastión. Es la única que necesita tener Ansible y Python.
- Nodos gestionados (managed nodes): los servidores sobre los que Ansible aplica los cambios. No necesitan instalar Ansible, solo:
- Python 3 (incluido por defecto en casi todas las distribuciones modernas).
- Conectividad SSH desde el controlador.
- Un usuario con permisos (típicamente con
sudosin contraseña o con--ask-become-pass).
flowchart LR
Dev[Controlador<br/>portátil del alumno] -- SSH --> N1[node1<br/>web]
Dev -- SSH --> N2[node2<br/>db]
Dev -- SSH --> N3[node3<br/>dns]
Dev --- Inv[inventory.ini]
Dev --- PB[playbook.yml]
Dev --- Roles[roles/]
Summary
Controlador → SSH → Nodos gestionados → Estado deseado aplicado.
Ventajas del modelo agentless¶
- Cero sobrecarga en los nodos: no hay procesos en segundo plano consumiendo CPU/RAM.
- Cero superficie de ataque adicional: no abres puertos extra ni instalas software con permisos elevados.
- Compatible con cualquier sistema que soporte SSH (servidores físicos, máquinas virtuales, EC2, contenedores...).
- Sencillez operativa: si la SSH funciona, Ansible funciona.
Advertencia
Aunque Ansible es agentless, no es stateless. El control sobre qué cambia y cuándo lo tiene el operador que ejecuta ansible-playbook. En entornos profesionales se complementa con AWX / Ansible Automation Platform o con pipelines CI/CD (GitHub Actions, GitLab CI) para gobernar la aplicación de cambios.
Características principales¶
En resumen, Ansible:
- Es declarativo e idempotente: describes el estado deseado del sistema; Ansible aplica solo lo necesario.
- Es agentless: solo necesitas SSH y Python en los nodos gestionados.
- Está basado en YAML, un formato sencillo y legible.
- Es modular: cientos de módulos oficiales (
ansible.builtin) y miles más en colecciones de la comunidad (Ansible Galaxy). - Permite gestionar miles de nodos en paralelo ajustando el parámetro
forks. - Es open source y multiplataforma (Linux, BSD, macOS, Windows mediante WinRM, dispositivos de red Cisco/Arista/Juniper, cloud providers...).
- Su lema es "Simple, Agentless, Powerful".
Características y definición técnica¶
Ansible se puede definir como una plataforma de automatización que combina:
- Un motor (
/usr/bin/ansible,/usr/bin/ansible-playbook) escrito en Python. - Una biblioteca de módulos que envuelven las APIs y comandos del sistema.
- Un modelo de inventario que describe los hosts gestionados.
- Un lenguaje YAML para describir las tareas (playbooks).
- Plugins que extienden el motor (lookups, callbacks, conexiones, filtros...).
Nota
El proyecto Ansible se publica en dos formatos:
ansible-core: el motor mínimo + los módulos esenciales (ansible.builtin).ansible(community package):ansible-core+ un conjunto curado de colecciones comunitarias (AWS, Docker, Kubernetes, MySQL, Postgres, NetBox, Windows...).
En el aula instalaremos el paquete completo (ansible) porque incluye las colecciones que necesitamos para los retos del Tema 6.
Arquitectura Ansible¶
Los componentes principales de Ansible son:
- Control Node (controlador): máquina con Ansible instalado desde la que se ejecutan los comandos.
- Managed Nodes (nodos gestionados): los hosts sobre los que Ansible aplica los cambios; no requieren agente.
- Inventory (inventario): fichero (estático) o plugin (dinámico) que lista los nodos y sus grupos.
- Modules (módulos): unidades de código que realizan acciones concretas (
apt,copy,service,mysql_user...). Ansible los copia al nodo, los ejecuta y los elimina. - Tasks (tareas): invocaciones a módulos dentro de un playbook.
- Playbooks: ficheros YAML que describen el orden y los nodos sobre los que ejecutar las tasks.
- Roles: estructura de carpetas estándar que empaqueta tasks, plantillas, ficheros, variables y handlers para ser reutilizada.
- Plugins: extienden la funcionalidad del controlador (lookups, filtros, callbacks, conexión, inventory...).
- Collections: unidad de distribución que agrupa módulos, roles, plugins y documentación (ej.
community.general,amazon.aws). - Galaxy: repositorio público de roles y colecciones de la comunidad (galaxy.ansible.com).
flowchart TB
subgraph Controlador
CLI[ansible / ansible-playbook]
Inv[Inventory]
PB[Playbooks]
Roles[Roles + Collections]
Cfg[ansible.cfg]
end
subgraph Nodos
N1[node1 - Python 3]
N2[node2 - Python 3]
N3[node3 - Python 3]
end
CLI -- SSH --> N1
CLI -- SSH --> N2
CLI -- SSH --> N3
Inv --> CLI
PB --> CLI
Roles --> CLI
Cfg --> CLI
Nota
- Módulos y plugins son extensibles. Puedes escribir los tuyos en Python si necesitas algo muy específico.
- Colecciones son la forma moderna de empaquetar y distribuir contenido en Ansible (desde la versión 2.10).
Comparativa con otras herramientas de gestión de configuración¶
| Aspecto | Ansible | Puppet | Chef | SaltStack |
|---|---|---|---|---|
| Lenguaje | YAML | DSL propio (Ruby-like) | Ruby | YAML + Python |
| Arquitectura | Agentless (SSH) | Cliente-servidor (agente) | Cliente-servidor (agente) | Maestro-minion (agente) |
| Modelo | Imperativo + declarativo | Declarativo | Declarativo (con receta) | Declarativo |
| Curva de aprendizaje | Baja | Media-alta | Alta | Media |
| Casos de uso típicos | DevOps, despliegue, ad-hoc, IaC complementaria | Gestión a gran escala estable | Aplicaciones complejas, multinube | Gestión reactiva y orquestación rápida |
| Empresa detrás | Red Hat (IBM) | Perforce | Progress | VMware |
Summary
En el aula y en la mayoría de proyectos profesionales se usa Ansible porque su barrera de entrada es la más baja, no necesita servidor central y se integra perfectamente con Git, Terraform y CI/CD.
Instalación¶
Para instalar Ansible lo recomendable es seguir la documentación oficial. Se aconseja partir de una máquina Ubuntu Server 22.04 (o superior) o Debian 12 que actuará como controlador.
- Ansible en Linux (Ubuntu/Debian):
- Ansible en macOS:
- Ansible en otras distribuciones:
Advertencia
- Ansible no es compatible con Windows como controlador de forma nativa: en Windows hay que usar WSL2 (Ubuntu) para tener un controlador funcional.
- Sí puede gestionar nodos Windows desde un controlador Linux, mediante WinRM.
- La versión recomendada es Python 3.10 o superior en el controlador.
Ejemplo de instalación en Ubuntu Server¶
A continuación se muestra la instalación aconsejada para Ubuntu Server utilizando el repositorio oficial (PPA):
- Actualización del sistema e instalación de dependencias:
sudo apt update
sudo apt install -y software-properties-common
- Añadir el PPA oficial de Ansible:
sudo add-apt-repository --yes --update ppa:ansible/ansible
- Instalación de Ansible:
sudo apt install -y ansible
- Verificación de la instalación:
ansible --version
La salida debe mostrar la versión, la ruta del binario, la versión de Python, etc. Algo similar a:
ansible [core 2.16.x]
config file = None
python version = 3.10.x ...
jinja version = 3.x
- Comprobación contra
localhostcon el móduloping:
ansible localhost -m ping -c local
Resultado esperado:
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
Tip
También puedes instalar Ansible con pipx (recomendado por la documentación oficial) para tenerlo aislado del sistema:
sudo apt install -y pipx
pipx install --include-deps ansible
pipx ensurepath
Instalación de colecciones recomendadas para el PI¶
Para los retos del Proyecto Intermodular vas a necesitar varias colecciones de la comunidad. Se instalan así:
ansible-galaxy collection install community.general community.docker community.mysql amazon.aws ansible.posix
Lista de qué aporta cada colección:
| Colección | Módulos relevantes para el PI |
|---|---|
ansible.builtin |
apt, copy, template, service, user, file, lineinfile, cron, command, shell |
community.general |
timezone, ufw, htpasswd, archive, git_config |
community.docker |
docker_container, docker_compose_v2, docker_image, docker_network |
community.mysql |
mysql_user, mysql_db, mysql_query |
amazon.aws |
ec2_instance, aws_ec2 (inventory plugin), s3_object |
ansible.posix |
authorized_key, firewalld, sysctl, mount |
Configuración SSH (clave pública)¶
Ansible se conecta a los nodos gestionados por SSH. Para evitar tener que introducir la contraseña en cada ejecución, se trabaja siempre con claves públicas.
- Generar un par de claves en el controlador (si no las tienes ya):
ssh-keygen -t ed25519 -C "asir-pi-controller" -f ~/.ssh/asir_pi
- Copiar la clave pública al nodo gestionado:
ssh-copy-id -i ~/.ssh/asir_pi.pub ubuntu@192.168.1.50
- Probar conexión sin contraseña:
ssh -i ~/.ssh/asir_pi ubuntu@192.168.1.50
- Probar con Ansible:
ansible -i 192.168.1.50, -u ubuntu --private-key ~/.ssh/asir_pi -m ping all
Nota
Fíjate en la coma , después de la IP. Es la forma de Ansible de aceptar un inventario en línea sin necesidad de fichero. Útil para pruebas rápidas.
Configuración del proyecto: ansible.cfg¶
Cada proyecto Ansible suele tener un fichero ansible.cfg en su raíz que sobreescribe la configuración por defecto. Un ejemplo minimalista pero profesional:
[defaults]
inventory = inventory/hosts.ini
roles_path = roles
collections_path = collections
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
forks = 10
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
| Parámetro | Significado |
|---|---|
inventory |
Ruta del inventario por defecto (sin tener que pasar -i en cada comando) |
roles_path |
Dónde busca Ansible los roles del proyecto |
host_key_checking |
A False evita la pregunta "Are you sure you want to continue connecting?" la primera vez |
stdout_callback = yaml |
Salida más legible: cada task muestra su resultado en YAML |
forks |
Cuántos hosts gestiona en paralelo (10 es razonable en el aula) |
pipelining |
Acelera notablemente la ejecución al reducir el número de conexiones SSH por tarea |
Advertencia
host_key_checking = False simplifica el aula pero no se recomienda en producción. En entornos reales hay que mantener True y gestionar correctamente el fichero known_hosts.
Principales comandos¶
A continuación se muestran los comandos de línea más utilizados:
| Comando | Acción | Comando | Acción |
|---|---|---|---|
ansible --version |
Muestra versión y rutas de configuración | ansible-playbook |
Ejecuta un playbook YAML |
ansible |
Ejecuta un comando ad-hoc (un solo módulo) | ansible-galaxy collection install |
Instala una colección |
ansible -m ping all |
Comprueba conectividad SSH/Python en todos los hosts | ansible-galaxy role install |
Instala un rol de Galaxy |
ansible-inventory --graph |
Muestra el árbol del inventario | ansible-vault encrypt |
Cifra un fichero de secretos |
ansible-inventory --list |
Muestra el inventario en JSON | ansible-vault edit |
Edita un fichero cifrado |
ansible-config dump |
Muestra la configuración efectiva | ansible-lint playbook.yml |
Comprueba la calidad del playbook |
Inventarios¶
El inventario es el fichero (o plugin) que indica a Ansible qué hosts gestiona y cómo se agrupan.
Inventario estático en formato .ini¶
# inventory/hosts.ini
[webservers]
web01 ansible_host=192.168.1.50
web02 ansible_host=192.168.1.51
[dbservers]
db01 ansible_host=192.168.1.60
[dns]
ns01 ansible_host=192.168.1.70
[all:vars]
ansible_user = ubuntu
ansible_ssh_private_key_file = ~/.ssh/asir_pi
[produccion:children]
webservers
dbservers
dns
Inventario estático en formato .yml¶
# inventory/hosts.yml
all:
children:
webservers:
hosts:
web01:
ansible_host: 192.168.1.50
web02:
ansible_host: 192.168.1.51
dbservers:
hosts:
db01:
ansible_host: 192.168.1.60
dns:
hosts:
ns01:
ansible_host: 192.168.1.70
vars:
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/asir_pi
Visualización del inventario¶
ansible-inventory --graph
ansible-inventory --list
ansible webservers --list-hosts
Inventario dinámico (AWS EC2)¶
Para entornos cloud, mantener un inventario estático es inviable porque las IPs cambian a cada terraform apply. Ansible incluye un plugin de inventario dinámico para EC2:
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
tag:Project: pi-asir
keyed_groups:
- key: tags.Role
prefix: role
hostnames:
- tag:Name
compose:
ansible_host: public_ip_address | default(private_ip_address)
ansible_user: "'ubuntu'"
Uso:
ansible-inventory -i inventory/aws_ec2.yml --graph
Esto descubre automáticamente todas las EC2 con la tag Project=pi-asir, las agrupa por Role (role_web, role_db, etc.) y compone su ansible_host con la IP pública o privada.
Tip
El inventario dinámico es clave cuando integras Terraform + Ansible (Tema 6, reto RG602), porque elimina por completo la necesidad de mantener IPs a mano.
Comandos ad-hoc¶
Antes de escribir playbooks, Ansible permite ejecutar un solo módulo sobre un grupo de hosts con ansible <pattern> -m <módulo> -a "<argumentos>". Útil para:
- Probar conectividad.
- Diagnósticos rápidos.
- Operaciones puntuales (parchear urgente, reiniciar un servicio).
Ejemplos típicos¶
# Probar conectividad SSH + Python en todos los hosts
ansible all -m ping
# Listar el espacio en disco de los webservers
ansible webservers -m shell -a "df -h /"
# Instalar un paquete en los dbservers (necesita sudo, de ahí --become)
ansible dbservers -m apt -a "name=mysql-client state=present" --become
# Reiniciar nginx en todos los webservers
ansible webservers -m service -a "name=nginx state=restarted" --become
# Copiar un fichero del controlador a todos los nodos
ansible all -m copy -a "src=motd.txt dest=/etc/motd owner=root group=root mode=0644" --become
# Comprobar el uptime
ansible all -m command -a "uptime"
Nota
Los módulos command y shell no son idempotentes por defecto (Ansible no sabe qué hacen). Para tareas serias prefiere módulos específicos (apt, service, copy, lineinfile, etc.).
Playbooks¶
Un playbook es un fichero YAML que define una secuencia de plays. Cada play aplica un conjunto de tasks a un grupo de hosts.
Estructura básica¶
---
- name: Configuración base de los servidores web
hosts: webservers
become: true
vars:
paquetes_base:
- curl
- git
- htop
- vim
tasks:
- name: Actualizar caché de paquetes
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Instalar paquetes base
ansible.builtin.apt:
name: "{{ paquetes_base }}"
state: present
- name: Asegurar que el servicio SSH está activo
ansible.builtin.service:
name: ssh
state: started
enabled: true
Ejecución:
ansible-playbook -i inventory/hosts.ini site.yml
ansible-playbook -i inventory/hosts.ini site.yml --check --diff # simulación
ansible-playbook -i inventory/hosts.ini site.yml --limit web01 # un solo host
Elementos clave de un play¶
| Elemento | Qué define |
|---|---|
name |
Nombre legible del play o de la task |
hosts |
A qué grupo o host se aplica |
become |
Si se eleva privilegios (sudo) |
vars |
Variables locales del play |
tasks |
Lista de tareas (cada una invoca un módulo) |
handlers |
Tareas que se ejecutan solo si las notifica otra (reinicios, recargas) |
roles |
Lista de roles a aplicar (alternativa a tasks) |
Idempotencia y modo --check¶
Una task es idempotente si aplicarla N veces produce el mismo resultado. Para validarlo, ejecuta el playbook dos veces seguidas:
ansible-playbook site.yml # 1.ª ejecución: cambios reales
ansible-playbook site.yml # 2.ª ejecución: changed=0
Si en la 2.ª ejecución aparece changed > 0, tu playbook no es idempotente y debe revisarse.
Advertencia
--check simula sin aplicar cambios y --diff muestra exactamente qué líneas cambiarían en los ficheros. Úsalos siempre antes de un apply real:
ansible-playbook site.yml --check --diff
Bucles y condicionales¶
- name: Crear varios usuarios del equipo
ansible.builtin.user:
name: "{{ item.nombre }}"
groups: "{{ item.grupos | default('users') }}"
shell: /bin/bash
state: present
loop:
- { nombre: "alumno1", grupos: "sudo" }
- { nombre: "alumno2", grupos: "sudo" }
- { nombre: "alumno3" }
- name: Instalar fail2ban solo en Debian/Ubuntu
ansible.builtin.apt:
name: fail2ban
state: present
when: ansible_os_family == "Debian"
register y debug¶
- name: Obtener uptime
ansible.builtin.command: uptime
register: salida_uptime
changed_when: false
- name: Mostrar uptime
ansible.builtin.debug:
var: salida_uptime.stdout
Variables¶
Ansible tiene una jerarquía de variables muy potente. De menos a más prioridad (resumen):
defaults/main.yml(dentro de un rol)inventory/group_vars/all.ymlinventory/group_vars/<grupo>.ymlinventory/host_vars/<host>.ymlvars/dentro de un rolvars:del play--extra-varsen línea de comandos
Ejemplos¶
inventory/group_vars/all.yml
proyecto: "intermodular-asir"
zona_horaria: "Europe/Madrid"
puerto_web: 80
inventory/group_vars/webservers.yml
nginx_conf_path: /etc/nginx/conf.d/default.conf
dominio: web.proyecto.local
inventory/host_vars/db01.yml
mysql_bind_address: "{{ ansible_default_ipv4.address }}"
mysql_databases:
- wordpress
- laravel
Hechos (facts)¶
Cada vez que Ansible conecta con un host recopila hechos sobre él (SO, IPs, CPU, RAM, etc.). Puedes verlos así:
ansible web01 -m setup
Y usarlos directamente como variables en plantillas y playbooks:
- ansible.builtin.debug:
msg: "Esta máquina es {{ ansible_distribution }} {{ ansible_distribution_version }}"
| Hecho útil | Significado |
|---|---|
ansible_hostname |
Nombre corto del host |
ansible_default_ipv4.address |
IP principal |
ansible_distribution / ansible_distribution_version |
Distro y versión (Ubuntu 22.04, Debian 12...) |
ansible_memtotal_mb |
RAM total |
ansible_processor_vcpus |
Núcleos virtuales |
ansible_date_time.epoch |
Marca de tiempo (útil para generar números de serie DNS, por ejemplo) |
Plantillas Jinja2¶
Las plantillas Jinja2 (.j2) permiten generar ficheros de configuración a partir de variables. Es probablemente el rasgo más potente de Ansible.
Ejemplo: plantilla de configuración Nginx¶
roles/web/templates/site.conf.j2
server {
listen {{ puerto_web }};
server_name {{ dominio }};
root /var/www/{{ proyecto }};
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
{% if https_habilitado | default(false) %}
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/{{ dominio }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ dominio }}/privkey.pem;
{% endif %}
}
Task que la renderiza:
- name: Desplegar configuración de Nginx
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/sites-available/{{ proyecto }}.conf
owner: root
group: root
mode: "0644"
notify: Recargar nginx
Tip
Jinja2 soporta condicionales ({% if %}), bucles ({% for %}), filtros (| default(), | upper, | join(',')) y herencia de plantillas. Es prácticamente un mini-lenguaje de programación.
Roles¶
A partir de cierto tamaño un playbook se vuelve inmanejable. Los roles son la unidad de reutilización en Ansible. Cada rol tiene una estructura estándar:
roles/
└── common/
├── defaults/main.yml # valores por defecto (prioridad baja)
├── vars/main.yml # variables internas (prioridad alta)
├── tasks/main.yml # tareas principales
├── handlers/main.yml # handlers
├── templates/ # plantillas .j2
├── files/ # ficheros estáticos a copiar
├── meta/main.yml # dependencias del rol
└── README.md # documentación
Uso desde un playbook¶
---
- name: Aprovisionamiento de webservers
hosts: webservers
become: true
roles:
- common
- docker_host
- web
- backups
Crear un rol vacío con plantilla estándar¶
ansible-galaxy role init roles/web
Crea automáticamente todas las carpetas y los main.yml vacíos.
Ejemplo: rol common mínimo¶
roles/common/defaults/main.yml
zona_horaria: "Europe/Madrid"
paquetes_base:
- curl
- git
- htop
- vim
- ufw
- python3-pip
roles/common/tasks/main.yml
---
- name: Configurar zona horaria
community.general.timezone:
name: "{{ zona_horaria }}"
- name: Actualizar caché de paquetes
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Instalar paquetes base
ansible.builtin.apt:
name: "{{ paquetes_base }}"
state: present
- name: Habilitar UFW con política deny por defecto
community.general.ufw:
state: enabled
policy: deny
- name: Permitir SSH en UFW
community.general.ufw:
rule: allow
port: "22"
proto: tcp
Handlers¶
Los handlers son tareas que solo se ejecutan si otra tarea las notifica. Su uso típico es reiniciar servicios tras un cambio de configuración.
roles/web/handlers/main.yml
---
- name: Recargar nginx
ansible.builtin.service:
name: nginx
state: reloaded
- name: Reiniciar nginx
ansible.builtin.service:
name: nginx
state: restarted
Task que lo notifica:
- name: Configuración de Nginx
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/sites-available/{{ proyecto }}.conf
notify: Recargar nginx
Si Nginx ya tenía esa configuración, la task no cambia nada y el handler no se ejecuta. Si cambia, el handler se dispara al final del play.
Nota
Los handlers se ejecutan una sola vez, al final del play, sin importar cuántas tareas lo hayan notificado.
Ansible Vault: secretos cifrados¶
Nunca debes subir contraseñas, claves o tokens en claro a Git. Ansible incluye ansible-vault para cifrar ficheros sensibles.
Crear un fichero cifrado¶
ansible-vault create vault/secrets.yml
Te pide una contraseña y abre un editor. Lo que escribas se guardará cifrado:
vault_mysql_root_password: "P4ssw0rd_super_segura"
vault_mysql_app_password: "Otra_clave_robusta"
vault_aws_secret_key: "AKIA..."
Editar un fichero cifrado¶
ansible-vault edit vault/secrets.yml
Usarlo en un playbook¶
- name: Importar secretos cifrados
ansible.builtin.include_vars: vault/secrets.yml
- name: Configurar usuario root MySQL
community.mysql.mysql_user:
name: root
password: "{{ vault_mysql_root_password }}"
no_log: true
Ejecutar pidiendo la contraseña del vault¶
ansible-playbook site.yml --ask-vault-pass
O usando un fichero (no commiteable):
ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt
Advertencia
- Añade
~/.vault_pass.txtal.gitignorey nunca subas la vault password al repositorio. - Usa
no_log: trueen tareas que manejen secretos para que no aparezcan en los logs. - En CI/CD (GitHub Actions) guarda la vault password como secret del repositorio.
Integración Terraform + Ansible¶
Una de las combinaciones más potentes en DevOps es Terraform para crear la infraestructura + Ansible para configurarla.
sequenceDiagram
participant Dev as Operador
participant TF as Terraform
participant AWS as AWS API
participant ANS as Ansible
participant EC2 as EC2 nodes
Dev->>TF: terraform apply
TF->>AWS: Crea VPC, SG, EC2
AWS-->>TF: IDs e IPs
TF-->>Dev: outputs (IPs, DNS)
Dev->>ANS: ansible-playbook (inventario dinámico)
ANS->>EC2: SSH + configuración
EC2-->>ANS: changed / ok
Patrón recomendado¶
- Terraform crea VPC, subredes, SG, EC2 con la tag
Project=pi-asir. - Inventario dinámico de Ansible (
amazon.aws.aws_ec2) descubre las EC2 automáticamente y las agrupa por la tagRole(web,app,db,dns). - Ansible aplica los roles correspondientes a cada grupo.
Ejemplo de flujo completo¶
# 1. Infraestructura
cd deploy/terraform
terraform init && terraform apply -auto-approve
# 2. Esperar a que SSH esté disponible
sleep 60
# 3. Configuración
cd ../ansible
ansible-playbook -i inventory/aws_ec2.yml site.yml --ask-vault-pass
Tip
Esta integración se desarrolla en profundidad en el Tema 6: Despliegue y operación con el reto RG602.
Buenas prácticas profesionales¶
Summary
Recomendaciones extraídas de la documentación oficial de Ansible y de la experiencia profesional:
- Un rol = una responsabilidad. Roles pequeños y reutilizables, no monolíticos.
- Nombres claros en tareas (
name:): el output debe ser legible. - Idempotencia obligatoria. Si la 2.ª ejecución hace cambios, hay un bug.
- Variables parametrizadas, no valores hardcoded.
- Defaults razonables en
defaults/main.yml; sobrescritos según necesidad. - Ansible Vault para secretos. Nunca subir credenciales en claro.
no_log: trueen tareas sensibles para que no aparezcan en logs.--check --diffantes de cualquierapplyreal.ansible-linten CI para detectar problemas antes de producción.- Tags y nombres consistentes entre Terraform y Ansible (
Project,Role,Environment). - Documentar cada rol en su
README.md. - Git para versionar: PR + revisión + merge a
main. pipelining = Trueenansible.cfgpara acelerar SSH.
Troubleshooting y errores comunes¶
Conectividad SSH¶
| Síntoma | Causa probable | Solución |
|---|---|---|
UNREACHABLE! Permission denied (publickey) |
Clave SSH incorrecta, usuario erróneo o la clave pública no está en el nodo | Verifica ansible_user, ansible_ssh_private_key_file y usa ssh-copy-id |
Failed to connect to the host via ssh: Host key verification failed |
El host no está en known_hosts |
Conecta manualmente una vez con ssh o pon host_key_checking = False en ansible.cfg |
timeout (12s) waiting for privilege escalation prompt |
sudo pide contraseña pero no se la has pasado |
Añade --ask-become-pass o configura sudoers con NOPASSWD |
Idempotencia y módulos¶
| Síntoma | Causa probable | Solución |
|---|---|---|
changed=1 siempre que ejecutas un shell/command |
Esos módulos no son idempotentes por naturaleza | Usar módulo específico (apt, service, copy...) o añadir creates:/changed_when: |
Failed to lock apt |
Otro proceso apt corriendo en el nodo |
Esperar / wait_for: path=/var/lib/dpkg/lock-frontend state=absent |
Failed to import the required Python library (PyMySQL) |
Falta python3-pymysql en el nodo gestionado |
Instalarlo en el rol antes de usar módulos mysql_* |
The conditional check ... failed |
Variable indefinida o tipo incorrecto en when: |
Usar | default(...) o comprobar is defined |
Inventario¶
| Síntoma | Causa probable | Solución |
|---|---|---|
| Inventario dinámico AWS vacío | Filtros mal escritos, región incorrecta o credenciales faltantes | ansible-inventory -i aws_ec2.yml --graph y revisar regions / filters / variables AWS |
Could not match supplied host pattern, ignoring: webservers |
El grupo no existe en el inventario | ansible-inventory --graph para ver grupos reales |
Vault¶
| Síntoma | Causa probable | Solución |
|---|---|---|
Attempting to decrypt but no vault secrets found |
No pasaste --ask-vault-pass ni --vault-password-file |
Añadir uno de los dos |
ERROR! ... cipher mismatch |
Fichero corrupto o mal cifrado | Recuperar de Git, recrear con ansible-vault encrypt |
Modos de depuración útiles
-v,-vv,-vvv: aumenta el nivel de verbosidad.--start-at-task "Nombre de la task": retoma la ejecución desde una task concreta.--step: pregunta antes de ejecutar cada task.--list-hosts/--list-tasks: muestra qué se ejecutaría sin tocar nada.
Seguridad básica en automatización¶
| Riesgo | Mitigación |
|---|---|
| Secretos en claro en repositorio | ansible-vault, .gitignore, GitHub Secrets en CI/CD |
Acceso root por SSH |
PermitRootLogin no aplicado por playbook |
| Contraseñas SSH habilitadas | PasswordAuthentication no + claves obligatorias |
| Sudoers permisivo | NOPASSWD solo para usuarios y comandos concretos, no para todos |
| Variables sensibles en logs | no_log: true en tareas que las manejen |
Inventario con credenciales en ansible_password |
Migrar a claves SSH y ansible-vault |
Checklist operativo¶
Pre-vuelo (antes de aplicar)¶
- El inventario está actualizado (
ansible-inventory --graph). -
ansible all -m pingresponde correctamente. -
ansible-lint site.ymlno muestra errores críticos. - Ejecución en
--check --diffrevisada. - Secretos cifrados con
ansible-vault. - El equipo ha hecho pull de los últimos cambios en
main.
Post-vuelo (tras aplicar)¶
- La ejecución terminó sin
failed. - Segunda ejecución del playbook devuelve
changed=0(idempotencia). - Los servicios críticos están active (
ansible all -m service -a "name=nginx state=started"). - Logs revisados:
journalctl -p err -ben cada nodo. - Documentación de la sesión actualizada en la memoria.
Actividades¶
Las siguientes prácticas están inspiradas directamente en los proyectos reales del alumnado ASIR que sirven como referencia del módulo:
- Línea Docker: Proyecto Intermodular 2 ASIR IJJ — Repositorio.
- Línea AWS: Proyecto Intermodular — Alejandro Mariño — Repositorio.
- PR104 (RA3 // CE3b // 1-5p). Primer playbook: configuración base de un servidor Linux
Criterio de evaluación asociado:
- CE-RA3b: Configuración correcta del entorno y verificación funcional.
Contexto profesional:
Acabas de recibir un servidor Linux recién instalado (Ubuntu Server 22.04 o Debian 12) que va a soportar uno de los servicios del proyecto intermodular. Debes prepararlo siguiendo el procedimiento estándar de aprovisionamiento base que aplica el equipo de sistemas a cualquier host nuevo.
Arquitectura / escenario:
- 1 controlador: tu portátil o la máquina del aula con Ansible instalado.
- 1 nodo gestionado: VM (Vagrant / VirtualBox / KVM) o EC2 con Ubuntu Server 22.04.
- Comunicación SSH con clave pública.
Requisitos previos:
- Ansible instalado en el controlador (
ansible --version≥ 2.16). - Par de claves SSH generado (
~/.ssh/asir_pi). - Clave pública copiada al nodo gestionado (
ssh-copy-id).
Tareas:
-
Crear estructura mínima de proyecto:
pr104/ ├── ansible.cfg ├── inventory/hosts.ini └── site.yml -
Configurar
ansible.cfgconinventory,host_key_checking = Falseystdout_callback = yaml. - Crear inventario con 1 host gestionado en el grupo
[webservers]. - Crear un playbook
site.ymlque:- Configure la zona horaria a
Europe/Madrid. - Actualice la caché de paquetes.
- Instale los paquetes base:
curl,git,htop,vim,ufw. - Cree un usuario
asircon shell/bin/bash, perteneciente al gruposudo. - Habilite UFW con política por defecto deny y permita SSH.
- Configure la zona horaria a
- Ejecutar el playbook y comprobar
changed > 0. - Volver a ejecutar el playbook y comprobar
changed=0(idempotencia). - Documentar evidencias con capturas.
Buenas prácticas:
- Nombrar cada task con un texto descriptivo en español.
- No usar
commandnishellsi existe un módulo específico (apt,user,ufw). - Probar primero con
--check --diff.
Errores habituales:
- Olvidar
become: truey obtenerPermission deniedal instalar paquetes. - No tener
python3en el nodo (instalar consudo apt install python3). - Confundir
state: startedconstate: present(servicios vs paquetes).
Resultado esperado:
- Salida
PLAY RECAPconok>0, changed>0, failed=0en la 1.ª ejecución. - Salida
PLAY RECAPconok>0, changed=0, failed=0en la 2.ª. - Usuario
asirpuede conectarse por SSH con clave. sudo ufw statusmuestraStatus: activecon regla SSH.
Posibles ampliaciones:
- Añadir un task que despliegue una banner de bienvenida (
/etc/motd) mediante plantilla Jinja2. - Configurar
fail2banaplicando una protección básica del SSH.
- PR105 (RA3 // CE3b // 1-10p). Inventario multi-nodo y roles reutilizables
Criterio de evaluación asociado:
- CE-RA3b: Configuración correcta de múltiples nodos mediante roles.
Contexto profesional:
El equipo de operaciones ha levantado tres servidores Linux que sostendrán el proyecto. Cada uno tiene una función distinta:
- web01: servidor web (Nginx + PHP).
- db01: base de datos MySQL.
- mon01: monitorización (Node Exporter).
Tu tarea es organizar el proyecto Ansible en roles reutilizables para que cualquier integrante pueda aprovisionar los tres nodos con un solo comando.
Arquitectura / escenario:
flowchart LR
Ctrl[Controlador] -- SSH --> Web[web01<br/>Nginx + PHP]
Ctrl -- SSH --> DB[db01<br/>MySQL]
Ctrl -- SSH --> Mon[mon01<br/>Node Exporter]
Requisitos previos:
- 3 VMs Linux accesibles por SSH desde el controlador.
- Haber completado PR104.
Tareas:
-
Crear estructura de proyecto con roles:
pr105/ ├── ansible.cfg ├── inventory/ │ ├── hosts.ini │ └── group_vars/all.yml ├── site.yml └── roles/ ├── common/ ├── web/ ├── db/ └── monitoring/ -
Crear el rol
commoncon: zona horaria, paquetes base, UFW, usuarioasir. - Crear el rol
webque:- Instale
nginx,php-fpm,php-mysql. - Despliegue un
index.phpconphpinfo();mediante plantilla. - Use un handler que recargue Nginx tras cambios.
- Instale
- Crear el rol
dbque:- Instale
mysql-serverypython3-pymysql. - Cree la BBDD
wordpressy el usuariowp_usercon permisos. - Use
ansible-vaultpara la contraseña.
- Instale
- Crear el rol
monitoringque:- Despliegue Node Exporter como servicio systemd.
- Abra el puerto
9100solo desde el rango del aula.
- Crear
site.ymlque aplique:commona todos.webawebservers.dbadbservers.monitoringamonitoring.
- Ejecutar con
--ask-vault-pass, verificar y volver a ejecutar (idempotencia). - Documentar el árbol de directorios, los
PLAY RECAPy capturas de los servicios funcionando.
Fragmento de código real (rol
web):
# roles/web/tasks/main.yml
---
- name: Instalar Nginx y PHP-FPM
ansible.builtin.apt:
name: [nginx, php-fpm, php-mysql]
state: present
update_cache: true
- name: Desplegar página de prueba
ansible.builtin.template:
src: index.php.j2
dest: /var/www/html/index.php
owner: www-data
group: www-data
mode: "0644"
notify: Recargar nginx
- name: Permitir HTTP en UFW
community.general.ufw:
rule: allow
port: "80"
proto: tcp
Buenas prácticas:
- Variables compartidas en
group_vars/all.yml, específicas enhost_vars/. defaults/main.ymlpor rol para valores reutilizables.- Contraseñas siempre en Vault y con
no_log: true.
Errores habituales:
- Olvidar instalar
python3-pymysqlantes de usarcommunity.mysql.mysql_*. - Confundir la prioridad de variables (
vars>host_vars>group_vars>defaults). - No notificar al handler tras un
template(cambio sin reinicio del servicio).
Resultado esperado:
http://<IP_web01>muestra la salida dephpinfo().- Desde
web01,mysql -h db01 -u wp_user -p wordpressconecta correctamente. curl http://<IP_mon01>:9100/metricsdevuelve métricas Prometheus.
Posibles ampliaciones:
- Añadir HTTPS automático con
certboto un certificado autofirmado generado por Ansible. - Programar copias diarias de MySQL mediante
crondesde un rolbackups.
- PR106 (RA3 // CE3b // 1-10p). Despliegue automatizado del stack del proyecto (línea Docker)
Criterio de evaluación asociado:
- CE-RA3b: Despliegue reproducible del stack de aplicación del proyecto intermodular.
Contexto profesional:
Vuestro grupo lleva la línea Docker del proyecto (referencia: Proyecto Intermodular 2 ASIR IJJ). El stack incluye Nginx + WordPress + Laravel + MySQL y hasta ahora se levantaba ejecutando docker compose up -d a mano.
Tu tarea es convertir ese despliegue en un proceso totalmente automatizado con Ansible: cualquier miembro del equipo, partiendo de un Ubuntu Server limpio, debe poder ejecutar ansible-playbook site.yml y obtener el stack idéntico al validado, sin tocar la consola del servidor.
Arquitectura / escenario:
flowchart LR
Ctrl[Controlador] -- SSH --> Host[Host Linux]
Host -- docker compose --> Nginx[Nginx proxy]
Host --> WP[WordPress]
Host --> Laravel[Laravel]
Host --> MySQL[(MySQL)]
Requisitos previos:
- 1 VM Ubuntu Server 22.04 accesible por SSH.
- Repositorio Git del proyecto Docker con el
docker-compose.ymlvalidado en el Tema 5. - Ansible y colección
community.dockerinstalados.
Tareas:
- Crear los roles:
common(igual que PR104/PR105).docker_host: instala Docker Engine + Compose plugin desde el repo oficial.stack_compose: despliegadocker-compose.ymlynginx.confdesde plantillas Jinja2 y arranca el stack concommunity.docker.docker_compose_v2.
- Variables clave en
group_vars/all.yml:stack_dir: /opt/proyectomysql_db,mysql_user,dominio.
- Variables sensibles en
vault/secrets.ymlcifrado:vault_mysql_password,vault_mysql_root_password.
- Crear
roles/stack_compose/templates/docker-compose.yml.j2que sea una plantilla Jinja2 deldocker-compose.ymldel proyecto, parametrizando contraseñas y nombres con variables. - Crear un handler
Recrear stackque ejecutedocker_compose_v2conrecreate: alwayscuando cambie la plantilla. - Ejecutar el playbook con
--ask-vault-passy verificar:http://<IP_host>carga WordPress.- El segundo
applyproducechanged=0.
- Documentar evidencias y el procedimiento en la memoria del proyecto.
Fragmento de código real (rol
stack_compose):
# roles/stack_compose/tasks/main.yml
---
- name: Crear directorio del stack
ansible.builtin.file:
path: "{{ stack_dir }}"
state: directory
mode: "0750"
- name: Desplegar docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ stack_dir }}/docker-compose.yml"
mode: "0640"
notify: Recrear stack
- name: Arrancar el stack
community.docker.docker_compose_v2:
project_src: "{{ stack_dir }}"
state: present
Buenas prácticas:
- Docker NO es el protagonista del Tema 6; aquí Ansible invoca Docker. La idea es que el
docker-compose.ymlesté generado por Ansible a partir de variables. - Probar siempre con
--check --diffantes de unapplyreal. - Validar con un test de regresión: parar el stack, ejecutar el playbook, comprobar que vuelve a estar funcional.
Errores habituales:
- Olvidar instalar
community.dockerconansible-galaxy. - No añadir
modealtemplate: ficheros con permisos demasiado abiertos (0644vs0640). - Mezclar valores reales y plantilla: la plantilla no debe contener contraseñas literales.
Resultado esperado:
- Levantar un Ubuntu Server limpio +
ansible-playbook site.yml --ask-vault-pass= stack del proyecto funcional en < 5 min. - Idempotencia verificada (
changed=0en la 2.ª ejecución). - Memoria con el árbol de roles, el
PLAY RECAP, capturas de la web cargando y de los contenedores arriba.
Posibles ampliaciones:
- Añadir rol
backupsque programemysqldumpdiario en/var/backups/proyecto. - Añadir rol
monitoringcon Node Exporter + Prometheus para integrar con el Tema 5.
- PR107 (RA3+RA4 // CE3b+CE4a // 1-10p). Integración Terraform + Ansible en AWS (línea cloud)
Criterios de evaluación asociados:
- CE-RA3b: Despliegue automatizado reproducible.
- CE-RA4a: Documentación del procedimiento.
Contexto profesional:
Vuestro grupo lleva la línea AWS del proyecto (referencia: Proyecto Intermodular — Alejandro Mariño). La infraestructura actual se montó a mano en la consola de AWS: VPC, EC2 para WordPress, EC2 para Laravel, EC2 para MySQL y EC2 para Bind9. Tu tarea es eliminar el clic-clic en la consola sustituyéndolo por Terraform para la infraestructura y Ansible para la configuración.
Arquitectura / escenario:
flowchart LR
subgraph VPC["VPC 10.0.0.0/16"]
subgraph PUB["Subred pública 10.0.1.0/24"]
WEB[EC2 web<br/>WordPress]
APP[EC2 app<br/>Laravel]
DNS[EC2 DNS<br/>Bind9]
end
subgraph PRIV["Subred privada 10.0.2.0/24"]
DB[(EC2 MySQL)]
end
end
Ctrl[Controlador] -- terraform apply --> VPC
Ctrl -- ansible-playbook --> WEB
Ctrl -- ansible-playbook --> APP
Ctrl -- ansible-playbook --> DNS
Ctrl -- ansible-playbook --> DB
Requisitos previos:
- Cuenta AWS Educate Learner Lab o equivalente.
- Terraform ≥ 1.6 y Ansible ≥ 2.16 instalados.
- Colección
amazon.awsinstalada. - Par de claves SSH AWS importado o creado.
Tareas:
-
Estructura del proyecto:
pr107/ ├── terraform/ │ ├── main.tf │ ├── variables.tf │ ├── network.tf │ ├── security.tf │ ├── compute.tf │ └── outputs.tf └── ansible/ ├── ansible.cfg ├── inventory/aws_ec2.yml ├── site.yml └── roles/ ├── common/ ├── web/ ├── app/ ├── db/ ├── dns/ └── hardening/ -
Terraform crea: VPC, IGW, subredes pública/privada, SG (
web,db,dns), 4 EC2 con tagsProject=pi-asiryRole=web|app|db|dns. - Configurar el inventario dinámico
aws_ec2.ymlpara que Ansible descubra automáticamente los nodos por la tagRole. - Aplicar los roles:
common+hardeninga todos los nodos.dns(Bind9) al gruporole_dns.db(MySQL en EC2, sin RDS) al gruporole_db.web(WordPress) al gruporole_web.app(Laravel) al gruporole_app.
-
Flujo completo:
cd terraform && terraform apply -auto-approve sleep 60 cd ../ansible ansible-playbook -i inventory/aws_ec2.yml site.yml --ask-vault-pass -
Validar:
nslookup web.proyecto.local @<IP_DNS>responde.http://<IP_pública_web>muestra WordPress.- El SG bloquea
3306desde cualquier IP que no seasg-web(prueba negativa documentada). - Idempotencia: 2.ª ejecución sin cambios.
Fragmento de código real (rol
dnscon plantilla Jinja2):
{# roles/dns/templates/db.proyecto.local.j2 #}
$TTL 604800
@ IN SOA ns.proyecto.local. admin.proyecto.local. (
{{ ansible_date_time.epoch }} ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.proyecto.local.
ns IN A {{ hostvars[groups['role_dns'][0]]['ansible_host'] }}
web IN A {{ hostvars[groups['role_web'][0]]['ansible_host'] }}
app IN A {{ hostvars[groups['role_app'][0]]['ansible_host'] }}
db IN A {{ hostvars[groups['role_db'][0]]['private_ip_address'] }}
Buenas prácticas:
.gitignoredebe incluir.terraform/,*.tfstate*,*.tfvars,vault_pass,*.pem.- Tags coherentes entre Terraform y Ansible (
Project,Role). - Ejecutar
terraform planyansible-playbook --check --diffantes del apply real. - Documentar el procedimiento paso a paso en la memoria del proyecto (Tema 6).
Errores habituales:
- Inventario dinámico vacío: revisar
regions,filtersy credenciales AWS. - SSH falla justo después de
terraform apply: la EC2 todavía arranca; añadirwait_for_connectional inicio del play. - Olvidar
become: trueen tareas que tocan/etc.
Resultado esperado:
- Levantar la infraestructura completa desde cero en un solo flujo automatizado.
- Memoria del proyecto con capturas del
terraform plan,applyyPLAY RECAP. - Idempotencia verificada.
Posibles ampliaciones:
- Configurar un backend remoto Terraform con S3 + DynamoDB para
state-locking. - Pipeline GitHub Actions que ejecute
terraform planautomáticamente en cada Pull Request. - Añadir rol que instale el CloudWatch Agent y configure alarmas básicas.
- PR108 (RA4 // CE4a // 1-5p). Generación automática de documentación técnica del despliegue
Criterio de evaluación asociado:
- CE-RA4a: Documentación técnica del procedimiento de despliegue.
Contexto profesional:
Tras automatizar el despliegue con Ansible, el equipo de operaciones quiere que la documentación se genere sola a partir del propio inventario y roles. Así, cualquier cambio en infraestructura queda reflejado en la memoria sin esfuerzo manual.
Tareas:
- Crear un playbook
report.ymlque recopile los siguientes datos de todos los nodos:- Distribución y versión del SO.
- IP principal.
- Servicios
systemdactivos relevantes (nginx, mysql, docker, bind9). - Versiones de software clave (Docker, Nginx, MySQL).
- Usar el módulo
ansible.builtin.templatecon una plantilla Jinja2inventario.md.j2que genere un ficherodocs/inventario.mdlisto para la memoria. - Documentar el procedimiento en el
README.mddel repositorio. - Añadir el comando al workflow del equipo: ejecutar
report.ymldespués de cada despliegue.
Fragmento de plantilla Jinja2:
# Inventario técnico generado automáticamente
> Generado el {{ ansible_date_time.iso8601 }} con Ansible.
{% for host in groups['all'] %}
## {{ host }}
- **IP:** {{ hostvars[host]['ansible_default_ipv4']['address'] }}
- **SO:** {{ hostvars[host]['ansible_distribution'] }} {{ hostvars[host]['ansible_distribution_version'] }}
- **Memoria total:** {{ hostvars[host]['ansible_memtotal_mb'] }} MB
- **vCPU:** {{ hostvars[host]['ansible_processor_vcpus'] }}
{% endfor %}
Buenas prácticas:
- Versionar el resultado generado en la rama
mainpara que aparezca en GitHub Pages. - Incluir un workflow CI que ejecute
report.ymly haga commit del resultado.
Resultado esperado:
- Fichero
docs/inventario.mdactualizado tras cada despliegue. - Memoria del proyecto enlaza esta página como parte del capítulo "Despliegue y operación".
Recursos y referencias¶
Documentación oficial¶
- Ansible — Documentación oficial
- Ansible Builtin modules
- Ansible Galaxy — Colecciones y roles
- Ansible — Best Practices
- Amazon EC2 dynamic inventory plugin
- Ansible Vault
Proyectos del alumnado ASIR (referencia técnica)¶
- Proyecto Intermodular Docker (IJJ) — Repositorio.
- Proyecto Intermodular AWS (Alejandro Mariño) — Repositorio.
Material relacionado del módulo¶
- Tema 5 — Integración, seguridad y validación
- Tema 6 — Despliegue y operación (ampliación completa de Ansible aplicado al proyecto)
- Git — Sistemas de control de versiones
- Docker — Guía completa
Glosario de términos y acrónimos¶
- Agentless: modelo en el que la herramienta no requiere instalar un agente en los nodos gestionados
- Controlador (Control node): máquina con Ansible instalado desde la que se ejecutan los comandos
- Nodo gestionado (Managed node): servidor sobre el que Ansible aplica cambios
- Inventario: fichero o plugin que lista los hosts gestionados y sus grupos
- Playbook: fichero YAML que describe la secuencia de tareas a aplicar
- Task: unidad mínima de trabajo dentro de un playbook
- Módulo: programa que ejecuta una acción concreta (
apt,service,copy...) - Rol: unidad reutilizable con estructura estandarizada (tasks, handlers, templates, files, vars, defaults, meta)
- Handler: tarea que se ejecuta solo si otra la notifica (típicamente reinicios)
- Colección: unidad de distribución que agrupa módulos, roles y plugins
- Galaxy: repositorio oficial de roles y colecciones de la comunidad
- Idempotencia: propiedad por la que aplicar N veces produce el mismo resultado
- Vault: mecanismo de Ansible para cifrar secretos en YAML
- Jinja2: motor de plantillas usado por Ansible
- Fact: información que Ansible descubre del host (SO, IPs, RAM...) usable como variable
- IaC: Infrastructure as Code
- CM: Configuration Management