Ansible error handling.
try/catch/finally for your plays. A failed task usually halts the host β but `block`/`rescue`/`always`, `failed_when`, `ignore_errors`, and `assert`/`fail` let you decide exactly when a play recovers, when it cleans up, and when it should stop loudly.
Ansible error handling animated tutorial. Default failure behavior where a failed task halts the affected host while others continue, any_errors_fatal and max_fail_percentage to tune the blast radius, ignore_errors and failed_when to redefine failure, changed_when, block rescue always as try catch finally with ansible_failed_task and ansible_failed_result, assert and fail to guard preconditions, meta end_play, and --force-handlers. RHCE EX294 ready.A failed task usually halts the host β but you decide how brittle a play really is. Watch a deploy fail, jump to rescue, recover, and still run its cleanup, then see what changes when the block succeeds.
- Β·You can write and run an Ansible playbook
- Β·Comfortable with tasks, register, and when:
- Β·Know what a non-zero exit code means
Control failure in a play: tune the blast radius, redefine what counts as failed, recover with block/rescue/always, and stop deliberately with assert and fail.
pace: 9 minutes
block:run theseinstall packageβ oktemplate configβ failedstart serviceβ skippedrescue:catch β recovery(no rescue defined)β idlealways:finally β cleanup(no always defined)β idleWhat failure does by default
When a task fails on a host, Ansible STOPS running further tasks on that host β but keeps going on every other host in the play. The remaining tasks for the failed host are skipped and it drops out of the run. You tune this blast radius with `any_errors_fatal: true`, which aborts the whole play the instant any host fails, or with `max_fail_percentage`, which lets you tolerate a fraction of the fleet failing before bailing out.
$- name: template config
ansible.builtin.template: ... # β FAILED$any_errors_fatal: true$max_fail_percentage: 30By default a failed task halts that host. block/rescue/always give you try/catch/finally β recover in rescue, clean up in always β while failed_when/ignore_errors let you decide what even counts as failure. Together they turn a brittle playbook into one that fails loudly only when it should.
$(default)$any_errors_fatal: true$max_fail_percentage: N$ignore_errors: true$failed_when: COND$changed_when: COND / false$block: / rescue: / always:$ansible_failed_task$ansible_failed_result$assert: that: [..] fail_msg/success_msg$fail: msg="..." (+ when:)$meta: end_play / end_host$--force-handlers$register: resultBy default, a task fails on one of five hosts. What happens?
When do `rescue:` tasks run?
When do `always:` tasks run?
A `command` task exits non-zero, but for your case that's actually fine. How do you stop Ansible treating it as a failure?
These aren't graded β they're just for active recall, which is what actually makes the lesson stick.
Ansible roles
Once a play is robust, make it reusable. Roles structure your tasks, handlers, vars, and templates into a tidy, shareable package you can drop into any playbook.
Read this lesson as text5 chapters Β· quiz answers
01What failure does by default
When a task fails on a host, Ansible STOPS running further tasks on that host β but keeps going on every other host in the play. The remaining tasks for the failed host are skipped and it drops out of the run. You tune this blast radius with
any_errors_fatal: true, which aborts the whole play the instant any host fails, or withmax_fail_percentage, which lets you tolerate a fraction of the fleet failing before bailing out.$ - name: template config ansible.builtin.template: ... # β FAILEDβ later tasks on this host skip$ any_errors_fatal: trueβ any host fails β abort whole play$ max_fail_percentage: 30β tolerate up to 30% failing
02ignore_errors & failed_when
Sometimes a non-zero exit isn't really a failure for your case.
ignore_errors: truelets the play march on past a failed task instead of halting the host.failed_when:redefines WHAT failure even means β fail only when stderr contains ERROR, say.changed_when:does the same for the changed status, so a read-only command can reportchanged_when: false. Register the result first, then express the condition against it.$ ignore_errors: trueβ continue even if this task fails$ failed_when: "'ERROR' in result.stderr"β only THIS counts as failure$ register: result failed_when: result.rc not in [0, 2]β rc 2 is fine here
03block / rescue / always
Group related tasks under a
block:. If ANY task in the block fails, Ansible jumps to therescue:tasks β your recovery path, like a catch.always:runs no matter what happened, success or failure, like a finally clause β perfect for releasing locks or tearing down temp state. This is Ansible's structured error handling, and rescue blocks can inspectansible_failed_taskandansible_failed_resultto react to exactly what broke.$ block: - ...risky tasks rescue: - ...recovery always: - ...cleanupβ the whole structure$ debug: var=ansible_failed_task.nameβ what failed, inside rescue$ always: - file: path=/run/app.lock state=absentβ cleanup runs regardless
04assert & fail β stop on purpose
Sometimes you WANT to stop.
assert:checks the conditions in itsthat:list and fails with a customfail_msgif any are false β ideal for validating preconditions before you touch anything.fail:stops immediately with your message, usually guarded by awhen:so it only fires in the bad case. For finer control,meta: end_playends the play for everyone andend_hostbows out just the current host.$ assert: that: - ansible_distribution == 'RedHat' fail_msg: "RHEL only"β validate a precondition$ fail: msg: "unsupported OS" when: ansible_os_family != 'RedHat'β bail out on purpose$ meta: end_playβ end play (or end_host)
05Putting it together β robust plays
Stack the tools. Put
assertat the top to validate inputs before anything runs, wrap the risky steps inblock/rescueso a failure becomes a controlled recovery, and drop teardown inalwaysso locks and temp files always get cleaned up. Reserveignore_errorsfor genuinely optional steps. Pair the whole thing with--force-handlersso a mid-play failure still flushes the handlers a host already notified.$ assert: that: [ required_var is defined ]β fail fast on bad input$ block: [risky] rescue: [notify ops] always: [cleanup]β controlled recovery + teardown$ ansible-playbook site.yml --force-handlersβ flush handlers despite a failure
Quiz β answers
- By default, a task fails on one of five hosts. What happens?
- βThat host stops running further tasks; the other four keep going β unless any_errors_fatal or max_fail_percentage say otherwise.A failed task halts only the affected host; its remaining tasks are skipped and it drops out of the run, while the rest of the fleet continues. You change that with any_errors_fatal: true (abort everything) or max_fail_percentage (tolerate a fraction).
- When do
rescue:tasks run? - βOnly when a task in the associated
block:failed.rescue is the catch clause: it runs only if a task inside its block failed. If the block succeeds, rescue is skipped entirely. - When do
always:tasks run? - βAlways β whether the block succeeded or failed β like a finally clause.always is the finally clause: it runs regardless of whether the block succeeded or failed (and regardless of whether rescue ran). It's where cleanup like releasing locks belongs.
- A
commandtask exits non-zero, but for your case that's actually fine. How do you stop Ansible treating it as a failure? - βSet failed_when to redefine the failure condition (e.g. failed_when: result.rc not in [0,2]) β or ignore_errors: true to simply continue past it.failed_when redefines what counts as failure based on the registered result, so the task is marked ok. ignore_errors: true is the blunter option β the task still shows as failed, but the play continues anyway.
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.