Ansible handlers.
A handler reacts to change: it restarts or reloads a service only when a task actually changed something β once, at the end of the play. Watch a changed task drop its handler into the notify queue, the queue dedupe, and the flush fire it exactly once.
Ansible handlers and notifications animated tutorial. Using notify on a task to trigger a handler only when the task reports changed, defining handlers in the play handlers section or a role handlers/main.yml, why a handler runs once at the end of the play in defined order, meta flush_handlers to run queued handlers mid-play, the failure gotcha where queued handlers are skipped and --force-handlers / force_handlers to run them anyway, and listen topics to fan one notify out to many handlers. RHCE EX294 ready.A handler restarts the service only when the config actually changed β once, at the end of the play. Watch a changed task drop its handler into the notify queue, the queue dedupe, and the flush fire it exactly once.
- Β·You can write a basic Ansible play with tasks
- Β·Familiar with the template / copy / service modules
- Β·Understand changed vs ok task results
Use notify and handlers to restart or reload a service only on change, understand that handlers run once at the end of the play in defined order, handle the failure gotcha with --force-handlers, and fan out with listen topics.
pace: 8 minutes
deploy httpd.conf [template]β changedopen firewall [firewalld]β okemptyπ restart httpdβ idleπ reload firewalldβ idleWhy handlers exist
You only want to restart a service when its config actually changed β not on every single run. A HANDLER is a task that runs only when NOTIFIED, and only at the end of the play. Define handlers in the play's `handlers:` section (or, for a role, in roles/NAME/handlers/main.yml). A regular task adds `notify:` pointing at a handler by its exact name.
$tasks:
- template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify: restart httpd$handlers:
- name: restart httpd
service:
name: httpd
state: restarted$# name must match EXACTLY
notify: restart httpd β - name: restart httpdA handler is a task that runs only when something actually CHANGED, exactly ONCE, at the END of the play, in the order it is DEFINED. That single rule is what lets you say "restart the service" without restarting it on every single run.
$handlers:$- name: restart httpd β service$notify: restart httpd$notify: [ h1, h2 ]$fires only when task is changed$runs ONCE even if notified many times$runs at END of play$in DEFINED order$- meta: flush_handlers$listen: "topic" + notify: "topic"$--force-handlers / force_handlers: true$not notified β handler skippedWhen does a handler actually run?
Five different tasks all `notify: restart httpd`, and all five report changed. How many times does httpd restart?
Two handlers are both queued. In what order do they run?
A task notifies `restart httpd`, then a later task FAILS before the play ends. What happens to the queued handler?
These aren't graded β they're just for active recall, which is what actually makes the lesson stick.
Ansible roles
Tasks, handlers, templates, and defaults belong together. Package them into a reusable role so the handlers you just learned ride along automatically wherever the role is used.
Read this lesson as text5 chapters Β· quiz answers
01Why handlers exist
You only want to restart a service when its config actually changed β not on every single run. A HANDLER is a task that runs only when NOTIFIED, and only at the end of the play. Define handlers in the play's
handlers:section (or, for a role, in roles/NAME/handlers/main.yml). A regular task addsnotify:pointing at a handler by its exact name.$ tasks: - template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpdβ task points at a handler by name$ handlers: - name: restart httpd service: name: httpd state: restartedβ the matching handler block$ # name must match EXACTLY notify: restart httpd β - name: restart httpdβ a typo = handler never runs
02notify β trigger on change
A task fires its
notifyONLY if it reportschanged. If the run isok(the system already matched), nothing is notified and the handler stays idle. A single task can notify several handlers by givingnotifya list. Here the template CHANGED (amber), so it queuesrestart httpd; the firewall task was already correct, so it queues nothing.$ - template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpdβ changed β queues the handler$ - copy: src: app.conf dest: /etc/app.conf notify: - restart httpd - reload firewalldβ notify a LIST of handlers$ - firewalld: service: http state: enabled permanent: true notify: reload firewalld # ok β no notifyβ unchanged task notifies nothing
03Run once, at the end, in defined order
No matter how many tasks notify the same handler, it runs ONCE. Handlers run AFTER every task in the play, in the ORDER THEY ARE DEFINED in
handlers:β not the order they were notified. Two tasks both notifiedrestart httpd, but the queue is deduped, so httpd restarts a single time at the flush. Need it sooner?meta: flush_handlersruns the queue right now, mid-play.$ - template: { src: httpd.conf.j2, dest: /etc/httpd/conf/httpd.conf } notify: restart httpd - copy: { src: vhost.conf, dest: /etc/httpd/conf.d/ } notify: restart httpdβ two notify β still ONE restart$ handlers: - name: restart httpd # runs first - name: reload firewalld # runs secondβ DEFINED order, not notified order$ - meta: flush_handlersβ run queued handlers immediately, mid-play
04Gotchas β failures skip handlers
If a later task FAILS before the play reaches the flush, the queued handlers do NOT run β the change you made (the new config) is left un-applied, so the service keeps running the old config.
--force-handlerson the command line (orforce_handlers: truein the play) runs any notified handlers even after a failure. Useful, but know the risk: you may restart into a half-finished change.$ ansible-playbook site.yml --force-handlersβ run notified handlers even after a failure$ - hosts: web force_handlers: true tasks: ...β same, set per-play in YAML$ # risk: handler restarts into a # half-applied change β decide deliberatelyβ forcing is not always safe
05listen β fan out one notify to many handlers
Instead of notifying handlers by name, you can notify a TOPIC: every handler whose
listen:matches that topic runs. This decouples the task from the handler names, so onenotifycan fan out to a whole group of handlers (great in roles). You can mix styles β a handler can have aname, alisten, or both.$ - template: { src: stack.j2, dest: /etc/stack.conf } notify: "restart web stack"β notify a TOPIC, not a name$ handlers: - name: restart httpd service: { name: httpd, state: restarted } listen: "restart web stack"β handler subscribes to the topic$ - name: reload firewalld service: { name: firewalld, state: reloaded } listen: "restart web stack"β both fire on one notify
Quiz β answers
- When does a handler actually run?
- βAt the end of the play, and only if it was notified by a task that reported
changed.Handlers are deferred: they queue when a task reportschangedand notifies them, then run together after all tasks in the play. Anok(unchanged) task notifies nothing, so its handler never runs. - Five different tasks all
notify: restart httpd, and all five report changed. How many times does httpd restart? - βOne β the queue is deduped, so a handler runs once no matter how many times it was notified.The notify queue is deduplicated by handler. However many tasks notify
restart httpd, it runs exactly once at the flush β that's the whole point of handlers. - Two handlers are both queued. In what order do they run?
- βThe order they are DEFINED in the handlers section β not the order they were notified.Run order follows the definition order in
handlers:(or the role's handlers/main.yml), regardless of which task notified which handler first. If order matters, order your handler definitions. - A task notifies
restart httpd, then a later task FAILS before the play ends. What happens to the queued handler? - βIt doesn't run β queued handlers are skipped on failure β unless you use --force-handlers / force_handlers: true.A failure before the flush aborts the play, leaving notified handlers un-run β so your new config sits there un-applied.
--force-handlers(orforce_handlers: true) forces notified handlers to run even after a failure.
keep going β these pair well with what you just learned.
Ansible control node & inventory
Ansible is agentless β one control node pushes work to a fleet over SSH. Install ansible-core, write an inventory with groups, ping the fleet, and fire ad-hoc commands. RHCE-ready.
Ansible playbooks & YAML
Plays, tasks, and idempotency. Watch a playbook run task-by-task β ok, changed, skipped β then run it again and watch everything go green with 0 changed. RHCE-ready.
Ansible core modules
file, user, copy, dnf, service β the modules that do the real work. Declare the desired state and let Ansible converge to it, idempotently. RHCE-ready.