AnsibleXXV · Check Mode, Diff and Static ValidationCheck mode, diff and static validation
The sequence before a production run
What you'll learn
- Run the six rungs in order and state the stop condition for each
- Name the defect class each rung catches and the class it is blind to
- Assemble the evidence a change record should carry from a real run
- Recognise when a rung has been satisfied without being read
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
Each of the previous six lessons produced a control with a stated limit. This one puts them in order, because the ordering is where most of the value is: the rungs are arranged so that each one is cheaper than the next and rules out a class of mistake that would otherwise waste the next one’s time.
The ladder, with its blind spots
| # | Rung | Catches | Blind to |
|---|---|---|---|
| 1 | --syntax-check | YAML errors, unresolvable module names, missing roles and static imports | Module options, variables, template sources, dynamic includes |
| 2 | ansible-lint | Deprecated constructs, non-determinism, missing file modes, shell where a module exists | Anything that depends on a value; anything computed at run time |
| 3 | --list-hosts | The wrong target set, a pattern that matched more than intended | What would change; whether the hosts are reachable |
| 4 | --check on one canary | Connectivity, authentication, escalation, guard evaluation, template resolution, a change count | Unsupported modules, sequential dependencies, environmental preconditions, everything second-order |
| 5 | --check --diff on one canary | The exact content of predicted file changes | Whether that content is correct or the service accepts it |
| 6 | Real run under --limit to the canary | Everything the earlier rungs are blind to, on one host | Behaviour at scale, concurrency, load |
The third column is the one to read. Every rung above is satisfiable without establishing safety, and the reason to write the blind spots down is that a ladder whose limits are undocumented gets treated as complete by whoever inherits it.
The sequence, as an actual procedure
set -euo pipefail
PLAY=site.yml
INV=inventories/production/hosts
CANARY=web01.example.com
# 1. It compiles.
ansible-playbook -i "$INV" "$PLAY" --syntax-check
# 2. It is not obviously wrong. Fail on the target profile.
ansible-lint "$PLAY"
# 3. It targets who you think. READ THIS OUTPUT - do not just check the
# exit code, which is 0 for any resolvable pattern including a wrong one.
ansible-playbook -i "$INV" "$PLAY" --list-hosts
ansible-playbook -i "$INV" "$PLAY" --list-tasks# 4 and 5 together. One host: diff output for a fleet is unreadable, and
# the host whose diff differs from the rest is invisible in it.
ansible-playbook -i "$INV" "$PLAY" --limit "$CANARY" --check --diffansible-playbook -i "$INV" "$PLAY" --limit "$CANARY"
# Then verify the outcome directly, on the host, not from Ansible output:
# the service is running, the port answers, the log is clean, the
# dependent system still works. Ansible reporting ok means the module
# was satisfied, not that the service works.The stop conditions
A rung is not “run it and look at the exit code”. Each has something to read and a condition that stops the sequence.
1. --syntax-check. Stop on any non-zero exit. Note that 120 means
the inventory did not parse and 1 usually means a referenced file or role
is missing — neither is a problem in the playbook body, and knowing that
saves twenty minutes.
2. ansible-lint. Stop on a new finding in a file this change
touches. Pre-existing findings elsewhere are debt, not a stop condition —
that distinction is what makes the linter adoptable at all.
3. --list-hosts. Stop if the count differs from what you expected,
in either direction. Fewer is as alarming as more: a pattern that
matched twelve hosts when you expected fourteen means two are missing
from the inventory, and the run will report complete success while
leaving them behind.
4 and 5. --check --diff on the canary. Three separate reads:
- The
skippedcount, against the modules you expect to be skipped. A surprise here means something is not being evaluated and you do not know what. - The diff hunks, line by line. This is a code review of the rendered output and it is the reason the rung exists.
- The first task whose success depends on a previous task’s real effect. Everything after it is reported against a world that will not exist.
Stop if the diff contains a line you did not intend, or if the check run ended earlier than the play does.
6. The canary run. Stop unless the outcome verifies on the host.
Not changed=3 in the recap: the service is running, the port answers,
the dependent thing still works. This distinction — module satisfied
versus service working — is the subject of the testing part, and it is
the one that separates a canary from a slower deployment.
What the change record should carry
The output of the sequence is a paragraph, and it is a much better artefact than a screenshot of a green run:
Playbook: site.yml @ 4f2a91c
Inventory: inventories/production/hosts
Target: webservers (14 hosts, confirmed with --list-hosts)
Static gates: syntax-check pass; ansible-lint pass at profile 'safety'
Dry run: --check --diff --limit web01.example.com
18 of 22 tasks evaluated; 4 command tasks skipped
(no check-mode support); 3 files predicted to change,
diffs reviewed
Not validated: the 4 command tasks; anything after task 15, which
depends on the package install
Canary: web01.example.com, real run, service verified on host
Rollout: --limit 'webservers:!web01.example.com', serial 4
Two lines in that are unusual and both are the point. “18 of 22 tasks evaluated” replaces “check mode passed” with something a reviewer can weigh. “Not validated” is a section most change records do not have, and it is the one that would have prevented the incidents in the previous lessons.
Knowledge check
Knowledge check · 4 questions
Q1. --list-hosts reports 12 hosts and you expected 14. What is the correct response?
Q2. Why does --limit to a single canary belong on the check rung rather than only on the real run? Select all that apply.
Q3. A change record that states 18 of 22 tasks were evaluated and names the 4 that were not is more useful than one stating that check mode passed.
Q4. A 30-minute change window cannot fit the full sequence plus the whole-fleet rollout. What should be cut?
Passing score: 75%. Answers are checked in this browser.