Introducción
En este laboratorio, aprenderá a controlar el flujo de ejecución de los playbooks de Ansible en un sistema Red Hat Enterprise Linux (RHEL). Comenzará escribiendo un playbook que utiliza estructuras de control fundamentales, incluyendo bucles para repetir tareas de manera eficiente y condicionales para ejecutar tareas solo cuando se cumplen criterios específicos. También implementará handlers para activar acciones, como reinicios de servicios, solo cuando ocurre un cambio, haciendo su automatización más inteligente y eficiente.
Basándose en estas habilidades fundamentales, explorará técnicas más avanzadas para gestionar la ejecución de playbooks. Esto incluye el uso de sentencias block y rescue para manejar fallos de tareas de manera elegante y el empleo de changed_when y failed_when para obtener un control detallado sobre el estado de las tareas. Para concluir el laboratorio, aplicará todos estos conceptos en un ejercicio práctico para desplegar un servidor web seguro, consolidando su capacidad para crear automatizaciones de Ansible robustas y confiables.
Escribir un Playbook con Bucles y Condicionales
En este paso, aprenderá dos conceptos fundamentales en Ansible para controlar la ejecución de tareas: bucles y condicionales. Los bucles le permiten repetir una tarea varias veces con diferentes valores, lo cual es muy eficiente para tareas como instalar múltiples paquetes o crear múltiples usuarios. Los condicionales, utilizando la palabra clave when, le permiten ejecutar una tarea solo cuando se cumplen criterios específicos, como que el sistema operativo sea de una versión particular o que un archivo ya exista.
Primero, asegúrese de que Ansible esté instalado en su VM de LabEx. Utilizaremos el gestor de paquetes DNF para esto.
sudo dnf install -y ansible-core
Debería ver una salida que indica que ansible-core y sus dependencias se están instalando.
...
Installed:
ansible-core-2.x.x-1.el9.x86_64
...
Complete!
Ahora, configuremos nuestro directorio de proyecto. Todo nuestro trabajo para este laboratorio estará dentro de un directorio dedicado para mantener las cosas organizadas.
cd ~/project
mkdir control-flow-lab
cd control-flow-lab
Un proyecto de Ansible necesita un archivo de inventario, que define los hosts que desea gestionar. Para este laboratorio, gestionaremos la máquina local, localhost.
Cree un archivo de inventario llamado inventory usando el editor nano:
nano inventory
Agregue la siguiente línea al archivo. Esto le dice a Ansible que ejecute el playbook en localhost y que se conecte directamente a él en lugar de usar SSH.
localhost ansible_connection=local
Guarde el archivo y salga de nano presionando Ctrl+X, luego Y, y Enter.
A continuación, crearemos nuestro primer playbook, playbook.yml, para demostrar un bucle. Este playbook instalará una lista de herramientas útiles de línea de comandos.
nano playbook.yml
Ingrese el siguiente contenido YAML en el editor. Este playbook define una tarea que utiliza el módulo ansible.builtin.dnf para instalar paquetes. La directiva become: yes le dice a Ansible que ejecute las tareas con privilegios de sudo, lo cual es necesario para instalar paquetes. La palabra clave loop proporciona una lista de nombres de paquetes. Ansible ejecutará esta tarea una vez por cada elemento de la lista, sustituyendo el marcador de posición {{ item }} con el nombre del paquete actual.
---
- name: Install common tools
hosts: localhost
become: yes
tasks:
- name: Install specified packages
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- git
- tree
- wget
Guarde y salga del editor. Ahora, ejecute el playbook usando el comando ansible-playbook, especificando su archivo de inventario con la bandera -i.
ansible-playbook -i inventory playbook.yml
La salida mostrará la ejecución del playbook. Ansible verificará cada paquete y lo instalará si aún no está presente. El PLAY RECAP al final resume los resultados.
PLAY [Install tools and run conditional tasks] *********************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Install specified packages] **********************************************
changed: [localhost] => (item=git)
changed: [localhost] => (item=tree)
changed: [localhost] => (item=wget)
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ahora, modifiquemos el playbook para incluir una tarea condicional. Agregaremos una tarea que imprima un mensaje, pero solo si el sistema operativo es Red Hat Enterprise Linux. Este es un caso de uso común para adaptar la automatización a entornos específicos.
Abra el archivo playbook.yml nuevamente:
nano playbook.yml
Agregue las siguientes tareas al final del archivo. La palabra clave when evalúa la expresión dada. ansible_facts['distribution'] es una variable que Ansible descubre automáticamente sobre el host gestionado. La primera tarea se ejecutará porque nuestro entorno es RHEL, y la segunda tarea se omitirá.
---
- name: Install tools and run conditional tasks
hosts: localhost
become: yes
tasks:
- name: Install specified packages
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- git
- tree
- wget
- name: Show message on Red Hat systems
ansible.builtin.debug:
msg: "This system is a Red Hat family distribution."
when: ansible_facts['distribution'] == "RedHat"
- name: Show message on other systems
ansible.builtin.debug:
msg: "This system is NOT a Red Hat family distribution."
when: ansible_facts['distribution'] != "RedHat"
Guarde y salga del editor. Ejecute el playbook actualizado:
ansible-playbook -i inventory playbook.yml
Observe la salida cuidadosamente. La tarea de instalación de paquetes probablemente informará ok para todos los elementos, ya que ya están instalados. Más importante aún, verá que se imprime el primer mensaje de depuración, mientras que el segundo se marca como skipping.
PLAY [Install tools and run conditional tasks] *********************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Install specified packages] **********************************************
ok: [localhost] => (item=git)
ok: [localhost] => (item=tree)
ok: [localhost] => (item=wget)
TASK [Show message on Red Hat systems] *****************************************
ok: [localhost] => {
"msg": "This system is a Red Hat family distribution."
}
TASK [Show message on other systems] *******************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Ha escrito y ejecutado con éxito un playbook de Ansible que utiliza tanto bucles para realizar acciones repetitivas como condicionales para controlar la ejecución de tareas basándose en los hechos del sistema.
Implementar Handlers para Disparar Reinicios de Servicio
En este paso, aprenderá sobre los handlers de Ansible. Los handlers son tareas especiales que solo se ejecutan cuando son "notificados" por otra tarea. Típicamente se utilizan para acciones que solo deben ocurrir cuando se ha realizado un cambio, como reiniciar un servicio después de que su archivo de configuración haya sido actualizado. Este enfoque es más eficiente que reiniciar un servicio en cada ejecución del playbook, ya que asegura que la acción solo se tome cuando sea necesario.
Crearemos un playbook que instale el servidor web Nginx, despliegue una página de inicio personalizada y utilice un handler para recargar Nginx solo cuando el contenido de la página de inicio cambie.
Primero, creemos un nuevo directorio para este ejercicio para mantener nuestro proyecto organizado.
cd ~/project
mkdir control-handlers-lab
cd control-handlers-lab
Como antes, necesitamos un archivo de inventario para indicarle a Ansible dónde ejecutar el playbook.
nano inventory
Agregue la siguiente línea para especificar la máquina local.
localhost ansible_connection=local
Guarde y salga del editor (Ctrl+X, Y, Enter).
A continuación, necesitamos un archivo que sirva como página de inicio de nuestro servidor web. Crearemos un directorio files para almacenarlo.
mkdir files
Ahora, cree un archivo simple index.html dentro del directorio files.
nano files/index.html
Agregue el siguiente contenido HTML:
<h1>Welcome to the Ansible Handler Lab!</h1>
Guarde y salga del editor.
Ahora, creará el playbook deploy_nginx.yml. Este playbook realizará tres acciones principales: instalar Nginx, copiar el archivo index.html y definir un handler para recargar Nginx.
nano deploy_nginx.yml
Ingrese el siguiente contenido. Preste mucha atención a la palabra clave notify en la tarea "Copy homepage" y a la sección handlers correspondiente al final. La directiva become: yes le dice a Ansible que ejecute las tareas con privilegios de sudo, lo cual es necesario para instalar paquetes y gestionar servicios.
---
- name: Deploy Nginx with a handler
hosts: localhost
become: yes
tasks:
- name: Ensure Nginx is installed
ansible.builtin.dnf:
name: nginx
state: present
- name: Start and enable Nginx service
ansible.builtin.systemd:
name: nginx
state: started
enabled: yes
- name: Copy homepage
ansible.builtin.copy:
src: files/index.html
dest: /usr/share/nginx/html/index.html
notify: reload nginx
handlers:
- name: reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
Guarde y salga del editor.
Ahora, ejecute el playbook por primera vez.
ansible-playbook -i inventory deploy_nginx.yml
Verá una salida que muestra que Nginx fue instalado (o ya estaba presente), el servicio Nginx se inició y habilitó, el archivo index.html se copió (estado changed), y lo que es importante, el handler fue notificado y ejecutado al final del play.
...
TASK [Copy homepage] ***********************************************************
changed: [localhost]
RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Puede verificar que el servidor web se está ejecutando y sirviendo su página personalizada usando curl.
curl http://localhost
La salida debe ser el contenido de su archivo index.html.
<h1>Welcome to the Ansible Handler Lab!</h1>
Ahora, ejecute exactamente el mismo playbook nuevamente sin realizar ningún cambio.
ansible-playbook -i inventory deploy_nginx.yml
Esta vez, observe la salida. La tarea "Copy homepage" informará ok en lugar de changed porque el archivo en el destino ya coincide con la fuente. La tarea "Start and enable Nginx service" también informará ok ya que el servicio ya se está ejecutando y habilitado. Debido a que ninguna tarea notificó al handler, el handler no se ejecutó.
...
TASK [Copy homepage] ***********************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Para ver el handler en acción nuevamente, modifiquemos el archivo fuente index.html.
nano files/index.html
Cambie el contenido a lo siguiente:
<h1>The Handler Ran Again!</h1>
Guarde y salga. Ahora, ejecute el playbook una vez más.
ansible-playbook -i inventory deploy_nginx.yml
Debido a que el archivo fuente cambió, la tarea "Copy homepage" informará nuevamente changed, lo que a su vez notifica y ejecuta el handler reload nginx.
...
TASK [Copy homepage] ***********************************************************
changed: [localhost]
RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Verifique el cambio con curl una última vez.
curl http://localhost
Debería ver el mensaje actualizado.
<h1>The Handler Ran Again!</h1>
Este ejercicio demuestra el poder y la eficiencia de los handlers para gestionar el estado del servicio en respuesta a cambios de configuración.
Gestionar Fallos de Tareas con Block y Rescue
En este paso, aprenderá cómo manejar errores de forma elegante en sus playbooks de Ansible. Por defecto, si alguna tarea falla, Ansible deja de ejecutar todo el playbook en ese host. Si bien este es un comportamiento predeterminado seguro, a veces necesita más control. Explorará dos métodos para el manejo de errores: la directiva simple ignore_errors y la estructura más potente block, rescue y always, que proporciona una forma de intentar tareas y definir acciones de recuperación si fallan.
Primero, creemos un nuevo directorio para este ejercicio.
cd ~/project
mkdir control-errors-lab
cd control-errors-lab
Cree el archivo inventory estándar para localhost.
nano inventory
Agregue el siguiente contenido:
localhost ansible_connection=local
Guarde y salga del editor (Ctrl+X, Y, Enter).
Ahora, creemos un playbook llamado playbook.yml que está diseñado para fallar. La primera tarea intentará instalar un paquete que no existe.
nano playbook.yml
Ingrese el siguiente contenido. Este playbook intenta instalar un paquete falso httpd-fake y luego un paquete real, mariadb-server.
---
- name: Demonstrate Task Failure
hosts: localhost
become: yes
tasks:
- name: Attempt to install a non-existent package
ansible.builtin.dnf:
name: httpd-fake
state: present
- name: Install MariaDB server
ansible.builtin.dnf:
name: mariadb-server
state: present
Guarde y salga del editor. Ahora, ejecute el playbook.
ansible-playbook -i inventory playbook.yml
Verá que la primera tarea falla con un mensaje de error porque no se puede encontrar el paquete httpd-fake. Crucialmente, Ansible se detendrá y la segunda tarea, "Install MariaDB server", no se ejecutará.
...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "No match for argument: httpd-fake", "rc": 1, "results": []}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Ahora, usemos block y rescue para manejar este fallo de manera más elegante. La palabra clave block agrupa un conjunto de tareas. Si alguna tarea dentro del block falla, Ansible omite el resto de las tareas en el block y ejecuta las tareas en la sección rescue. La sección always se ejecutará independientemente de si las secciones block o rescue tuvieron éxito o fallaron.
Modifique playbook.yml para usar esta estructura.
nano playbook.yml
Reemplace todo el contenido con lo siguiente. Aquí, intentamos instalar el paquete falso en el block. Cuando falla, la sección rescue se ejecutará, instalando mariadb-server como un paso de recuperación. La sección always imprimirá un mensaje al final.
---
- name: Handle Task Failure with Block and Rescue
hosts: localhost
become: yes
tasks:
- name: Attempt primary task, with recovery
block:
- name: Attempt to install a non-existent package
ansible.builtin.dnf:
name: httpd-fake
state: present
- name: This task will be skipped
ansible.builtin.debug:
msg: "This message will not appear because the previous task fails."
rescue:
- name: Install MariaDB server on failure
ansible.builtin.dnf:
name: mariadb-server
state: present
always:
- name: This always runs
ansible.builtin.debug:
msg: "The block has completed, either by success or rescue."
Guarde y salga. Ejecute el playbook nuevamente.
ansible-playbook -i inventory playbook.yml
Observe la salida. La primera tarea en el block falla como se esperaba. La segunda tarea en el block se omite. Ansible luego pasa a la sección rescue e instala mariadb-server con éxito. Finalmente, se ejecuta la sección always.
...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => ...
TASK [This task will be skipped] ***********************************************
skipping: [localhost]
RESCUE START *******************************************************************
TASK [Install MariaDB server on failure] ***************************************
changed: [localhost]
ALWAYS START *******************************************************************
TASK [This always runs] ********************************************************
ok: [localhost] => {
"msg": "The block has completed, either by success or rescue."
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=1 ignored=0
Ahora, veamos qué sucede cuando el block tiene éxito. Edite el playbook y corrija el nombre del paquete.
nano playbook.yml
Cambie httpd-fake a un paquete real, httpd.
## ... (rest of the playbook)
block:
- name: Attempt to install a valid package
ansible.builtin.dnf:
name: httpd ## Corrected from httpd-fake
state: present
- name: This task will now run
ansible.builtin.debug:
msg: "This message will now appear because the previous task succeeds."
## ... (rest of the playbook)
Guarde y salga. Ejecute el playbook una última vez.
ansible-playbook -i inventory playbook.yml
Esta vez, ambas tareas en el block tienen éxito. Debido a que el block se completó sin errores, la sección rescue se omite por completo. La sección always todavía se ejecuta, como su nombre indica.
...
TASK [Attempt to install a valid package] **************************************
changed: [localhost]
TASK [This task will now run] **************************************************
ok: [localhost] => {
"msg": "This message will now appear because the previous task succeeds."
}
RESCUE START *******************************************************************
skipping rescue
ALWAYS START *******************************************************************
TASK [This always runs] ********************************************************
ok: [localhost] => {
"msg": "The block has completed, either by success or rescue."
}
PLAY RECAP *********************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ahora ha utilizado con éxito la estructura block/rescue/always para crear un playbook robusto que puede manejar fallos y realizar acciones de recuperación.
Controlar el Estado de las Tareas con changed_when y failed_when
En este paso, obtendrá un control más preciso sobre cómo Ansible interpreta el resultado de sus tareas. Aprenderá sobre dos directivas potentes: changed_when y failed_when.
changed_when: Por defecto, módulos comoansible.builtin.commandoansible.builtin.shellcasi siempre informan un estado de "cambiado", incluso si el comando que ejecutaron no alteró el sistema.changed_whenle permite definir una condición personalizada que determina si una tarea debe informarse como "cambiada". Esto es crucial para escribir playbooks idempotentes y para disparar handlers de manera precisa.failed_when: A veces, un comando puede salir con un código de estado distinto de cero (que Ansible considera un fallo) incluso cuando el resultado es aceptable.failed_whenle permite anular las condiciones de fallo predeterminadas, permitiendo que su playbook continúe basándose en criterios más inteligentes, como la salida del comando o un código de salida específico.
Comencemos configurando un nuevo directorio de proyecto.
cd ~/project
mkdir control-state-lab
cd control-state-lab
Cree el archivo inventory estándar para localhost.
nano inventory
Agregue el siguiente contenido:
localhost ansible_connection=local
Guarde y salga del editor (Ctrl+X, Y, Enter).
Usando changed_when
Primero, veamos cómo se comporta una tarea de comando por defecto. Crearemos un playbook que ejecute el comando date. Este comando simplemente imprime la fecha y no cambia el sistema, pero el módulo command lo informará como un cambio.
Cree un nuevo playbook llamado playbook.yml.
nano playbook.yml
Ingrese el siguiente contenido:
---
- name: Control Task State
hosts: localhost
tasks:
- name: Check local time (default behavior)
ansible.builtin.command: date
Guarde y salga. Ahora, ejecute el playbook.
ansible-playbook -i inventory playbook.yml
Observe en la salida que la tarea se informa como changed=1, a pesar de que nada en el sistema fue modificado.
...
TASK [Check local time (default behavior)] *************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 ...
Ahora, usemos changed_when para decirle a Ansible que este comando nunca cambia el sistema. Modifique playbook.yml.
nano playbook.yml
Agregue changed_when: false a la tarea.
---
- name: Control Task State
hosts: localhost
tasks:
- name: Check local time (with changed_when)
ansible.builtin.command: date
changed_when: false
Guarde y salga. Ejecute el playbook nuevamente.
ansible-playbook -i inventory playbook.yml
Esta vez, la tarea informa ok y el resumen final muestra changed=0. Ha anulado con éxito el comportamiento predeterminado.
...
TASK [Check local time (with changed_when)] ************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 ...
Usando failed_when
A continuación, exploremos failed_when. Crearemos una tarea que verifique la existencia de un archivo que no está allí. El comando "fallará" por defecto.
Primero, cree un archivo ficticio para buscar dentro.
echo "System is running" > status.txt
Ahora, modifique playbook.yml para buscar la palabra "ERROR" en este archivo. El comando grep saldrá con un código de estado de 1 porque la palabra no se encuentra, lo que Ansible interpreta como un fallo.
nano playbook.yml
Reemplace el contenido con lo siguiente:
---
- name: Control Task State
hosts: localhost
tasks:
- name: Check for ERROR in status file (will fail)
ansible.builtin.command: grep ERROR status.txt
Guarde y salga. Ejecute el playbook.
ansible-playbook -i inventory playbook.yml
Como se esperaba, la ejecución del playbook se detiene con un mensaje FAILED!.
...
TASK [Check for ERROR in status file (will fail)] ******************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["grep", "ERROR", "status.txt"], "delta": "...", "end": "...", "msg": "non-zero return code", "rc": 1, ...}
...
Esto no es lo que queremos. La ausencia de "ERROR" es una condición de éxito para nosotros. Podemos usar failed_when para redefinir qué constituye un fallo. Le diremos a Ansible que solo falle si el código de retorno del comando es mayor que 1. Un código de retorno de 1 (patrón no encontrado) ahora se considerará un éxito. También necesitamos register el resultado de la tarea para inspeccionar su código de retorno (rc).
Modifique playbook.yml una última vez.
nano playbook.yml
Actualice el playbook con register y failed_when.
---
- name: Control Task State
hosts: localhost
tasks:
- name: Check for ERROR in status file (with failed_when)
ansible.builtin.command: grep ERROR status.txt
register: grep_result
failed_when: grep_result.rc > 1
changed_when: false
También agregamos changed_when: false porque grep es una operación de solo lectura y no cambia el sistema.
Guarde y salga. Ejecute el playbook final.
ansible-playbook -i inventory playbook.yml
¡Éxito! La tarea ahora informa ok porque su código de retorno fue 1, lo que no cumple nuestra nueva condición de fallo (rc > 1). El playbook se completa con éxito.
...
TASK [Check for ERROR in status file (with failed_when)] ***********************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 ...
Ahora ha aprendido a usar changed_when y failed_when para definir con precisión los estados de éxito, cambio y fallo de sus tareas, lo que lleva a una automatización más robusta e inteligente.
Desplegar un Servidor Web Seguro Usando Control de Tareas
En este paso final, combinará todos los conceptos que ha aprendido (bucles, condicionales, handlers y manejo de errores) para construir un playbook único y robusto. El objetivo es desplegar el servidor web Apache (httpd), asegurarlo con mod_ssl, generar un certificado SSL autofirmado y desplegar una página de inicio personalizada. Este ejercicio práctico imita una tarea de automatización del mundo real.
Primero, configuremos el directorio del proyecto para este ejercicio culminante.
cd ~/project
mkdir control-review-lab
cd control-review-lab
Como siempre, cree un archivo inventory para definir su host de destino.
nano inventory
Agregue la entrada localhost:
localhost ansible_connection=local
Guarde y salga del editor (Ctrl+X, Y, Enter).
A continuación, necesitamos un directorio para almacenar los archivos que desplegará nuestro playbook.
mkdir files
Ahora, cree una página de inicio personalizada, index.html, dentro del directorio files.
nano files/index.html
Agregue el siguiente contenido HTML. Esta será la página servida por nuestro servidor web seguro.
<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>
Guarde y salga del editor.
Ahora es el momento de construir el playbook principal, deploy_secure_web.yml. Este playbook será más complejo que los anteriores, integrando múltiples conceptos.
nano deploy_secure_web.yml
Ingrese el siguiente playbook completo. Lea los comentarios dentro del código para comprender cómo cada parte contribuye al objetivo general.
---
- name: Deploy a Secure Apache Web Server
hosts: localhost
become: yes
vars:
packages_to_install:
- httpd
- mod_ssl
ssl_cert_path: /etc/pki/tls/certs/localhost.crt
ssl_key_path: /etc/pki/tls/private/localhost.key
tasks:
- name: Stop nginx to free port 80
ansible.builtin.systemd:
name: nginx
state: stopped
ignore_errors: yes
- name: Install httpd and mod_ssl packages
ansible.builtin.dnf:
name: "{{ packages_to_install }}"
state: present
- name: Generate self-signed SSL certificate if it does not exist
ansible.builtin.command: >
openssl req -new -nodes -x509
-subj "/C=US/ST=None/L=None/O=LabEx/CN=localhost"
-keyout {{ ssl_key_path }}
-out {{ ssl_cert_path }}
args:
creates: "{{ ssl_cert_path }}"
- name: Deploy custom index.html
ansible.builtin.copy:
src: files/index.html
dest: /var/www/html/index.html
notify: restart httpd
- name: Start and enable httpd service
ansible.builtin.systemd:
name: httpd
state: started
enabled: yes
handlers:
- name: restart httpd
ansible.builtin.systemd:
name: httpd
state: restarted
Analicemos lo que hace este playbook:
vars: Define variables para los paquetes a instalar y las rutas para el certificado y la clave SSL, lo que hace que el playbook sea más fácil de leer y mantener.- Tarea de Detener Nginx: Detiene el servicio nginx del paso de laboratorio anterior para liberar el puerto 80 para Apache. Utiliza
ignore_errors: yesen caso de que nginx no se esté ejecutando. - Tarea de Instalación: Utiliza la variable
packages_to_installpara instalar tantohttpdcomomod_ssl. - Tarea de Generación de Certificado: Esta es una tarea clave. Utiliza el comando
opensslpara crear un certificado autofirmado. La directivaargs: { creates: ... }hace que esta tarea sea idempotente. El comando solo se ejecutará si el archivo del certificado (/etc/pki/tls/certs/localhost.crt) aún no existe. - Tarea de Despliegue de Página de Inicio: Copia su
index.htmlpersonalizado. Crucialmente, utilizanotify: restart httpdpara activar el handler si el archivo se modifica. - Tarea de Inicio de Servicio: Utiliza el módulo systemd para iniciar y habilitar el servicio httpd después de que toda la configuración esté en su lugar, asegurando que se inicie al arrancar.
- Handler: El handler
restart httpdrealiza un reinicio de Apache usando systemd, que solo se activa cuando cambia un archivo de configuración o contenido.
Guarde y salga del editor. Ahora, ejecute su playbook completo.
ansible-playbook -i inventory deploy_secure_web.yml
En la primera ejecución, debería ver varias tareas informando changed, incluyendo la detención de nginx, la instalación de paquetes, la generación de certificados, la copia de archivos y el inicio del servicio.
...
TASK [Start and enable httpd service] ******************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=5 unreachable=0 failed=0 ...
Finalmente, verifique que su servidor web seguro esté funcionando. Primero pruebe la versión HTTP, luego la versión HTTPS con la bandera -k para ignorar las advertencias sobre el certificado autofirmado.
curl http://localhost
Debería ver el contenido de su página de inicio personalizada.
<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>
También puede probar la versión HTTPS:
curl -k https://localhost
Si vuelve a ejecutar el playbook, verá que ninguna tarea informa changed, y el handler no se ejecuta, lo que demuestra que su playbook es idempotente.
¡Felicitaciones! Ha creado con éxito un playbook de Ansible práctico y robusto que combina bucles, variables, ejecución de comandos idempotente y handlers para desplegar una aplicación segura.
Resumen
En este laboratorio, aprendió a controlar la ejecución de playbooks de Ansible en un sistema RHEL. Comenzó configurando un entorno de proyecto básico, incluida la instalación de Ansible y la creación de un archivo de inventario. Luego exploró estructuras fundamentales de flujo de control, utilizando bucles para repetir eficientemente tareas con diferentes entradas y condicionales con la declaración when para ejecutar tareas solo bajo circunstancias específicas. Basándose en esto, implementó handlers para crear automatizaciones receptivas, como activar un reinicio de servicio solo cuando su archivo de configuración ha sido modificado.
El laboratorio también cubrió técnicas avanzadas para gestionar la ejecución de playbooks. Aprendió a crear playbooks más robustos utilizando cláusulas block y rescue para manejar fallos de tareas de manera elegante. Además, obtuvo un control detallado sobre los resultados de las tareas utilizando changed_when y failed_when para definir condiciones personalizadas de éxito y fallo. Finalmente, consolidó todas estas habilidades aplicándolas a un escenario práctico: el despliegue de un servidor web seguro, demostrando cómo combinar eficazmente bucles, condicionales, handlers y manejo de errores en un flujo de trabajo de automatización del mundo real.



