Estructuración de Playbooks Complejos de Ansible en RHEL

AnsibleBeginner
Practicar Ahora

Introducción

En este laboratorio, aprenderá técnicas esenciales para estructurar playbooks complejos de Ansible en RHEL para crear automatizaciones más manejables, escalables y reutilizables. Progresará desde conceptos fundamentales hasta estrategias de organización avanzadas, centrándose en cómo controlar con precisión en qué hosts se ejecuta su automatización y cómo dividir playbooks grandes en componentes lógicos y modulares.

Comenzará dominando la selección de hosts, utilizando nombres de grupo básicos, comodines, exclusiones y operadores lógicos para dirigirse a nodos específicos dentro de su inventario. A continuación, explorará la modularización refactorizando tareas en archivos separados utilizando include_tasks y import_tasks. Finalmente, aprenderá a componer un flujo de trabajo completo y de múltiples playbooks con import_playbook, culminando en la ejecución y verificación de su proyecto Ansible completamente estructurado y modularizado.

Este es un Guided Lab, que proporciona instrucciones paso a paso para ayudarte a aprender y practicar. Sigue las instrucciones cuidadosamente para completar cada paso y obtener experiencia práctica. Los datos históricos muestran que este es un laboratorio de nivel principiante con una tasa de finalización del 94%. Ha recibido una tasa de reseñas positivas del 100% por parte de los estudiantes.

Selección de Hosts con Patrones Básicos y Comodines

En este paso, aprenderá los fundamentos para dirigir hosts específicos en su automatización de Ansible. El núcleo de esto es el archivo de inventario de Ansible, que enumera los servidores que administra, y la directiva hosts dentro de un playbook, que especifica contra cuáles de esos servidores se deben ejecutar un conjunto de tareas. Comenzaremos instalando Ansible, creando un inventario y un playbook básicos, y luego exploraremos cómo seleccionar hosts utilizando nombres de grupo y patrones de comodín.

Primero, asegúrese de que Ansible esté instalado en su entorno. Ansible no está instalado por defecto, por lo que necesita instalarlo usando el gestor de paquetes DNF. El paquete ansible-core proporciona las herramientas esenciales de línea de comandos de Ansible, incluido ansible-playbook. Ejecute el siguiente comando:

sudo dnf install -y ansible-core

Debería ver una salida similar a esta:

...
Installed:
  ansible-core-2.16.x-x.el9.x86_64
  ...
Complete!

A continuación, creemos un directorio dedicado para este ejercicio para mantener nuestros archivos organizados. Todas las acciones subsiguientes en este paso se llevarán a cabo dentro de este nuevo directorio:

mkdir -p ~/project/ansible_patterns
cd ~/project/ansible_patterns

Ahora, cree un archivo de inventario. Un inventario es un archivo de texto que define los hosts y grupos de hosts sobre los cuales operan los comandos, módulos y tareas de Ansible. Utilizaremos el formato INI por su simplicidad.

Use el editor nano para crear un archivo llamado inventory:

nano inventory

Agregue el siguiente contenido al archivo inventory. Esto define dos grupos, webservers y dbservers, cada uno con dos hosts:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

Guarde el archivo y salga de nano presionando Ctrl+X, luego Y, y Enter.

Con nuestro inventario listo, creemos un playbook simple. Este playbook utilizará el módulo ansible.builtin.debug para imprimir un mensaje, confirmando en qué host se está ejecutando la tarea. Esta es una excelente manera de probar patrones de hosts sin realizar ningún cambio real en el sistema.

Cree un nuevo archivo llamado playbook.yml:

nano playbook.yml

Agregue el siguiente contenido YAML. Inicialmente, se dirige a todos los hosts del grupo webservers:

---
- name: Test Host Patterns
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Guarde y salga del editor. Ahora, ejecute el playbook usando ansible-playbook. La bandera -i especifica nuestro archivo de inventario personalizado:

ansible-playbook playbook.yml -i inventory

La salida debería verse así:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Como puede ver, solo se dirigieron los hosts del grupo webservers. Ahora, modifiquemos el playbook para usar un comodín (*). Los comodines permiten una coincidencia de patrones más flexible.

Edite playbook.yml y cambie la línea hosts a hosts: "*.lab.net". Recuerde encerrar los patrones que contienen comodines entre comillas:

---
- name: Test Host Patterns
  hosts: "*.lab.net"
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Ejecute el playbook nuevamente:

ansible-playbook playbook.yml -i inventory

Debería ver una salida similar a esta:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Esta vez, la ejecución se realizó solo en los hosts cuyos nombres terminan en .lab.net. Finalmente, usemos la palabra clave especial all para dirigirnos a todos los hosts definidos en el inventario.

Edite playbook.yml una última vez y cambie la línea hosts a hosts: all:

---
- name: Test Host Patterns
  hosts: all
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Ejecute el playbook para ver el resultado:

ansible-playbook playbook.yml -i inventory

La salida mostrará que se están dirigiendo todos los hosts:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

El playbook ahora se ejecuta en los cuatro hosts de su inventario, lo que demuestra el poder del patrón all.

Refinar la Selección de Hosts con Exclusiones y Operadores Lógicos

En este paso, avanzará en sus habilidades de selección de hosts aprendiendo a usar exclusiones y operadores lógicos. Estas características permiten una segmentación muy específica, lo cual es esencial al administrar entornos complejos. Aprenderá a excluir hosts usando el operador ! (NOT) y a combinar grupos usando el operador & (AND). Continuaremos trabajando con los archivos inventory y playbook.yml del paso anterior.

Primero, asegúrese de estar en el directorio de trabajo correcto:

cd ~/project/ansible_patterns

Para demostrar eficazmente los operadores lógicos, necesitamos crear cierta superposición en nuestros grupos de hosts. Editemos el archivo inventory para agregar un nuevo grupo llamado production que contenga un servidor web y un servidor de base de datos:

nano inventory

Agregue el grupo [production] y sus miembros al final del archivo:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

[production]
web1.example.com
db1.lab.net

Guarde el archivo y salga de nano presionando Ctrl+X, luego Y, y Enter.

Ahora, practiquemos la exclusión. El operador ! (que significa NOT) le permite excluir un host o grupo de una selección. Modifique su playbook.yml para dirigirse a todos los hosts excepto aquellos en el grupo dbservers:

nano playbook.yml

Actualice la línea hosts como se muestra a continuación. El patrón all,!dbservers selecciona todos los hosts y luego elimina cualquiera que esté en el grupo dbservers:

---
- name: Test Host Patterns
  hosts: all,!dbservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Guarde y salga del editor, luego ejecute el playbook:

ansible-playbook playbook.yml -i inventory

Debería ver que solo se dirigen los servidores web:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Como se esperaba, solo se dirigieron los hosts del grupo webservers.

A continuación, exploremos el operador lógico AND. El operador & selecciona solo los hosts que existen en ambos grupos especificados (una intersección). Modifiquemos el playbook para dirigirnos a los hosts que están en el grupo webservers Y también en el grupo production:

nano playbook.yml

Cambie la línea hosts a webservers,&production:

---
- name: Test Host Patterns
  hosts: webservers,&production
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Guarde y ejecute el playbook:

ansible-playbook playbook.yml -i inventory

Esta vez, solo se dirigirá la intersección de ambos grupos:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La salida muestra correctamente que solo se dirigió a web1.example.com, ya que es el único host que es miembro de los grupos webservers y production. Estos operadores le brindan un control preciso sobre a qué hosts afecta su automatización.

Modularizar un Play con include_tasks y import_tasks

En este paso, aprenderá a estructurar proyectos de Ansible más grandes dividiéndolos en archivos más pequeños y reutilizables. A medida que los playbooks crecen, mantener todas las tareas en un solo archivo se vuelve difícil de gestionar. Ansible proporciona dos directivas principales para esto: import_tasks y include_tasks. Ambas le permiten incorporar tareas de otro archivo.

  • import_tasks es estático. Se procesa cuando Ansible analiza el playbook por primera vez. Esto es ideal para partes estructurales e incondicionales de su play.
  • include_tasks es dinámico. Se procesa durante la ejecución del play. Esto lo hace adecuado para su uso con bucles y condicionales.

Ahora refactorizaremos nuestro playbook para usar ambos. Primero, asegúrese de estar en el directorio del proyecto:

cd ~/project/ansible_patterns

Antes de continuar, actualicemos el archivo de inventario para que los hosts apunten a localhost para este entorno de laboratorio. Esto permitirá que el playbook se ejecute correctamente:

nano inventory

Reemplace el contenido con la siguiente configuración que mapea los hosts de ejemplo a localhost:

[webservers]
web1.example.com ansible_host=localhost ansible_connection=local
web2.example.com ansible_host=localhost ansible_connection=local

[dbservers]
db1.lab.net ansible_host=localhost ansible_connection=local
db2.lab.net ansible_host=localhost ansible_connection=local

Guarde y salga del editor. Esta configuración utiliza ansible_host=localhost para redirigir las conexiones a la máquina local y ansible_connection=local para evitar intentos de conexión SSH.

Una práctica común es almacenar archivos de tareas reutilizables en un subdirectorio dedicado. Creemos uno llamado tasks:

mkdir tasks

Ahora, creemos un archivo para tareas de configuración comunes que puedan aplicarse a muchos servidores. Colocaremos aquí una tarea para instalar el paquete del servidor web httpd:

nano tasks/web_setup.yml

Agregue el siguiente contenido. Tenga en cuenta que este archivo es solo una lista de tareas; no contiene una estructura de play completa (como hosts: o name:):

- name: Install the httpd package
  ansible.builtin.dnf:
    name: httpd
    state: present
  become: true

Guarde y salga de nano. A continuación, cree un segundo archivo de tareas para un paso de verificación simple:

nano tasks/verify_config.yml

Agregue la siguiente tarea de depuración a este archivo:

- name: Display a verification message
  ansible.builtin.debug:
    msg: "Configuration tasks applied to {{ inventory_hostname }}"

Guarde y salga del editor. Ahora, modifiquemos el playbook.yml principal para usar estos nuevos archivos de tareas. Usaremos import_tasks para la configuración estática y include_tasks para el mensaje de verificación dinámico:

nano playbook.yml

Reemplace todo el contenido de playbook.yml con lo siguiente. Este playbook ahora se dirige al grupo webservers y utiliza los archivos de tareas modulares:

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: tasks/verify_config.yml

Guarde el archivo y ejecute el playbook:

ansible-playbook playbook.yml -i inventory

Debería ver las tareas modulares ejecutándose:

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
changed: [web1.example.com]
changed: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Observe cómo la salida indica claramente cuándo se importan e incluyen tareas de sus respectivos archivos. Este enfoque modular hace que su automatización sea más limpia y fácil de mantener.

Componer un Flujo de Trabajo con import_playbook

En este paso, aprenderá a orquestar playbooks completos para formar un flujo de trabajo complejo utilizando import_playbook. Mientras que import_tasks y include_tasks son para reutilizar listas de tareas dentro de un solo play, import_playbook opera a un nivel superior. Le permite crear un playbook maestro que ejecuta otros playbooks autocontenidos en un orden específico. Esta es la forma estándar de gestionar la automatización a gran escala, como el aprovisionamiento de una pila de aplicaciones completa.

Primero, asegurémonos de estar en el directorio correcto y organicemos nuestro proyecto para esta nueva estructura:

cd ~/project/ansible_patterns

Es una buena práctica almacenar playbooks individuales y de componentes en un subdirectorio dedicado. Creemos un directorio llamado playbooks:

mkdir playbooks

Ahora, mueva el playbook que creamos en el último paso, que configura los servidores web, a este nuevo directorio. Renombrarlo para que sea más descriptivo también es una buena idea:

mv playbook.yml playbooks/web_configure.yml

Sin embargo, dado que movimos el playbook a un subdirectorio, necesitamos actualizar las rutas relativas a los archivos de tareas. Los archivos de tareas todavía están en el directorio tasks/ relativo al directorio principal del proyecto, por lo que necesitamos ajustar las rutas:

nano playbooks/web_configure.yml

Actualice las rutas en el playbook para usar ../tasks/ en lugar de tasks/:

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: ../tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: ../tasks/verify_config.yml

Guarde y salga del editor.

Probemos el playbook corregido para asegurarnos de que las rutas funcionen correctamente:

ansible-playbook playbooks/web_configure.yml -i inventory

Debería ver que el playbook se ejecuta correctamente con las rutas corregidas.

A continuación, cree un playbook nuevo y separado para configurar sus servidores de bases de datos. Este playbook se dirigirá al grupo dbservers e instalará el paquete mariadb:

nano playbooks/db_setup.yml

Agregue el siguiente contenido al archivo. Este es un play completo e independiente:

---
- name: Configure Database Servers
  hosts: dbservers
  gather_facts: false
  tasks:
    - name: Install mariadb package
      ansible.builtin.dnf:
        name: mariadb
        state: present
      become: true

    - name: Display a confirmation message
      ansible.builtin.debug:
        msg: "Database server {{ inventory_hostname }} configured."

Guarde y salga del editor. Ahora tiene dos playbooks de componentes: uno para servidores web y otro para servidores de bases de datos.

Finalmente, cree un playbook "principal" de nivel superior. Este archivo no contendrá hosts ni tareas en sí mismo. Su único trabajo es importar los otros playbooks en el orden correcto para definir el flujo de trabajo general:

nano main.yml

Agregue el siguiente contenido. Esto crea un flujo de trabajo que primero configura los servidores web y luego configura los servidores de bases de datos:

---
- name: Import the web server configuration play
  import_playbook: playbooks/web_configure.yml

- name: Import the database server configuration play
  import_playbook: playbooks/db_setup.yml

Guarde y salga de nano. Ahora está listo para ejecutar todo su flujo de trabajo ejecutando el playbook main.yml:

ansible-playbook main.yml -i inventory

La salida mostrará ambos playbooks ejecutándose en secuencia:

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/playbooks/../tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
ok: [web1.example.com]
ok: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/playbooks/../tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY [Configure Database Servers] **********************************************

TASK [Install mariadb package] *************************************************
changed: [db1.lab.net]
changed: [db2.lab.net]

TASK [Display a confirmation message] ******************************************
ok: [db1.lab.net] => {
    "msg": "Database server db1.lab.net configured."
}
ok: [db2.lab.net] => {
    "msg": "Database server db2.lab.net configured."
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La salida muestra claramente dos plays separados que se ejecutan en secuencia, lo que demuestra cómo import_playbook compone eficazmente un flujo de trabajo más grande a partir de partes más pequeñas y manejables.

Ejecutar y Verificar el Playbook Modular Completo

En este paso final, ejecutará el flujo de trabajo modular completo que ha construido y, lo que es más importante, aprenderá a verificar que la automatización ha logrado el estado deseado en los sistemas de destino. Una ejecución exitosa del playbook es buena, pero confirmar el resultado es esencial para una automatización confiable.

Primero, asegúrese de estar en el directorio principal del proyecto:

cd ~/project/ansible_patterns

Antes de ejecutar el playbook final, visualicemos la estructura completa del proyecto que ha creado. El comando tree es excelente para esto. Si no está instalado, puede agregarlo con dnf:

sudo dnf install -y tree
tree .

Debería ver una estructura como esta:

.
├── inventory
├── main.yml
├── playbooks
│   ├── db_setup.yml
│   └── web_configure.yml
└── tasks
    ├── verify_config.yml
    └── web_setup.yml

2 directories, 6 files

Esta estructura, con un punto de entrada principal (main.yml), archivos de playbook separados y archivos de tareas reutilizables, es una forma escalable y mantenible de gestionar proyectos de Ansible.

Ahora, ejecute todo el flujo de trabajo ejecutando su playbook de nivel superior main.yml:

ansible-playbook main.yml -i inventory

Después de que el playbook se complete con éxito, el siguiente paso crucial es la verificación. Necesita confirmar que el sistema está en el estado que pretendía. Nuestro playbook fue diseñado para instalar el paquete httpd en los servidores web y el paquete mariadb en los servidores de bases de datos. Dado que todas las tareas en este laboratorio se ejecutan en su máquina local, podemos verificar su instalación directamente usando el comando rpm.

Primero, verifique si el paquete httpd se instaló como parte de la configuración del servidor web:

rpm -q httpd

Debería ver una salida que confirme que el paquete está instalado:

httpd-2.4.xx-x.el9.x86_64

A continuación, verifique la instalación del paquete mariadb de la configuración del servidor de bases de datos:

rpm -q mariadb

De manera similar, debería ver una confirmación de que mariadb está instalado:

mariadb-10.5.xx-x.el9.x86_64

Ver los nombres de los paquetes en la salida confirma que su playbook de Ansible configuró correctamente el sistema según lo previsto. Ahora ha construido, ejecutado y verificado con éxito un proyecto modular de Ansible de principio a fin.

Resumen

En este laboratorio, aprendió técnicas esenciales para estructurar playbooks complejos de Ansible en RHEL. Comenzó con los fundamentos de la selección de hosts, utilizando nombres de grupos básicos, comodines, exclusiones y operadores lógicos para apuntar con precisión a los nodos definidos en un archivo de inventario. Luego, el enfoque se centró en la modularización, donde practicó la división de plays grandes en componentes más manejables y reutilizables utilizando tanto include_tasks para la inclusión dinámica como import_tasks para la inclusión estática.

Basándose en estas habilidades, aprendió a componer un flujo de trabajo completo y de múltiples etapas vinculando playbooks individuales con import_playbook. El proceso práctico implicó la instalación de Ansible, la creación de una estructura de proyecto y la refactorización progresiva de un playbook simple en una estructura sofisticada de múltiples archivos. El laboratorio culminó con la ejecución del playbook compuesto final y la verificación de que todo el flujo de trabajo automatizado se ejecutó con éxito contra los hosts correctamente dirigidos, demostrando un enfoque organizado y escalable para la automatización.