Skip to main content
RunBook Academy

AnsibleLII · Anti-PatternsAnti-patterns of neglect

Anti-pattern: automation nobody tests, and automation nobody dares run

Advanced⏱ ~30 minbash

What you'll learn

  • Identify the five testing layers an untested repository is missing, cheapest first
  • Recognise the endpoint - automation nobody dares run - and the drift it produces
  • Explain why a repository that no longer matches the estate is worse than no repository
  • Use the anti-pattern to failure map as a repository review checklist

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

Not yet marked complete on this device.

This is one anti-pattern with two ends, and the second follows from the first with enough reliability that they belong in the same lesson.

The first end: no lint, no --syntax-check, no check-mode run, no Molecule scenario, no review. Production is the first test environment.

The second end, which is where it goes: the automation has broken things often enough that nobody wants to run it, so hosts get fixed by hand. That is drift with extra steps, plus a repository that now lies about the estate.

End one: production as the first test environment

Read-only / Safethe five layers, cheapest first - each takes seconds
# 1. Parse. Catches YAML errors, undefined roles, bad task keywords.
ansible-playbook site.yml --syntax-check

# 2. Lint. Catches anti-patterns from this whole part, by rule.
ansible-lint --profile production

# 3. Inventory resolution. Proves the targeting is what you think.
ansible-inventory -i inventory/ --graph
ansible-playbook site.yml --limit app_prod --list-hosts

# 4. Check mode with diff, against a canary.
ansible-playbook site.yml --limit canary --check --diff

# 5. Idempotence. The second run should report changed=0.
ansible-playbook site.yml --limit canary
ansible-playbook site.yml --limit canary

Five commands. Nothing to install beyond ansible-lint, no test infrastructure, no container runtime. Every one of them catches a class of defect that would otherwise be caught by a managed node.

Molecule sits above these: a role tested in a container, converged, verified and converged again, without a managed node. Part XXVI covers it. It is the most valuable layer and the most expensive one, which is why it belongs after the five above rather than instead of them.

The specific omission: no validation of what gets written

Configuration changethe difference validate: makes
# Without: a broken config is written, then the handler restarts
# the service into it.
- name: Install the nginx configuration
ansible.builtin.template:
  src: nginx.conf.j2
  dest: /etc/nginx/nginx.conf
notify: Restart nginx

# With: the file is validated before it replaces the live one.
- name: Install the nginx configuration
ansible.builtin.template:
  src: nginx.conf.j2
  dest: /etc/nginx/nginx.conf
  validate: 'nginx -t -c %s'
notify: Restart nginx

validate: is a per-task control that costs one line and converts “write a broken config and restart into it” into “fail before touching the live file”. Combined with serial, the failure stops at host one. Part XVII covers the render-check-activate pattern in full.

End two: automation nobody dares run

The endpoint has a recognisable set of symptoms. If several of these are familiar, this is where the repository is.

  1. The playbook is run only by one person, who "knows how it behaves".
  2. Runs are always scoped with --limit to a handful of hosts, never to a group, because a wider run is felt to be risky.
  3. There is a step in the runbook that says "check with the team before running site.yml".
  4. Hosts are being fixed by hand during incidents, with the intention of putting it in the playbook afterwards - which happens sometimes.
  5. Nobody has run the full playbook against the full fleet in more than six months.
  6. A new engineer is told which playbooks are safe and which are not, verbally.

Getting out

The instinct is to run the playbook against the fleet and let it converge. That is the one move guaranteed to confirm everyone’s fears.

  1. Measure the divergence before changing anything. --check --diff against one host per group, read the diffs, and write down what has drifted. This is read-only and it is the whole basis for what follows.
  2. Triage each difference: a manual fix that should be in the repository, a manual fix that was wrong, or a genuine per-host exception that belongs in inventory as data.
  3. Codify the first category. Every manual fix that should be automation becomes a commit. The repository starts describing reality again, one difference at a time.
  4. Declare the third category. Exceptions belong in group_vars or a documented exception group, with a reason and a review date, per Part XXXVI.
  5. Only then converge, and do it with staging: one canary host, then a small group, then the tier. The first honest run after a long gap is the most dangerous run in this course, and it should be treated like a migration.
  6. Add the five testing layers before the second convergence, so the gap does not reopen.

The order matters. Codifying before converging is what turns a frightening fleet-wide change into a series of reviewable commits whose effect is already known, because the diff was read first.

The part, as a review checklist

Each anti-pattern with the incident class it produces. This is the summary artefact — usable directly on a pull request or a repository review.

Anti-patternIncident classCorrected form taught in
shell everywhereFalse assurance — --check proves nothing, reruns re-performParts VIII, IX, XII
Secrets in GitIrrevocable leak, remediated by fleet-wide rotationPart XXI
No limit, no canaryFleet-wide outage with no healthy population leftParts XXX, XXXI
ignore_errors everywhereFalse assurance — success reported during an outagePart XXIII
Inaccurate changedRestart storms, then silent driftParts XII, XVI
Inventory logic in whenUnreviewable targeting; check mode generalises to nothingParts V, XV
latest everywhereUnreproducible estate; Part L becomes unachievableParts XXVII, XL
Nobody tests, nobody dares runSilent drift and a repository that liesParts XXV, XXVI

Five distinct incident classes across eight anti-patterns: false assurance, irrevocable leak, fleet-wide outage, unreproducible estate, and silent drift. Ranked by blast radius, the leak and the outage come first — but false assurance is the one that enables the others, because it is what stops you noticing.

Knowledge check

Knowledge check · 5 questions

  1. Q1. A team has no testing at all and limited time. Which layers give the most defect reduction for the least effort?

  2. Q2. Which failures follow from a repository that no longer describes the estate because hosts have been fixed by hand? Select all that apply.

  3. Q3. The right first move for a long-unrun playbook is to run it against the fleet so the estate converges back to the repository.

  4. Q4. Why does untested automation reliably become feared automation rather than staying merely untested?

  5. Q5. An estate is too diverged for a full reconciliation to be practical. Which approach is most likely to succeed?

Passing score: 75%. Answers are checked in this browser.