Los handlers en Ansible son tareas que se ejecutan cuando son notificadas por otras tareas. Se utilizan principalmente para llevar a cabo acciones que deben realizarse solo cuando ocurre un cambio en el sistema, como reiniciar servicios, recargar configuraciones o reiniciar aplicaciones. Los handlers ayudan a mantener la idempotencia y eficiencia de los playbooks, ya que solo se ejecutan cuando es necesario.
Un handler es una tarea especial que se activa mediante la directiva notify
de otra tarea. Los handlers suelen utilizarse para gestionar servicios y recursos dependientes, como reiniciar nginx
después de modificar su archivo de configuración. Si una tarea notifica a un handler y no hubo cambios en esa tarea, el handler no se ejecutará.
El flujo de trabajo de un handler es el siguiente:
1. Una tarea dentro del playbook realiza un cambio (por ejemplo, copia un archivo de configuración).
2. La tarea activa un handler utilizando la directiva notify
.
3. Si el cambio ocurre, Ansible ejecuta el handler al final de la ejecución del playbook para garantizar que todas las tareas se completen primero.
Los handlers se definen de manera similar a las tareas (tasks), pero dentro de una sección handlers
. Un ejemplo básico de un playbook con un handler:
- name: Configurar un servidor web
hosts: webservers
become: yes
tasks:
- name: Copiar archivo de configuración de Nginx
copy:
src: ./files/nginx.conf
dest: /etc/nginx/nginx.conf
notify: # Se activa el handler si esta tarea cambia algo
- Reiniciar Nginx
handlers:
- name: Reiniciar Nginx
service:
name: nginx
state: restarted
1. Tarea copy
: Copia un archivo de configuración de Nginx en el servidor de destino.
2. Directiva notify
: Notifica al handler llamado Reiniciar Nginx
si el archivo se copia (es decir, si hay un cambio).
3. Handler Reiniciar Nginx
: Reinicia el servicio de Nginx solo si el archivo nginx.conf
ha sido modificado.
Los handlers se definen en la sección handlers:
dentro de un playbook o en un archivo separado (handlers/main.yml
) dentro de un rol. La sintaxis es similar a las tareas estándar:
handlers:
- name: Reiniciar Apache
service:
name: apache2
state: restarted
Para activar un handler, se utiliza la directiva notify
dentro de la tarea correspondiente:
- name: Actualizar archivo de configuración de Apache
template:
src: apache.conf.j2
dest: /etc/apache2/apache2.conf
notify: Reiniciar Apache
En este caso, si el archivo apache2.conf
se actualiza, el handler Reiniciar Apache
será notificado y se ejecutará al final de la ejecución del playbook.
Puedes notificar a más de un handler desde la misma tarea:
- name: Actualizar configuración de la aplicación
copy:
src: app.conf
dest: /etc/app/app.conf
notify:
- Reiniciar Servicio de Aplicación
- Recargar Firewall
Aquí, la tarea copy
notifica a dos handlers: Reiniciar Servicio de Aplicación
y Recargar Firewall
.
Varios cambios en diferentes tareas pueden notificar al mismo handler. Si un handler es notificado varias veces, se ejecutará solo una vez:
- name: Modificar configuración de Nginx
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Reiniciar Nginx
- name: Agregar nueva regla al firewall
command: ufw allow 80/tcp
notify: Reiniciar Nginx
El handler Reiniciar Nginx
solo se ejecutará una vez, al final del playbook, independientemente de cuántas tareas lo hayan notificado.
- name: Configurar un servidor MySQL
hosts: dbservers
become: yes
tasks:
- name: Copiar archivo de configuración de MySQL
copy:
src: ./files/my.cnf
dest: /etc/mysql/my.cnf
notify: Recargar MySQL
handlers:
- name: Recargar MySQL
service:
name: mysql
state: reloaded
En este ejemplo, si my.cnf
se actualiza, el servicio de MySQL se recarga para aplicar la nueva configuración.
- name: Actualizar y configurar el servicio SSH
hosts: all
become: yes
tasks:
- name: Instalar o actualizar OpenSSH
apt:
name: openssh-server
state: latest
notify: Reiniciar SSH
handlers:
- name: Reiniciar SSH
service:
name: ssh
state: restarted
Si el paquete openssh-server
se actualiza, se reinicia el servicio SSH para aplicar la nueva versión.
Los handlers dentro de un rol se definen en el archivo handlers/main.yml
. Un ejemplo básico de un rol llamado apache
:
Estructura del rol:
apache/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
└── templates/
└── apache.conf.j2
Archivo tasks/main.yml
:
- name: Instalar Apache
apt:
name: apache2
state: present
- name: Copiar archivo de configuración
template:
src: apache.conf.j2
dest: /etc/apache2/apache2.conf
notify: Reiniciar Apache
Archivo handlers/main.yml
:
- name: Reiniciar Apache
service:
name: apache2
state: restarted
En este caso, el rol apache
reinicia el servicio de Apache solo si se modifica el archivo de configuración.
1. Usa nombres descriptivos para los handlers, como Reiniciar Apache
o Recargar Firewall
, en lugar de nombres genéricos.
2. Evita el uso innecesario de handlers para tareas que no requieren un reinicio o recarga.
3. Agrupa tareas similares en un solo handler para evitar reinicios múltiples de servicios.
4. Verifica la idempotencia de las tareas que notifican a handlers. Asegúrate de que las tareas no generen cambios innecesarios.
5. Usa listen
en lugar de name
en Ansible 2.7 y posteriores. listen
permite asociar varios handlers a un solo nombre, mejorando la organización:
handlers:
- listen: "restart_services"
name: Reiniciar Apache
service:
name: apache2
state: restarted
Para más detalles, consulta la documentación oficial de Ansible sobre Handlers en Ansible Handlers.
Jorge García
Fullstack developer