Tutoriales 12 min de lectura

Automatizar tareas de infraestructura con Ansible

Guía paso a paso para instalar Ansible, escribir tu primer playbook y automatizar la configuración de servidores Linux — desde actualización de paquetes hasta hardening completo.

Terminal mostrando ejecución de playbook Ansible configurando múltiples servidores simultáneamente
Terminal mostrando ejecución de playbook Ansible configurando múltiples servidores simultáneamente

Configurar un servidor manualmente funciona cuando tienes uno. Cuando tienes cinco, es tedioso. Cuando tienes veinte, es insostenible. Y lo peor: cada servidor configurado a mano es ligeramente diferente — alguien olvidó habilitar el firewall en uno, otro tiene una versión vieja de un paquete, un tercero tiene permisos diferentes. Esas diferencias invisibles son las que causan problemas en producción que nadie puede reproducir.

Ansible resuelve esto de raíz: defines la configuración deseada de tus servidores como código, la ejecutas y Ansible se encarga de que todos los servidores lleguen al mismo estado — sin importar cuántos sean.

Requisitos previos

Una máquina de control con Ubuntu 24.04 (puede ser tu laptop o un servidor de administración) y servidores objetivo con acceso SSH configurado con llaves.

Paso 1: Instalar Ansible

Ansible se instala solo en la máquina de control — no en los servidores objetivo:

sudo apt update
sudo apt install ansible -y
ansible --version

Verifica la conexión SSH a tus servidores:

ssh usuario@10.0.1.10 "hostname"
ssh usuario@10.0.1.11 "hostname"

Si puedes conectarte sin contraseña (con llaves SSH), Ansible también puede.

Paso 2: Inventario de servidores

El inventario le dice a Ansible qué servidores administrar. Crea un directorio de proyecto:

mkdir -p ~/ansible && cd ~/ansible
# inventory.ini
[webservers]
web1 ansible_host=10.0.1.10
web2 ansible_host=10.0.1.11

[dbservers]
db1 ansible_host=10.0.1.20

[monitoring]
monitor1 ansible_host=10.0.1.30

[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3

Verifica que Ansible puede conectarse:

ansible all -i inventory.ini -m ping

Deberías ver SUCCESS para cada servidor con un "pong" como respuesta.

Paso 3: Tu primer playbook

Un playbook es un archivo YAML que describe el estado deseado de tus servidores. Empecemos con uno básico que actualiza paquetes e instala herramientas esenciales:

# playbooks/base.yml
---
- name: Configuración base de servidores
  hosts: all
  become: true  # Ejecutar como root (sudo)

  tasks:
    - name: Actualizar caché de paquetes
      apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Actualizar todos los paquetes
      apt:
        upgrade: safe
      register: upgrade_result

    - name: Instalar paquetes esenciales
      apt:
        name:
          - curl
          - wget
          - git
          - htop
          - vim
          - unzip
          - net-tools
          - chrony
          - jq
        state: present

    - name: Configurar timezone
      timezone:
        name: America/Mexico_City

    - name: Habilitar sincronización NTP
      systemd:
        name: chrony
        state: started
        enabled: true

    - name: Mostrar resumen de actualización
      debug:
        msg: "Paquetes actualizados: {{ upgrade_result.stdout_lines | default(['Ninguno']) }}"

Ejecútalo:

ansible-playbook -i inventory.ini playbooks/base.yml

Ansible muestra cada tarea con su resultado: ok (ya estaba como se pidió), changed (se aplicó un cambio) o failed. Eso es idempotencia — si lo ejecutas otra vez, todo saldrá ok porque ya no hay cambios pendientes.

Paso 4: Playbook de hardening

Ahora automaticemos los pasos de hardening de servidores que hicimos manualmente:

# playbooks/hardening.yml
---
- name: Hardening de servidores Linux
  hosts: all
  become: true

  vars:
    ssh_port: 2222
    allowed_users: "deploy admin"
    fail2ban_maxretry: 3
    fail2ban_bantime: 3600

  tasks:
    # --- SSH ---
    - name: Configurar SSH seguro
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
        state: present
      loop:
        - { regexp: '^#?Port', line: 'Port {{ ssh_port }}' }
        - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
        - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
        - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
        - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
        - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
        - { regexp: '^#?AllowUsers', line: 'AllowUsers {{ allowed_users }}' }
      notify: Reiniciar SSH

    # --- Firewall ---
    - name: Instalar UFW
      apt:
        name: ufw
        state: present

    - name: Configurar política por defecto
      ufw:
        direction: "{{ item.direction }}"
        policy: "{{ item.policy }}"
      loop:
        - { direction: incoming, policy: deny }
        - { direction: outgoing, policy: allow }

    - name: Permitir SSH
      ufw:
        rule: limit
        port: "{{ ssh_port }}"
        proto: tcp
        comment: "SSH con rate limit"

    - name: Habilitar UFW
      ufw:
        state: enabled

    # --- fail2ban ---
    - name: Instalar fail2ban
      apt:
        name: fail2ban
        state: present

    - name: Configurar fail2ban
      template:
        src: templates/jail.local.j2
        dest: /etc/fail2ban/jail.local
        mode: '0644'
      notify: Reiniciar fail2ban

    # --- Actualizaciones automáticas ---
    - name: Instalar unattended-upgrades
      apt:
        name: unattended-upgrades
        state: present

    - name: Habilitar actualizaciones automáticas
      copy:
        content: |
          APT::Periodic::Update-Package-Lists "1";
          APT::Periodic::Unattended-Upgrade "1";
        dest: /etc/apt/apt.conf.d/20auto-upgrades
        mode: '0644'

    # --- Kernel hardening ---
    - name: Aplicar parámetros de kernel seguros
      sysctl:
        name: "{{ item.key }}"
        value: "{{ item.value }}"
        sysctl_set: true
        reload: true
      loop:
        - { key: 'net.ipv4.conf.all.accept_redirects', value: '0' }
        - { key: 'net.ipv4.conf.all.send_redirects', value: '0' }
        - { key: 'net.ipv4.tcp_syncookies', value: '1' }
        - { key: 'net.ipv4.icmp_echo_ignore_broadcasts', value: '1' }
        - { key: 'kernel.dmesg_restrict', value: '1' }
        - { key: 'kernel.randomize_va_space', value: '2' }

    # --- Auditoría ---
    - name: Instalar auditd
      apt:
        name: auditd
        state: present

    - name: Habilitar auditd
      systemd:
        name: auditd
        state: started
        enabled: true

  handlers:
    - name: Reiniciar SSH
      systemd:
        name: sshd
        state: restarted

    - name: Reiniciar fail2ban
      systemd:
        name: fail2ban
        state: restarted

Crea el template de fail2ban:

mkdir -p templates
# templates/jail.local.j2
[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = 600
maxretry = {{ fail2ban_maxretry }}
banaction = ufw

[sshd]
enabled = true
port = {{ ssh_port }}
filter = sshd
logpath = /var/log/auth.log
maxretry = {{ fail2ban_maxretry }}

Handlers

Los handlers solo se ejecutan si la tarea que los notifica hizo un cambio. Si SSH ya estaba configurado correctamente, el handler Reiniciar SSH no se ejecuta — evitando reinicios innecesarios.

Paso 5: Roles para organización

Cuando tus playbooks crecen, organízalos en roles — módulos reutilizables para cada función:

~/ansible/
├── inventory.ini
├── playbooks/
│   └── site.yml              # Playbook principal que llama roles
├── roles/
│   ├── base/
│   │   └── tasks/
│   │       └── main.yml
│   ├── hardening/
│   │   ├── tasks/
│   │   │   └── main.yml
│   │   ├── templates/
│   │   │   └── jail.local.j2
│   │   └── defaults/
│   │       └── main.yml      # Variables por defecto del rol
│   ├── docker/
│   │   └── tasks/
│   │       └── main.yml
│   └── monitoring/
│       └── tasks/
│           └── main.yml

El playbook principal aplica los roles según el grupo del inventario:

# playbooks/site.yml
---
- name: Configuración base para todos los servidores
  hosts: all
  become: true
  roles:
    - base
    - hardening

- name: Servidores web con Docker
  hosts: webservers
  become: true
  roles:
    - docker

- name: Servidores de monitoreo
  hosts: monitoring
  become: true
  roles:
    - monitoring
# Ejecutar todo
ansible-playbook -i inventory.ini playbooks/site.yml

# Ejecutar solo en webservers
ansible-playbook -i inventory.ini playbooks/site.yml --limit webservers

# Ejecutar solo el rol de hardening
ansible-playbook -i inventory.ini playbooks/site.yml --tags hardening

Paso 6: Playbook de Docker

Un ejemplo práctico — instalar Docker en todos los webservers:

# roles/docker/tasks/main.yml
---
- name: Instalar dependencias
  apt:
    name:
      - ca-certificates
      - curl
      - gnupg
    state: present

- name: Agregar GPG key de Docker
  apt_key:
    url: https://download.docker.com/linux/ubuntu/gpg
    state: present

- name: Agregar repositorio de Docker
  apt_repository:
    repo: "deb https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
    state: present

- name: Instalar Docker
  apt:
    name:
      - docker-ce
      - docker-ce-cli
      - containerd.io
      - docker-compose-plugin
    state: present
    update_cache: true

- name: Agregar usuario al grupo docker
  user:
    name: "{{ ansible_user }}"
    groups: docker
    append: true

- name: Habilitar Docker
  systemd:
    name: docker
    state: started
    enabled: true

- name: Verificar instalación
  command: docker --version
  register: docker_version
  changed_when: false

- name: Mostrar versión
  debug:
    msg: "Docker instalado: {{ docker_version.stdout }}"

Paso 7: Playbook de monitoreo

Instalar node_exporter en todos los servidores:

# roles/monitoring/tasks/main.yml
---
- name: Crear usuario node_exporter
  user:
    name: node_exporter
    system: true
    shell: /bin/false
    create_home: false

- name: Descargar node_exporter
  get_url:
    url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
    dest: /tmp/node_exporter.tar.gz

- name: Extraer binario
  unarchive:
    src: /tmp/node_exporter.tar.gz
    dest: /tmp/
    remote_src: true

- name: Instalar binario
  copy:
    src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
    dest: /usr/local/bin/node_exporter
    mode: '0755'
    remote_src: true

- name: Crear servicio systemd
  template:
    src: node_exporter.service.j2
    dest: /etc/systemd/system/node_exporter.service
  notify: Reiniciar node_exporter

- name: Habilitar y arrancar
  systemd:
    name: node_exporter
    state: started
    enabled: true
    daemon_reload: true

- name: Abrir puerto en firewall
  ufw:
    rule: allow
    from_ip: "{{ prometheus_server_ip }}"
    port: '9100'
    proto: tcp
    comment: "Prometheus scraping"

Comandos ad-hoc

No todo necesita un playbook. Ansible puede ejecutar comandos rápidos en todos tus servidores:

# Ver espacio en disco de todos los servidores
ansible all -i inventory.ini -a "df -h /"

# Reiniciar un servicio en todos los webservers
ansible webservers -i inventory.ini -b -m systemd -a "name=nginx state=restarted"

# Ver uptime de todos
ansible all -i inventory.ini -a "uptime"

# Copiar un archivo a todos los servidores
ansible all -i inventory.ini -b -m copy -a "src=./mi-config.conf dest=/etc/mi-config.conf mode=0644"

# Verificar qué paquetes se pueden actualizar
ansible all -i inventory.ini -b -a "apt list --upgradable"

Buenas prácticas

Usa --check antes de ejecutar — el modo dry-run muestra qué cambiaría sin hacer nada:

ansible-playbook -i inventory.ini playbooks/hardening.yml --check --diff

Versiona tus playbooks en git — tus configuraciones de infraestructura son código y merecen control de versiones, code review y historial.

Usa variables por entorno — diferentes configuraciones para desarrollo, staging y producción:

ansible-playbook -i inventory_produccion.ini playbooks/site.yml
ansible-playbook -i inventory_staging.ini playbooks/site.yml

Cifra secretos con Ansible Vault — contraseñas, llaves y tokens cifrados dentro del repositorio:

ansible-vault encrypt vars/secretos.yml
ansible-playbook -i inventory.ini playbooks/site.yml --ask-vault-pass

Prueba en staging primero — siempre ejecuta playbooks nuevos en un ambiente de pruebas antes de producción.

Siguientes pasos

Con Ansible dominado, puedes escalar tu automatización:

  • AWX / Semaphore — interfaz web para ejecutar playbooks con historial, permisos y programación
  • Ansible + Proxmox — aprovisionar VMs automáticamente con el módulo community.general.proxmox_kvm
  • Ansible + CI/CD — ejecutar playbooks desde pipelines de GitLab
  • Molecule — testing automatizado de roles de Ansible antes de aplicarlos en producción
  • Consultoría de infraestructura — diseñamos tu estrategia de automatización con Ansible adaptada a tu operación

Infraestructura como código

¿Quieres que tus servidores se configuren solos?

Implementamos Ansible para que cada servidor nuevo de tu infraestructura nazca configurado, seguro y monitoreado — en minutos, no en horas.

Solicitar evaluación

Preguntas frecuentes

Temas relacionados

#ansible#automatizacion#devops#linux#infraestructura#tutorial

¿Te fue útil? Compártelo

Artículos relacionados

Ver todos

Consultoría gratuita

¿Necesitas automatizar la gestión de tu infraestructura?

Implementamos Ansible para que la configuración de tus servidores sea reproducible, documentada y ejecutable en minutos.

Solicitar evaluación