Aller au contenu
Infrastructure as Code medium

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

12 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
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
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.

Cette page a un lab d’accompagnement : labs/ecrire-code/handlers/ dans stephrobert/ansible-training. Il contient un README.md guidé, un Makefile (make verify lance les tests), et un challenge final auto-évalué : déclencher deux handlers depuis une seule tâche avec validation httpd ServerTokens.

Une fois le lab provisionné :

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/handlers/
cat README.md # tuto pas à pas
cat challenge/README.md # consigne du challenge final
pytest -v challenge/tests/ # lancer les tests testinfra

Si les tests passent, vous maîtrisez les concepts couverts dans ce guide. En cas de blocage, docs/troubleshooting.md à la racine du repo couvre les pièges fréquents (rate-limit SSH, clé absente, collection manquante).

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn