Saltar a contenido

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 (.ini y .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

  1. Comprueba la versión instalada de Ansible con ansible --version.
  2. Crea un inventario hosts.ini con localhost y ejecuta ansible localhost -m ping -c local.
  3. Lanza un comando ad-hoc para listar paquetes instalados (ansible all -m package_facts).
  4. Crea tu primer playbook que actualice los paquetes del sistema.
  5. 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:

  1. 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.
  2. 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 sudo sin 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:

  1. Control Node (controlador): máquina con Ansible instalado desde la que se ejecutan los comandos.
  2. Managed Nodes (nodos gestionados): los hosts sobre los que Ansible aplica los cambios; no requieren agente.
  3. Inventory (inventario): fichero (estático) o plugin (dinámico) que lista los nodos y sus grupos.
  4. 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.
  5. Tasks (tareas): invocaciones a módulos dentro de un playbook.
  6. Playbooks: ficheros YAML que describen el orden y los nodos sobre los que ejecutar las tasks.
  7. Roles: estructura de carpetas estándar que empaqueta tasks, plantillas, ficheros, variables y handlers para ser reutilizada.
  8. Plugins: extienden la funcionalidad del controlador (lookups, filtros, callbacks, conexión, inventory...).
  9. Collections: unidad de distribución que agrupa módulos, roles, plugins y documentación (ej. community.general, amazon.aws).
  10. 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.

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):

  1. Actualización del sistema e instalación de dependencias:
sudo apt update
sudo apt install -y software-properties-common
  1. Añadir el PPA oficial de Ansible:
sudo add-apt-repository --yes --update ppa:ansible/ansible
  1. Instalación de Ansible:
sudo apt install -y ansible
  1. 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
  1. Comprobación contra localhost con el módulo ping:
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.

  1. 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
  1. Copiar la clave pública al nodo gestionado:
ssh-copy-id -i ~/.ssh/asir_pi.pub ubuntu@192.168.1.50
  1. Probar conexión sin contraseña:
ssh -i ~/.ssh/asir_pi ubuntu@192.168.1.50
  1. 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):

  1. defaults/main.yml (dentro de un rol)
  2. inventory/group_vars/all.yml
  3. inventory/group_vars/<grupo>.yml
  4. inventory/host_vars/<host>.yml
  5. vars/ dentro de un rol
  6. vars: del play
  7. --extra-vars en 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.txt al .gitignore y nunca subas la vault password al repositorio.
  • Usa no_log: true en 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

  1. Terraform crea VPC, subredes, SG, EC2 con la tag Project=pi-asir.
  2. Inventario dinámico de Ansible (amazon.aws.aws_ec2) descubre las EC2 automáticamente y las agrupa por la tag Role (web, app, db, dns).
  3. 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: true en tareas sensibles para que no aparezcan en logs.
  • --check --diff antes de cualquier apply real.
  • ansible-lint en 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 = True en ansible.cfg para 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 ping responde correctamente.
  • ansible-lint site.yml no muestra errores críticos.
  • Ejecución en --check --diff revisada.
  • 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 -b en 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:

  • 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:

  1. Ansible instalado en el controlador (ansible --version ≥ 2.16).
  2. Par de claves SSH generado (~/.ssh/asir_pi).
  3. Clave pública copiada al nodo gestionado (ssh-copy-id).

Tareas:

  1. Crear estructura mínima de proyecto:

    pr104/
    ├── ansible.cfg
    ├── inventory/hosts.ini
    └── site.yml
    
  2. Configurar ansible.cfg con inventory, host_key_checking = False y stdout_callback = yaml.

  3. Crear inventario con 1 host gestionado en el grupo [webservers].
  4. Crear un playbook site.yml que:
    • Configure la zona horaria a Europe/Madrid.
    • Actualice la caché de paquetes.
    • Instale los paquetes base: curl, git, htop, vim, ufw.
    • Cree un usuario asir con shell /bin/bash, perteneciente al grupo sudo.
    • Habilite UFW con política por defecto deny y permita SSH.
  5. Ejecutar el playbook y comprobar changed > 0.
  6. Volver a ejecutar el playbook y comprobar changed=0 (idempotencia).
  7. Documentar evidencias con capturas.

Buenas prácticas:

  • Nombrar cada task con un texto descriptivo en español.
  • No usar command ni shell si existe un módulo específico (apt, user, ufw).
  • Probar primero con --check --diff.

Errores habituales:

  • Olvidar become: true y obtener Permission denied al instalar paquetes.
  • No tener python3 en el nodo (instalar con sudo apt install python3).
  • Confundir state: started con state: present (servicios vs paquetes).

Resultado esperado:

  • Salida PLAY RECAP con ok>0, changed>0, failed=0 en la 1.ª ejecución.
  • Salida PLAY RECAP con ok>0, changed=0, failed=0 en la 2.ª.
  • Usuario asir puede conectarse por SSH con clave.
  • sudo ufw status muestra Status: active con regla SSH.

Posibles ampliaciones:

  • Añadir un task que despliegue una banner de bienvenida (/etc/motd) mediante plantilla Jinja2.
  • Configurar fail2ban aplicando 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:

  1. Crear estructura de proyecto con roles:

    pr105/
    ├── ansible.cfg
    ├── inventory/
    │   ├── hosts.ini
    │   └── group_vars/all.yml
    ├── site.yml
    └── roles/
        ├── common/
        ├── web/
        ├── db/
        └── monitoring/
    
  2. Crear el rol common con: zona horaria, paquetes base, UFW, usuario asir.

  3. Crear el rol web que:
    • Instale nginx, php-fpm, php-mysql.
    • Despliegue un index.php con phpinfo(); mediante plantilla.
    • Use un handler que recargue Nginx tras cambios.
  4. Crear el rol db que:
    • Instale mysql-server y python3-pymysql.
    • Cree la BBDD wordpress y el usuario wp_user con permisos.
    • Use ansible-vault para la contraseña.
  5. Crear el rol monitoring que:
    • Despliegue Node Exporter como servicio systemd.
    • Abra el puerto 9100 solo desde el rango del aula.
  6. Crear site.yml que aplique:
    • common a todos.
    • web a webservers.
    • db a dbservers.
    • monitoring a monitoring.
  7. Ejecutar con --ask-vault-pass, verificar y volver a ejecutar (idempotencia).
  8. Documentar el árbol de directorios, los PLAY RECAP y 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 en host_vars/.
  • defaults/main.yml por rol para valores reutilizables.
  • Contraseñas siempre en Vault y con no_log: true.

Errores habituales:

  • Olvidar instalar python3-pymysql antes de usar community.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 de phpinfo().
  • Desde web01, mysql -h db01 -u wp_user -p wordpress conecta correctamente.
  • curl http://<IP_mon01>:9100/metrics devuelve métricas Prometheus.

Posibles ampliaciones:

  • Añadir HTTPS automático con certbot o un certificado autofirmado generado por Ansible.
  • Programar copias diarias de MySQL mediante cron desde un rol backups.

  • 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.yml validado en el Tema 5.
  • Ansible y colección community.docker instalados.

Tareas:

  1. Crear los roles:
    • common (igual que PR104/PR105).
    • docker_host: instala Docker Engine + Compose plugin desde el repo oficial.
    • stack_compose: despliega docker-compose.yml y nginx.conf desde plantillas Jinja2 y arranca el stack con community.docker.docker_compose_v2.
  2. Variables clave en group_vars/all.yml:
    • stack_dir: /opt/proyecto
    • mysql_db, mysql_user, dominio.
  3. Variables sensibles en vault/secrets.yml cifrado:
    • vault_mysql_password, vault_mysql_root_password.
  4. Crear roles/stack_compose/templates/docker-compose.yml.j2 que sea una plantilla Jinja2 del docker-compose.yml del proyecto, parametrizando contraseñas y nombres con variables.
  5. Crear un handler Recrear stack que ejecute docker_compose_v2 con recreate: always cuando cambie la plantilla.
  6. Ejecutar el playbook con --ask-vault-pass y verificar:
    • http://<IP_host> carga WordPress.
    • El segundo apply produce changed=0.
  7. 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.yml esté generado por Ansible a partir de variables.
  • Probar siempre con --check --diff antes de un apply real.
  • Validar con un test de regresión: parar el stack, ejecutar el playbook, comprobar que vuelve a estar funcional.

Errores habituales:

  • Olvidar instalar community.docker con ansible-galaxy.
  • No añadir mode al template: ficheros con permisos demasiado abiertos (0644 vs 0640).
  • 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=0 en 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 backups que programe mysqldump diario en /var/backups/proyecto.
  • Añadir rol monitoring con 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.aws instalada.
  • Par de claves SSH AWS importado o creado.

Tareas:

  1. 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/
    
  2. Terraform crea: VPC, IGW, subredes pública/privada, SG (web, db, dns), 4 EC2 con tags Project=pi-asir y Role=web|app|db|dns.

  3. Configurar el inventario dinámico aws_ec2.yml para que Ansible descubra automáticamente los nodos por la tag Role.
  4. Aplicar los roles:
    • common + hardening a todos los nodos.
    • dns (Bind9) al grupo role_dns.
    • db (MySQL en EC2, sin RDS) al grupo role_db.
    • web (WordPress) al grupo role_web.
    • app (Laravel) al grupo role_app.
  5. Flujo completo:

    cd terraform && terraform apply -auto-approve
    sleep 60
    cd ../ansible
    ansible-playbook -i inventory/aws_ec2.yml site.yml --ask-vault-pass
    
  6. Validar:

    • nslookup web.proyecto.local @<IP_DNS> responde.
    • http://<IP_pública_web> muestra WordPress.
    • El SG bloquea 3306 desde cualquier IP que no sea sg-web (prueba negativa documentada).
    • Idempotencia: 2.ª ejecución sin cambios.

Fragmento de código real (rol dns con 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:

  • .gitignore debe incluir .terraform/, *.tfstate*, *.tfvars, vault_pass, *.pem.
  • Tags coherentes entre Terraform y Ansible (Project, Role).
  • Ejecutar terraform plan y ansible-playbook --check --diff antes 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, filters y credenciales AWS.
  • SSH falla justo después de terraform apply: la EC2 todavía arranca; añadir wait_for_connection al inicio del play.
  • Olvidar become: true en 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, apply y PLAY RECAP.
  • Idempotencia verificada.

Posibles ampliaciones:

  • Configurar un backend remoto Terraform con S3 + DynamoDB para state-locking.
  • Pipeline GitHub Actions que ejecute terraform plan automá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:

  1. Crear un playbook report.yml que recopile los siguientes datos de todos los nodos:
    • Distribución y versión del SO.
    • IP principal.
    • Servicios systemd activos relevantes (nginx, mysql, docker, bind9).
    • Versiones de software clave (Docker, Nginx, MySQL).
  2. Usar el módulo ansible.builtin.template con una plantilla Jinja2 inventario.md.j2 que genere un fichero docs/inventario.md listo para la memoria.
  3. Documentar el procedimiento en el README.md del repositorio.
  4. Añadir el comando al workflow del equipo: ejecutar report.yml despué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 main para que aparezca en GitHub Pages.
  • Incluir un workflow CI que ejecute report.yml y haga commit del resultado.

Resultado esperado:

  • Fichero docs/inventario.md actualizado 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

Proyectos del alumnado ASIR (referencia técnica)

Material relacionado del módulo


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