Aller au contenu
Infrastructure as Code medium

Handlers Ansible : notify, listen, flush_handlers et restart-on-config-change

65 min de lecture

Logo Ansible

Un handler est une tâche réactive qui ne s'exécute que si une autre tâche l'a notifiée. C'est le mécanisme central du pattern restart-on-config-change : vous modifiez /etc/nginx/nginx.conf, et nginx est rechargé uniquement si la configuration a vraiment changé. Sans handlers, vous redémarreriez nginx à chaque exécution du playbook, ce qui casse l'idempotence et provoque des coupures de service inutiles.

Cette page couvre tout ce qu'il faut savoir : déclenchement via notify:, regroupement via listen:, exécution forcée avec meta: flush_handlers, ordre d'exécution exact dans un play, et les pièges classiques (handler qui ne se déclenche pas, double notification, ordre inversé).

  • Le concept d'un handler comme tâche réactive (pas exécutée par défaut) ;
  • Le déclenchement via notify: (un seul ou plusieurs handlers) ;
  • Le regroupement via listen: (plusieurs handlers réagissent à un même topic) ;
  • L'ordre d'exécution : à la fin de chaque section (pre_tasks, tasks, post_tasks) ;
  • meta: flush_handlers pour forcer un déclenchement immédiat ;
  • Le pattern restart-on-config-change appliqué à nginx, sshd, postgresql.

Cas typique : vous gérez /etc/nginx/nginx.conf et voulez que nginx recharge sa config uniquement si le fichier a changé. Sans handler :

# ❌ Anti-pattern : restart à chaque run, même si rien n'a changé
- name: Mettre à jour nginx.conf
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: Redémarrer nginx
ansible.builtin.systemd:
name: nginx
state: restarted # ← exécuté CHAQUE FOIS

Avec un handler, le redémarrage n'a lieu que si le fichier change :

# ✅ Pattern restart-on-config-change avec handler
tasks:
- name: Mettre à jour nginx.conf
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Recharger nginx # ← notifie le handler
handlers:
- name: Recharger nginx
ansible.builtin.systemd:
name: nginx
state: reloaded

Au second run, si le fichier n'a pas changé, le handler n'est pas déclenché et changed=0. C'est l'idempotence appliquée aux services.

Une tâche notifie un handler par son name: exact :

- name: Mettre à jour la config
ansible.builtin.copy:
src: app.conf
dest: /etc/app/app.conf
mode: "0644"
notify: Restart app # nom EXACT du handler

Plusieurs handlers possibles :

- name: Mettre à jour la config
ansible.builtin.copy:
src: app.conf
dest: /etc/app/app.conf
mode: "0644"
notify:
- Restart app
- Notifier Slack
- Vider le cache

Règle d'or : un handler n'est notifié que si la tâche est en changed. Si la tâche est ok (rien à faire), le handler ne se déclenche pas. C'est exactement ce qu'on veut.

listen: permet de découpler la notification du nom du handler. Plusieurs handlers peuvent écouter le même topic :

tasks:
- name: Mettre à jour la config nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: "config nginx changée"
- name: Mettre à jour le vhost
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/conf.d/site.conf
notify: "config nginx changée"
handlers:
- name: Recharger nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
listen: "config nginx changée"
- name: Recharger fail2ban (qui dépend de nginx)
ansible.builtin.systemd:
name: fail2ban
state: reloaded
listen: "config nginx changée"

Avec listen:, vous notifiez un événement abstrait ; tous les handlers qui écoutent ce topic se déclenchent. Pratique pour les rôles publics (Galaxy) qui ne veulent pas figer le nom des handlers.

Les handlers s'exécutent à la fin de chaque section qui les a notifiés :

1. gather_facts
2. pre_tasks → handlers notifiés ici tournent maintenant
3. roles → handlers notifiés ici tournent maintenant
4. tasks → handlers notifiés ici tournent maintenant
5. post_tasks → handlers notifiés ici tournent maintenant
6. fin du play

Conséquence : si une tasks: modifie nginx.conf et notifie Recharger nginx, le handler tourne avant post_tasks:. Donc dans post_tasks, vous pouvez tester http://localhost et nginx aura déjà sa nouvelle config.

Ordre interne : les handlers s'exécutent dans l'ordre de leur déclaration dans handlers:, pas dans l'ordre des notify:. Si vous voulez un ordre précis, déclarez les handlers dans cet ordre.

Parfois vous avez besoin que le handler tourne immédiatement, sans attendre la fin de la section. Le module meta avec flush_handlers force l'exécution :

tasks:
- name: Mettre à jour la config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Recharger nginx
- name: Forcer le reload immédiat
ansible.builtin.meta: flush_handlers
- name: Tester que la nouvelle config répond
ansible.builtin.uri:
url: http://localhost
status_code: 200

Sans flush_handlers:, le uri: testerait l'ancienne config (handler pas encore déclenché). Avec, nginx est rechargé entre les deux tâches.

Un handler est dédupliqué : si plusieurs tâches notifient Recharger nginx, le handler tourne une seule fois à la fin. Aucun risque de redémarrer 5 fois nginx parce que 5 tâches ont modifié sa config.

Cette propriété est ce qui rend le pattern restart-on-config-change propre et performant.

Pour les services critiques comme sshd, il faut valider la nouvelle config avant d'appliquer le restart. Sinon vous risquez de vous bloquer hors du serveur :

tasks:
- name: Mettre à jour sshd_config
ansible.builtin.template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
validate: '/usr/sbin/sshd -t -f %s' # ← validation préalable
backup: true
notify: Restart sshd
handlers:
- name: Restart sshd
ansible.builtin.systemd:
name: sshd
state: restarted

validate: '/usr/sbin/sshd -t -f %s' lance sshd -t -f /tmp/<temp>.conf avec le fichier généré. Si la commande échoue (config invalide), le template: ne remplace pas le fichier final et le handler n'est pas notifié. Votre serveur reste accessible.

SymptômeCauseFix
Handler jamais déclenchéLe notify: ne match pas exactement le name: du handlerVérifier l'orthographe (case-sensitive, espaces)
Handler exécuté plusieurs foisFaux : Ansible déduplique. Si vous le voyez plusieurs fois, c'est qu'il y a plusieurs handlers du même nomRenommer les handlers ou utiliser listen:
Handler exécuté trop tardVous attendiez qu'il tourne avant la tâche suivanteAjouter - meta: flush_handlers après la notif
notify: sur une tâche ok=trueNormal : un handler ne se déclenche que sur changedForcer avec changed_when: true (rarement justifié)
notify: ignore une variablenotify: ne supporte pas Jinja2 dans le nom au runtimeUtiliser listen: qui accepte des topics dynamiques
Service qui ne redémarre pasLe service est state: reloaded mais le binaire a changéUtiliser state: restarted (reload seul ne recharge pas le binaire)
  • Un handler est une tâche réactive déclenchée par notify:, uniquement si la tâche notificatrice est changed.
  • listen: regroupe plusieurs handlers sur un topic abstrait, utile pour les rôles publics.
  • Les handlers s'exécutent à la fin de chaque section (pre_tasks, tasks, post_tasks), dans l'ordre de leur déclaration.
  • meta: flush_handlers force l'exécution immédiate (utile avant un test post-config).
  • Pour les services critiques (sshd, nginx, postgresql), toujours poser un validate: sur le module template: pour ne pas appliquer une config cassée.
  • Un handler est dédupliqué : 5 notifs = 1 exécution. C'est la base de l'idempotence.

Un handler ne se déclenche que si la tâche notificatrice change vraiment, condition qu'un extrait de code ne peut pas démontrer. Le lab vous fait notifier plusieurs handlers depuis une seule tâche, les découpler avec listen, forcer leur exécution immédiate par meta: flush_handlers, et poser un validate pour ne jamais appliquer une configuration invalide. Le contrôle rejoue le playbook : au second passage, aucun handler ne doit repartir, c'est la mesure de l'idempotence.

  • Import vs Include : le comportement des handlers change selon que vous importez ou incluez un fichier de tâches.
  • Parallélisme et stratégies : ce que devient un handler pendant un rolling update piloté par serial:.
  • Check mode et diff : repérer en dry-run les tâches qui passeraient en changed, donc les handlers qui partiraient.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn