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.
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_taskses estático. Se procesa cuando Ansible analiza el playbook por primera vez. Esto es ideal para partes estructurales e incondicionales de su play.include_taskses 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.


