AnsibleL · Automation Disaster RecoveryProving the rebuild works
The timed rebuild drill
What you'll learn
- Run a controller rebuild drill with a defined start condition, success criterion and clock
- Choose a drill operator who cannot rely on undocumented knowledge
- Record the three findings a drill reliably produces and turn each into a repository change
- Distinguish a drill that proves recovery from one that only proves the tooling installs
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Five lessons of plan. This one is the only lesson in the part that produces evidence.
Everything before it can be completed on paper and reviewed favourably. The repository has the right contents, the pins are exact, the escrow exists, the runbook is written. None of that is a claim about whether a controller can be rebuilt; it is a claim about whether the artefacts that a rebuild would use are present.
The drill is the difference. It is a scheduled exercise with a clock, a defined start condition and a binary success criterion, and it has one property that makes it uniquely valuable: it cannot be passed by knowing things, because the operator is chosen not to know them.
The drill, defined
The clock is not there to make anyone feel bad. It is there because RTO is a number your organisation has committed to somewhere, and this is the only measurement of it that exists.
Who runs it
This decides whether the drill measures anything.
The person who built the original controller cannot run the drill, for
the same reason an author cannot proofread their own writing: they will
supply the missing step without noticing it was missing. They will type
the extra export from muscle memory, know that the inventory needs a
flag, and remember that the internal Galaxy server needs a proxy — and
each of those is exactly the finding the drill exists to produce.
The right operator is a competent engineer who has not built this controller. New team members are ideal, and running the drill as part of onboarding is a pattern worth stealing: it produces a tested runbook and a productive new colleague from the same afternoon.
The sequence
- Start the clock. Provision a fresh host and record what OS and how it was obtained - "provisioned by the platform team, 40 minutes" is a legitimate and important part of the RTO.
- Clone the repository. If this needs a credential, that credential is a DR dependency and belongs in the escrow inventory - most teams discover it here.
- Read the rebuild runbook from the clone. The operator follows it literally. Where it is ambiguous, they record the ambiguity rather than interpreting it.
- Install the pinned toolchain: the virtualenv from lesson 3, or the execution environment from lesson 5, whichever the runbook names.
- Restore the secrets from escrow: vault password per vault ID, automation private key, become password if used.
- Verify the toolchain: ansible --version, the collection diff against requirements.yml, and ansible-galaxy collection verify.
- Verify the inventory resolves: ansible-inventory --graph, and confirm the host count matches expectations.
- Prove decryption: ansible-vault view on every encrypted file, redirected to /dev/null.
- Run the read-only play against a small representative group, then against the whole fleet if the group succeeds.
- Stop the clock. Record the elapsed time, every question asked, and every step that was not in the runbook.
The verification play
The success criterion needs to be a real play against real hosts, not
ansible --version. It also must not change anything.
- name: Rebuild drill - prove the controller works, change nothing
hosts: all
gather_facts: true
become: false
tasks:
- name: Connectivity and a working remote interpreter
ansible.builtin.ping:
- name: Facts are usable, so templates and conditionals will resolve
ansible.builtin.assert:
that:
- ansible_facts['distribution'] is defined
- ansible_facts['default_ipv4'] is defined
fail_msg: 'Facts incomplete on {{ inventory_hostname }}'
- name: The vault password decrypts what the plays actually use
run_once: true
ansible.builtin.assert:
that: vault_smoke_test == 'decrypted'
fail_msg: 'Vault variable did not resolve - the escrowed password is wrong for this vault ID.'
- name: Report the toolchain that produced this run
run_once: true
ansible.builtin.debug:
msg: >-
core {{ ansible_version.full }},
python {{ ansible_playbook_python }},
{{ ansible_play_hosts_all | length }} hosts reachedvault_smoke_test is a variable you place in an encrypted group_vars
file for exactly this purpose, holding the literal string decrypted.
It is not a secret, and its only job is to make “the escrowed password
opens the file the plays actually read” a task that either passes or
fails rather than an assumption.
# Confirm the target set before running anything.
ansible-playbook -i inventory/ drill-verify.yml --limit canary --list-hosts
# The proof.
ansible-playbook -i inventory/ drill-verify.yml --limit canary
ansible-playbook -i inventory/ drill-verify.ymlThe three findings, and what to do with each
A first drill essentially always produces these. Their reliability is the argument for running one.
| Finding | What it looks like | The repository change |
|---|---|---|
| An undocumented manual step | The operator is stuck; the builder says “oh, you need to export ANSIBLE_CONFIG first” | Add the step to the runbook, or better, remove the need for it |
| A collection installed by hand | The rebuild succeeds and the first real play fails with “couldn’t resolve module/action” | Add it to requirements.yml, pinned |
| A credential that only existed on the dead host | The operator cannot clone, cannot decrypt, or cannot connect | Add it to the escrow inventory and re-escrow |
Every finding closes as a commit. A drill whose output is a document describing what went wrong has produced a report; a drill whose output is a merged pull request has produced a fix. Track them the way you would track incident actions, because that is what they are — you have simply run the incident on purpose.
Cadence and scope
- Full drill, fresh host, unfamiliar operator: annually at minimum, and after any structural change to the controller build.
- Escrow-only exercise - open it, prove every vault ID decrypts, prove the SSH key authenticates against a canary: quarterly. This is an hour and catches the rotated-key failure from lesson 4.
- Pin verification - the collection diff and ansible-galaxy collection verify against requirements.yml: on every CI run, because it is cheap and it is exactly the check that catches a hand-installed collection the day it appears.
- Runbook read-through by someone who has not read it: whenever a new engineer joins. Free, and it finds ambiguities before the annual drill does.
The fire-drill discipline itself — how to schedule exercises so they happen, how to keep them from becoming ceremonial, how to write up results people act on — is developed at length in the Proxmox course’s DR part. This lesson deliberately does not restate it. What is specific to Ansible is the content of the drill: the toolchain, the escrow, the inventory, the vault, and the read-only run that proves all four at once.
Knowledge check
Knowledge check · 4 questions
Q1. Why must the drill operator be someone other than the person who built the original controller?
Q2. Which of these would a first rebuild drill reliably expose? Select all that apply.
Q3. A drill that completes successfully on the same host used for the previous drill is valid, because the rebuild steps are identical either way.
Q4. The drill verification play runs with `become: false`. What does that choice leave unproven, and how should it be handled?
Passing score: 75%. Answers are checked in this browser.