Skip to content

ANSIBLE

El acceso a los equipos se puede hacer por usr-pass pero se DEBE hacer por pares de llaves público-privadas.

Para ello lo ideal es generar las llaves en el gestor (ordenador del profesor) y ubicar la llave pública del mismo en TODAS las máquinas a controlar, añadirla a ~/.ssh/authorized_keys tanto en Windows como GNU/Linux con cat llave_a_añadir.pub >> ~/.ssh/authorized_keys.

Comandos CLI

Ansible puede funcionar lanzando comandos directos o por play books separadas.

Pruebas "ping"

ansible -i inventory.yml vps -m ping -u usuario con par de llaves o con -K para que pida contraseña.

Corriendo playbooks

ansible-playbook -i inventory playbook-vps.yml

O con -vvvv para ser mas verboso y con -u usuario podemos seleccionar un usuario diferente al del ssh.

Run

ansible-playbook --inventory hosts \
  -user root \
  --ask-pass \
  --forks 5 \
  --limit SUBSET \
  playbook.yml

Recuerda que en ANSIBLE los permisos de root se adquieren con el modo --become y pide la clave con --ask-become-pass.

SUBSET es un patrón que limita el número de equipos del grupo dado, p.e. "des1", "sys1" o un equipo dado "des1_101".

Por defecto se realizarán 5 conexiones en paralelo, por lo que como óptimo puede ser adecuado para no saturar la red. Si tienes 24 alumnos puede ser adecuado subirlo a 6 o bajarlo a 4.

Instalar ansible

en Ubuntu y derivadas

sudo apt-get install ansible

Inventario

Ejemplo por grupos (ideal para aulas):

all:
  vars:
    ansible_user: usuario_remoto
    ansible_ssh_private_key_file: /home/usuario_local/ansible/ansible-private-key
    ansible_connection: ssh

   children:
    # Desarrollo DAW1 y DAM1
    des1:
      hosts:
        des1_101:
        des1_102:
          ansible_host: 192.168.30.102
        des1_103:
          ansible_host: 192.168.30.103
        des1_104:
          ansible_host: 192.168.30.104
        des1_105:
          ansible_host: 192.168.30.105
        des1_106:
          ansible_host: 192.168.30.106
        des1_107:
          ansible_host: 192.168.30.107
        des1_108:
          ansible_host: 192.168.30.108
        des1_109:
          ansible_host: 192.168.30.109
        des1_110:
          ansible_host: 192.168.30.110
        des1_111:
          ansible_host: 192.168.30.111
        des1_112:
          ansible_host: 192.168.30.112
        des1_113:
          ansible_host: 192.168.30.113
        des1_114:
          ansible_host: 192.168.30.114
        des1_115:
          ansible_host: 192.168.30.115
        des1_116:
          ansible_host: 192.168.30.116
        des1_117:
          ansible_host: 192.168.30.117
        des1_118:
          ansible_host: 192.168.30.118
        des1_119:
          ansible_host: 192.168.30.119
        des1_120:
          ansible_host: 192.168.30.120
        des1_121:
          ansible_host: 192.168.30.121
        des1_122:
          ansible_host: 192.168.30.122
        des1_123:
          ansible_host: 192.168.30.123
        des1_124:
          ansible_host: 192.168.30.124
        des1_125:
          ansible_host: 192.168.30.125
        des1_126:
          ansible_host: 192.168.30.126
        des1_127:
          ansible_host: 192.168.30.127
        des1_128:
          ansible_host: 192.168.30.128
        des1_129:
          ansible_host: 192.168.30.129
        des1_130:
          ansible_host: 192.168.30.130

    # Sistemas SMR1 y ASIR1
    sys1:
      hosts:
        sys1_201:
        #...
Ejemplos más complejos:
all:
  children:
    localhost:
      hosts:
        127.0.0.1
    vps:
      hosts:
        ateca-profe: 
          # ansible_host: 192.168.XX.YY
          ansible_host: dominio.duckdns.org
          ansible_port: 12345
          #ansible_ssh_user=root 
          #ansible_ssh_pass=xxxxxx
        #ateca-profe-ip:
        #  ansible_host: AA.BB.CC.DD
        #  ansible_port: 12345
    server_group2:
      children:
        app1:
          hosts:
            host2:
            host3:
        app2:
          hosts:
            host4:
            host5:

    #vars:
    #  ssh_port: 1234

#ungrouped:

Ejemplos:

Depuración básica

playbook-helloWorld.yml:
- hosts: all
  tasks:
    - name: Print message
      debug:
        msg: Hello Ansible World

Añadir, evitar y actualizar paquetes

  1. Comprueba conectividad (si existe seguimos)
  2. Instala los paquetes de la lista
  3. Elimina los paquetes de la lista de ausentes
  4. Actualiza el resto de paquetes

playbook-vps.yml:

- hosts: vps
  tasks:
    - name: Ping
      ansible.builtin.ping:

    - name: Install a list of packages
      ansible.builtin.apt:
        pkg:
          - sudo
          - curl
          - fail2ban
        #name: nginx=1.18.0
        state: latest
        update_cache: yes

    - name: Remove curl
      ansible.builtin.apt:
        pkg:
          - vagrant
        state: absent

    - name: Upgrade the OS (apt-get dist-upgrade)
      ansible.builtin.apt:
        upgrade: dist

Actualizar y apagar equipos

---
- name: Playbook de Actualización y Reinicio Controlado
  hosts: web_servers  # Asegúrate de que este grupo esté definido en your_inventory.ini
  become: yes         # Ejecutar con privilegios elevados (sudo)
  gather_facts: yes   # Recopila información del sistema operativo

  tasks:
    # 1. Actualización de paquetes
    - name: 🔄 1. Actualizar lista de paquetes
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600 # Solo actualizar si ha pasado una hora
      register: apt_update_status
      ignore_errors: yes # Permite seguir incluso si la actualización falla

    - name: 📦 2. Aplicar las actualizaciones de todos los paquetes
      ansible.builtin.apt:
        upgrade: yes
        autoremove: yes
      register: apt_upgrade_status

# --- LÓGICA DE APAGADO (SHUTDOWN) ---
# Si tu objetivo NO es actualizar, sino solo apagar, usa este playbook simple:
 - name: Apagado Controlado
   hosts: target_equipment
   become: yes
   tasks:
     - name: Ejecutar apagado seguro
       ansible.builtin.reboot:
         state: absent # Indica que el objetivo es el apagado
         reboot_timeout: 10

Bloquear acceso al exterior

---
- name: Configure iptables to block all except local and 192.168.30.0/24
  hosts: all
  become: true
  tasks:
    # 1. Allow loopback traffic (essential for local system operations)
    - name: Allow loopback interface
      ansible.builtin.iptables:
        chain: INPUT
        in_interface: lo
        jump: ACCEPT

    # 2. Allow traffic from the specific subnet 192.168.30.0/24
    - name: Allow traffic from 192.168.30.0/24
      ansible.builtin.iptables:
        chain: INPUT
        source: 192.168.30.0/24
        jump: ACCEPT

    # 3. Set default policy to DROP for all other incoming traffic
    - name: Drop all other incoming traffic
      ansible.builtin.iptables:
        chain: INPUT
        policy: DROP

    # 4. (Optional) Allow established/related connections to maintain active sessions
    - name: Allow established connections
      ansible.builtin.iptables:
        chain: INPUT
        ctstate:
          - ESTABLISHED
          - RELATED
        jump: ACCEPT   

Eliminar reglas IPTABLE

Para eliminar una regla, se usa el parámetro state: absent. Es crucial especificar los mismos parámetros de coincidencia (cadena, protocolo, puerto, comentario) que en la regla original para que Ansible pueda identificarla correctamente.

- name: Eliminar regla de puerto HTTP
  ansible.builtin.iptables:
    chain: INPUT
    protocol: tcp
    destination_port: 80
    jump: ACCEPT
    state: absent
    comment: "Regla para Apache"

Consideraciones importantes:

  • Persistencia: El módulo ansible.builtin.iptables aplica las reglas en tiempo real. Para que persistan tras un reinicio, depende de la distribución: en Debian/Ubuntu suele requerir iptables-persistent, mientras que en RHEL/CentOS se guarda en /etc/sysconfig/iptables.
  • Seguridad: Al establecer políticas restrictivas (ej. policy: DROP), asegúrese de permitir primero el tráfico SSH (port 22) para no perder la conexión remota.
  • Módulos alternativos: En sistemas con firewalld, es preferible usar el módulo firewalld de Ansible en lugar de iptables directamente.

Servicio complejo "Flask"

playbook-docker-compose-localhost.yml:

# Examples use the django example at https://docs.docker.com/compose/django. Follow it to create the
# flask directory

- name: Run using a project directory
  #hosts: localhost
  hosts: ateca-profe
  gather_facts: false
  tasks:
    - name: Tear down existing services
      community.docker.docker_compose_v2:
        project_src: flask
        state: absent

    - name: Create and start services
      community.docker.docker_compose_v2:
        project_src: flask
      register: output

    - name: Show results
      ansible.builtin.debug:
        var: output

    - name: Run `docker compose up` again
      community.docker.docker_compose_v2:
        project_src: flask
      register: output

    - name: Show results
      ansible.builtin.debug:
        var: output

    - ansible.builtin.assert:
        that: not output.changed

    - name: Stop all services
      community.docker.docker_compose_v2:
        project_src: flask
        state: stopped
      register: output

    - name: Show results
      ansible.builtin.debug:
        var: output

    - name: Verify that web and db services are not running
      ansible.builtin.assert:
        that:
          - web_container.State != 'running'
          - db_container.State != 'running'
      vars:
        web_container: >-
          {{ output.containers | selectattr("Service", "equalto", "web") | first }}
        db_container: >-
          {{ output.containers | selectattr("Service", "equalto", "db") | first }}

    - name: Restart services
      community.docker.docker_compose_v2:
        project_src: flask
        state: restarted
      register: output

    - name: Show results
      ansible.builtin.debug:
        var: output

    - name: Verify that web and db services are running
      ansible.builtin.assert:
        that:
          - web_container.State == 'running'
          - db_container.State == 'running'
      vars:
        web_container: >-
          {{ output.containers | selectattr("Service", "equalto", "web") | first }}
        db_container: >-
          {{ output.containers | selectattr("Service", "equalto", "db") | first }}

Activar un servicio

En ocasiones podemos querer levantar un servicio concreto a nuestros alumnos, p.e. mysql por lo que con ésto podemos independizar dicho servicio según profesor.

playbook-docker-compose-prueba.yml:

- name: Run using a project directory
  #hosts: localhost
  hosts: ateca-profe
  gather_facts: false
  tasks:
  - name: copy Docker Compose files
    copy:
      src: files/item
      dest: /home/luis/yourproject
    loop:
    - compose.yml
    - compose.prod.yml

  # use files parameter to use multiple docker-compose.yml files
  # mind the _v2 suffix
  - name: deploy Docker Compose stack
    community.docker.docker_compose_v2:
      project_src: /home/luis/yourproject/item
      files:
      - compose.yml
      - compose.prod.yml

Crear SUDO:

Equipos Debian NO suelen venir con sudo por lo que, los que venimos de Ubuntu y derivadas nos resulta incómodo trabajar con su... playbook-vps-create-user-sudo.yml:

# Le falta crear .profile y .bashrc
- hosts: vps
  tasks:
    - name: Make sure we have a 'wheel' group
      ansible.builtin.group:
        name: wheel
        state: present

    - name: Allow 'wheel' group to have passwordless sudo
      lineinfile:
        dest: /etc/sudoers
        state: present
        regexp: "^%wheel"
        line: "%wheel ALL=(ALL) NOPASSWD: ALL"
        validate: "visudo -cf %s"

    - name: Add sudoers users to wheel group
      user: name=luis
        groups=wheel
        append=yes
        state=present
        createhome=yes

    - name: Set up authorized keys for the deployer user
      authorized_key: user=luis key="{{item}}"
      with_file:
        - /home/luis/.ssh/id_ed25519.pub

Docker

playbook-vps-docker.yml:

---
# Cambiar hosts y sistema Ubuntu/Debian/...
- name: Install Docker on Ubuntu
  hosts: vps
  #remote_user: garis  # Change remote user to your sudo user!
  become: true
  vars:
    arch_mapping:  # Map ansible architecture {{ ansible_architecture }} names to Docker's architecture names
      x86_64: amd64
      aarch64: arm64

  tasks:
    - name: Update and upgrade all packages to the latest version
      ansible.builtin.apt:
        update_cache: true
        upgrade: dist
        cache_valid_time: 3600

    - name: Install required packages
      ansible.builtin.apt:
        pkg:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
          - software-properties-common

    - name: Create directory for Docker's GPG key
      ansible.builtin.file:
        path: /etc/apt/keyrings
        state: directory
        mode: '0755'

    - name: Add Docker's official GPG key
      ansible.builtin.apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        keyring: /etc/apt/keyrings/docker.gpg
        state: present

    - name: Print architecture variables
      ansible.builtin.debug:
        msg: "Architecture: {{ ansible_architecture }}, Codename: {{ ansible_lsb.codename }}"

    - name: Add Docker repository
      ansible.builtin.apt_repository:
        repo: >-
          deb [arch={{ arch_mapping[ansible_architecture] | default(ansible_architecture) }}
          signed-by=/etc/apt/keyrings/docker.gpg]
          https://download.docker.com/linux/debian {{ ansible_lsb.codename }} stable
        filename: docker
        state: present

    - name: Install Docker and related packages
      ansible.builtin.apt:
        name: "{{ item }}"
        state: present
        update_cache: true
      loop:
        - docker-ce
        - docker-ce-cli
        - containerd.io
        - docker-buildx-plugin
        - docker-compose-plugin

    - name: Add Docker group
      ansible.builtin.group:
        name: docker
        state: present

    - name: Add user to Docker group
      ansible.builtin.user:
        name: "{{ ansible_user }}"
        groups: docker
        append: true

    - name: Enable and start Docker services
      ansible.builtin.systemd:
        name: "{{ item }}"
        enabled: true
        state: started
      loop:
        - docker.service
        - containerd.service

Windows

Connect to Windows (Managed Node)To manage your Windows 11 machine from Ansible, you will need to configure PowerShell Remoting on the Windows side.

  1. Open an Administrator PowerShell prompt on your Windows 11 machine.

  2. Run the initialization script to allow remote connections (en powershell) powershell Enable-PSRemoting -Force

  3. Set the WinRM service to automatically start (en powershell) powershell Set-Service -Name "WinRM" -StartupType Automatic Start-Service

⚠️ ADVERTENCIA: los playbooks contra equipos Windows pueden ser diferentes a los de GNU/Linux.

Bloquear conexiones externas

---
- name: Configure Windows Firewall to block all except local
  hosts: windows_servers
  tasks:
    # 1. Allow Loopback Traffic (Localhost)
    - name: Allow inbound loopback traffic
      community.windows.win_firewall_rule:
        name: "Allow Inbound Loopback"
        localport: any
        direction: in
        protocol: any
        remoteip: "127.0.0.1,::1"
        action: allow
        state: present
        enabled: yes

    - name: Allow outbound loopback traffic
      community.windows.win_firewall_rule:
        name: "Allow Outbound Loopback"
        remoteport: any
        direction: out
        protocol: any
        localip: "127.0.0.1,::1"
        action: allow
        state: present
        enabled: yes

    # 2. Allow Specific Local Management Ports (e.g., WinRM)
    # Ensure these are allowed before the global block rules
    - name: Allow WinRM Inbound from Management Subnet
      community.windows.win_firewall_rule:
        name: "Allow WinRM Inbound"
        localport: 5985
        direction: in
        protocol: tcp
        remoteip: "192.168.1.0/24" # Replace with your management subnet
        action: allow
        state: present
        enabled: yes

    # 3. Acceso a Moodle (178.255.108.43)
    # Ensure these are allowed before the global block rules
    - name: Allow moodle.educarex.es
      community.windows.win_firewall_rule:
        name: "Allow Moodle"
        direction: out
        protocol: tcp
        remoteip: "178.255.108.43/32" # Replace with your management subnet
        action: allow
        state: present
        enabled: yes


    # 4. Block All Other Inbound Traffic
    - name: Block all other inbound traffic
      community.windows.win_firewall_rule:
        name: "Block All Inbound"
        localport: any
        direction: in
        protocol: any
        remoteip: "any"
        action: block
        state: present
        enabled: yes

    # 5. Block All Other Outbound Traffic
    - name: Block all other outbound traffic
      community.windows.win_firewall_rule:
        name: "Block All Outbound"
        remoteport: any
        direction: out
        protocol: any
        localip: "any"
        action: block
        state: present
        enabled: yes   

Fuentes: