TL;DR — Resumen Rápido

Domina Ansible playbooks y roles para automatizar infraestructura. Cubre inventario, modulos, Jinja2, ansible-vault, manejo de errores y despliegue LAMP.

ANSIBLE — PLAYBOOKS, ROLES Y VAULT Inventario INI / YAML group_vars/ host_vars/ Playbook plays / tareas handlers / vars Motor Ansible Roles / Modulos Vault / Jinja2 SSH webservers nginx / php dbservers mysql / backup loadbalancers haproxy / certs ansible-vault Secretos cifrados AES-256 Roles tasks / handlers templates / files defaults / meta Modelo push sin agentes — el inventario lo impulsa todo

Gestionar infraestructura a escala sin automatizacion es una formula para la deriva de configuracion, pasos olvidados e incidentes a las 3 de la madrugada. Los playbooks y roles de Ansible te dan una forma estructurada y repetible de definir exactamente como debe verse cada servidor y aplicar ese estado de forma idempotente en toda tu flota. Esta guia cubre el flujo de trabajo completo de Ansible — desde la gestion de inventario y la arquitectura de playbooks hasta los roles, plantillas Jinja2, secretos con ansible-vault y manejo de errores — con un despliegue real de una pila LAMP como ejemplo final.

Requisitos Previos

  • Ansible instalado en un nodo de control (Ubuntu 22.04+ o cualquier maquina Linux/macOS)
  • Acceso SSH basado en clave a uno o mas hosts gestionados
  • Python 3 en todos los hosts destino (preinstalado en la mayoria de distribuciones)
  • Familiaridad basica con la sintaxis YAML y la linea de comandos de Linux

Arquitectura de Ansible: El Modelo Push

Ansible utiliza una arquitectura push sin agentes. Tu nodo de control empuja la configuracion a los nodos gestionados a traves de SSH. No hay ningun demonio que ejecutar, ni base de datos de estado, ni nada que instalar en los destinos mas alla de un interprete Python funcional.

La cadena de ejecucion es: Inventario → Playbook → Modulos.

  1. Ansible lee el inventario para determinar que hosts apuntar
  2. Analiza el playbook para construir una lista ordenada de tareas
  3. Para cada tarea, copia un pequeno modulo Python al host remoto via SSH, lo ejecuta, captura el resultado y elimina el modulo
  4. Los resultados (ok / changed / failed / skipped) se agregan y muestran en el terminal

Este modelo significa que cada ejecucion de playbook es independiente. Si un host no esta disponible, solo ese host falla — el resto continua.

Gestion de Inventario

Formato INI vs YAML

# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_port=2222

[dbservers]
db1.example.com
db2.example.com

[production:children]
webservers
dbservers

[all:vars]
ansible_user=deployer
ansible_ssh_private_key_file=~/.ssh/id_ed25519
# inventory.yml
all:
  vars:
    ansible_user: deployer
    ansible_ssh_private_key_file: ~/.ssh/id_ed25519
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
          ansible_port: 2222
    dbservers:
      hosts:
        db1.example.com:
        db2.example.com:

group_vars y host_vars

Coloca las variables en directorios dedicados que Ansible carga automaticamente:

proyecto/
  inventory.ini
  group_vars/
    all.yml
    webservers.yml
    dbservers.yml
  host_vars/
    web1.example.com.yml
  site.yml
# group_vars/webservers.yml
http_port: 80
https_port: 443
document_root: /var/www/html
worker_processes: 4

Inventario dinamico con el plugin aws_ec2

# aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
  "tag:Environment": production
keyed_groups:
  - key: tags.Role
    prefix: role

Estructura de un Playbook

Un playbook contiene uno o mas plays. Cada play mapea un conjunto de tareas a un grupo de hosts.

- name: Configurar servidores web
  hosts: webservers
  become: true
  vars:
    app_name: myapp
    app_port: 8080

  tasks:
    - name: Instalar nginx
      apt:
        name: nginx
        state: present
        update_cache: true

    - name: Desplegar config de nginx desde plantilla
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: "0644"
      notify: Reiniciar nginx

    - name: Asegurar que nginx esta activo y habilitado
      service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Reiniciar nginx
      service:
        name: nginx
        state: restarted

Condicionales con when

- name: Instalar apache2 (solo Debian)
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

- name: Instalar httpd (solo Red Hat)
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

Bucles con loop

- name: Instalar paquetes necesarios
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - ufw
    - fail2ban

- name: Crear directorios de la aplicacion
  file:
    path: "{{ item.path }}"
    state: directory
    owner: "{{ item.owner }}"
    mode: "{{ item.mode }}"
  loop:
    - { path: /var/www/myapp, owner: www-data, mode: "0755" }
    - { path: /var/log/myapp, owner: www-data, mode: "0750" }
    - { path: /etc/myapp, owner: root, mode: "0755" }

Plantillas Jinja2

{# templates/nginx.conf.j2 #}
user www-data;
worker_processes {{ worker_processes | default('auto') }};

events {
    worker_connections {{ worker_connections | default(1024) }};
}

http {
    server {
        listen {{ http_port }};
        server_name {{ server_name }};
        root {{ document_root }};

        {% if enable_ssl | default(false) %}
        listen {{ https_port }} ssl;
        ssl_certificate {{ ssl_cert_path }};
        {% endif %}
    }
}

Etiquetas para ejecucion selectiva

# Ejecutar solo tareas etiquetadas 'config'
ansible-playbook site.yml --tags config

# Omitir tareas etiquetadas 'install'
ansible-playbook site.yml --skip-tags install

Modulos Esenciales

ModuloPropositoParametros clave
apt / yumGestion de paquetesname, state, update_cache
copyCopiar archivos estaticossrc, dest, owner, mode
templateDesplegar plantillas Jinja2src, dest, owner, mode
serviceGestionar servicios del sistemaname, state, enabled
userGestionar cuentas de usuarioname, groups, shell, state
fileCrear dirs, enlaces simbolicospath, state, owner, mode
lineinfileEditar lineas en un archivopath, regexp, line, state
commandEjecutar un comandoargv, creates
shellComandos de shell con pipescmd, chdir
gitClonar o actualizar un reporepo, dest, version
docker_containerGestionar contenedores Dockername, image, state, ports
debugImprimir valores de variablesmsg, var

Estructura de los Roles

Los roles son el mecanismo principal para organizar y compartir automatizacion de Ansible. Crea uno con:

ansible-galaxy init roles/webserver

Esto crea el directorio completo:

roles/webserver/
  tasks/
    main.yml
  handlers/
    main.yml
  templates/
    nginx.conf.j2
  files/
    index.html
  vars/
    main.yml
  defaults/
    main.yml
  meta/
    main.yml

defaults vs vars

  • defaults/main.yml: Baja precedencia. Los llamadores pueden sobreescribir estos valores facilmente.
  • vars/main.yml: Alta precedencia. Estos sobreescriben la mayoria de otras fuentes de variables.
# roles/webserver/defaults/main.yml
http_port: 80
https_port: 443
worker_processes: auto
document_root: /var/www/html
enable_ssl: false
# roles/webserver/tasks/main.yml
- name: Instalar nginx
  apt:
    name: nginx
    state: present
    update_cache: true

- name: Crear directorio raiz del documento
  file:
    path: "{{ document_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: "0755"

- name: Desplegar configuracion de nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Reiniciar nginx

Usar roles en un playbook

# site.yml
- name: Configurar nivel web
  hosts: webservers
  become: true
  roles:
    - role: common
    - role: security
    - role: webserver
      vars:
        http_port: 80
        enable_ssl: true
        document_root: /var/www/myapp

Ansible Galaxy para Roles de la Comunidad

# Instalar un rol desde Galaxy
ansible-galaxy install geerlingguy.mysql

# Instalar desde un archivo de requisitos
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
  - name: geerlingguy.mysql
    version: "4.3.2"
collections:
  - name: community.docker
    version: ">=3.4.0"

Ansible Vault para Gestion de Secretos

Cifrar archivos y cadenas

# Crear un nuevo archivo cifrado
ansible-vault create group_vars/all/vault.yml

# Editar un archivo cifrado existente
ansible-vault edit group_vars/all/vault.yml

# Cifrar un archivo existente
ansible-vault encrypt secrets.yml

# Cifrar una cadena individual
ansible-vault encrypt_string 'MiContraseña123' --name 'db_password'

El patron estandar es mantener dos archivos de variables por grupo:

# group_vars/all/vars.yml (texto plano, en git)
db_host: db1.example.com
db_password: "{{ vault_db_password }}"
# group_vars/all/vault.yml (cifrado, en git)
vault_db_password: "SuperSecreto123!"
vault_api_key: "abcdef1234567890"
# Usar archivo de contrasena para evitar prompts interactivos
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Manejo de Errores

ignore_errors y failed_when

- name: Verificar si la aplicacion esta instalada
  command: which myapp
  register: myapp_check
  ignore_errors: true
  changed_when: false

- name: Instalar aplicacion solo si falta
  apt:
    name: myapp
    state: present
  when: myapp_check.rc != 0

- name: Ejecutar script de migracion
  command: /opt/myapp/migrate.sh
  register: migration_result
  failed_when: "'ERROR' in migration_result.stdout"
  changed_when: "'Changes applied' in migration_result.stdout"

block / rescue / always

- name: Desplegar aplicacion con rollback
  block:
    - name: Detener servicio de la aplicacion
      service:
        name: myapp
        state: stopped

    - name: Desplegar nueva version
      git:
        repo: https://github.com/org/myapp.git
        dest: /opt/myapp
        version: "{{ app_version }}"

    - name: Iniciar servicio de la aplicacion
      service:
        name: myapp
        state: started

  rescue:
    - name: Revertir a la version anterior
      git:
        repo: https://github.com/org/myapp.git
        dest: /opt/myapp
        version: "{{ previous_version }}"

    - name: Notificar al equipo del fallo
      debug:
        msg: "Despliegue de {{ app_version }} fallido. Revertido a {{ previous_version }}."

  always:
    - name: Registrar intento de despliegue
      shell: echo "{{ app_version }} intentado en $(date)" >> /var/log/deployments.log
      changed_when: false

Buenas Practicas de Idempotencia

  • Prefiere modulos sobre shell/command: los modulos verifican el estado antes de actuar
  • Usa creates con command: salta el comando si el archivo ya existe
  • Establece changed_when: false en comandos que solo leen estado
  • Usa lineinfile en lugar de shell echo >> para modificar archivos de configuracion
  • Evita apt: update_cache: true en cada tarea: usa cache_valid_time: 3600

Configuracion de ansible.cfg

[defaults]
inventory = ./inventory.ini
remote_user = deployer
private_key_file = ~/.ssh/id_ed25519
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
roles_path = ./roles:~/.ansible/roles
forks = 10

[privilege_escalation]
become = True
become_method = sudo
become_user = root

Depuracion y Pruebas

# Simulacro -- muestra que cambiaria sin aplicar
ansible-playbook site.yml --check --diff

# Maxima verbosidad
ansible-playbook site.yml -vvv

# Validar sintaxis del playbook
ansible-playbook site.yml --syntax-check
- name: Mostrar variables resueltas
  debug:
    msg: "App: {{ app_name }}, Puerto: {{ app_port }}"

- name: Capturar e inspeccionar salida de comando
  command: systemctl status nginx
  register: nginx_status
  ignore_errors: true
  changed_when: false

- name: Imprimir salida de nginx
  debug:
    var: nginx_status.stdout_lines

Comparativa de Herramientas

CaracteristicaAnsibleTerraformPuppetChefSaltStack
Uso principalGestion config + orquestacionAprovisionamiento infraGestion configGestion configConfig + ejecucion remota
ArquitecturaSin agente (SSH)Sin agente (API)Con agenteCon agenteCon o sin agente
LenguajeYAMLHCLPuppet DSLRubyYAML / Python
Seguimiento estadoSin estadoArchivo tfstatePuppetDBChef serverSalt mine
Curva aprendizajeBajaMediaAltaAltaMedia
IdempotenteSiSiSiSiSi

Ansible y Terraform son complementarios: usa Terraform para crear recursos en la nube, luego Ansible para configurarlos.

Ejemplo Real: Despliegue de Pila LAMP

# lamp.yml
- name: Desplegar pila LAMP
  hosts: webservers
  become: true
  vars:
    php_version: "8.3"
    mysql_root_password: "{{ vault_mysql_root_password }}"
    mysql_db_name: myapp
    mysql_db_user: appuser
    mysql_db_password: "{{ vault_mysql_db_password }}"

  tasks:
    - name: Instalar Apache y paquetes PHP
      apt:
        name:
          - apache2
          - "libapache2-mod-php{{ php_version }}"
          - "php{{ php_version }}"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-curl"
          - "php{{ php_version }}-mbstring"
        state: present
        update_cache: true

    - name: Instalar servidor MySQL
      apt:
        name:
          - mysql-server
          - python3-pymysql
        state: present

    - name: Iniciar y habilitar MySQL
      service:
        name: mysql
        state: started
        enabled: true

    - name: Crear base de datos de la aplicacion
      mysql_db:
        login_user: root
        login_password: "{{ mysql_root_password }}"
        name: "{{ mysql_db_name }}"
        state: present

    - name: Habilitar mod_rewrite de Apache
      apache2_module:
        name: rewrite
        state: present
      notify: Reiniciar Apache

    - name: Clonar aplicacion desde git
      git:
        repo: "{{ app_repo }}"
        dest: /var/www/html
        version: "{{ app_version }}"
        force: true

    - name: Establecer propiedad del directorio web
      file:
        path: /var/www/html
        owner: www-data
        group: www-data
        recurse: true

    - name: Asegurar que Apache esta iniciado y habilitado
      service:
        name: apache2
        state: started
        enabled: true

  handlers:
    - name: Reiniciar Apache
      service:
        name: apache2
        state: restarted
ansible-playbook -i inventory.ini lamp.yml --vault-password-file ~/.vault_pass

Resumen

  • La arquitectura push sin agentes de Ansible significa que solo necesitas acceso SSH para empezar a automatizar
  • El inventario (INI, YAML o dinamico) dirige todo el apuntamiento a hosts; group_vars y host_vars mantienen las variables organizadas
  • Los playbooks encadenan plays, tareas, handlers y plantillas Jinja2 en un flujo de trabajo declarativo y legible
  • Los roles empaquetan tareas, handlers, plantillas y defaults en unidades reutilizables y compartibles
  • ansible-vault cifra secretos en reposo; combina archivos vault con archivos de variables en texto plano para mantener limpio el historial de git
  • block/rescue/always proporciona manejo estructurado de errores y logica de rollback para despliegues en produccion
  • Siempre valida con --check --diff y -vvv antes de ejecutar en produccion

Articulos Relacionados