AnsibleXXXVI · Drift and ConvergenceDrift and convergence
Detect, decide, enforce
What you'll learn
- Separate audit from enforcement at the entry point rather than by duplicating tasks
- Triage a drift finding into one of four outcomes before touching a host
- Remediate across waves rather than converging the fleet in a single run
- State the change-management trade-off in unattended scheduled convergence, in both directions
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
Detection produces a list. What happens next is where drift programmes succeed or quietly stop existing.
The failure is predictable: the report arrives, somebody looks at it, somebody runs the playbook for real against everything to “fix it”, a service restarts during business hours, and the drift report is quietly descoped to a weekly email nobody opens.
The structure that avoids that has three stages, and the middle one is a human.
Detect, decide, enforce
| Stage | What runs | Blast radius | Who decides |
|---|---|---|---|
| Detect | The roles, --check --diff, whole scope, scheduled | None (see the caveats in lesson 3) | Nobody — it is a report |
| Decide | A person reading the report | None | Named owner |
| Enforce | The roles, for real, against a chosen scope | Real, and stated in advance | Change process |
The stage that gets skipped is decide, because when the fix is one command away it feels like a formality. It is not: a drift finding has four possible correct outcomes and only one of them is “run the playbook”.
Triage: four outcomes, one of which is enforcement
For each finding:
1. Real drift — enforce. The host changed, the declaration is right, the host should be corrected. Run the role against it, in waves.
2. An undeclared requirement — change the code. The host is right and the declaration is wrong. Somebody made a deliberate improvement out of band and it should become the standard. Enforcing here would undo a correct change, which is the outcome that makes people distrust automation.
3. A legitimate deviation — record an exception. This host genuinely must differ: a licensing constraint, a customer requirement, a device that will not tolerate the standard setting. Enforcing breaks it; doing nothing means it appears in every report forever. It needs the mechanism in the next lesson.
4. A reporting defect — fix the task. The host and the declaration
agree and the task claims otherwise: a rendered timestamp, an unfenced
command, a mode expressed as a string. Enforcing changes nothing real
and the finding returns tomorrow.
Separating audit from enforcement without duplicating anything
The instruction “keep the audit playbook separate from the enforcement
playbook” is right and is usually implemented wrongly, by writing a
second playbook full of command and assert tasks that check the same
things the roles declare.
That duplicate immediately starts drifting from the roles. Within months the audit passes on hosts the enforcement would change, which is the worst possible failure for a compliance control.
Separate the entry point, not the logic. One set of roles, two thin playbooks:
- name: audit the web tier against its declaration
hosts: "{{ target | default('env_prod') }}"
gather_facts: true
roles:
- common
- nginx
- monitoring_agent- name: converge the web tier
hosts: "{{ target | mandatory }}"
gather_facts: true
roles:
- common
- nginx
- monitoring_agentTwo details in there are deliberate.
target | default('env_prod') in the audit: a scheduled report should
cover everything by default, and a missing variable should not produce a
zero-host run.
target | mandatory in the enforcement playbook: a missing variable
must be an error. An enforcement run with an unset hosts pattern that
silently falls back to all is the single most expensive typo in this
course.
Enforcing in waves
Remediation is a fleet-wide change and gets the same treatment as any other: waves, gates, a stated blast radius. Converging four hundred drifted hosts in one run is exactly the operation the wave discipline exists to prevent.
# what would this touch?
ansible-playbook -i inventory/ site.yml \
-e target=wave1_canary --tags nginx --list-hosts
# see the change before making it
ansible-playbook -i inventory/ site.yml \
-e target=wave1_canary --tags nginx --check --diff
# then, deliberately
ansible-playbook -i inventory/ site.yml \
-e target=wave1_canary --tags nginxNarrowing by tag matters as much as narrowing by host. A finding in the
nginx role does not justify running common and monitoring_agent
against those hosts as well — that is three roles’ worth of blast radius
to fix one file.
Convergence on a schedule: the honest trade
Running the enforcement playbook automatically, on a timer, against the fleet, is called unattended convergence. It is a legitimate and widely used design, and it is a change-management decision rather than a convenience.
What you get:
- Drift is corrected within one cycle rather than within one triage meeting. The window during which a host is wrong is bounded.
- The estate genuinely stays converged, so
changed=0remains a meaningful signal rather than an aspiration. - Manual out-of-band changes do not persist, which removes the incentive to make them.
What you give up:
- Production changes with nobody watching. At 02:00 a handler may restart a service, and the first person to know is whoever is paged.
- The content gate becomes the only gate. Whatever is on the main branch at 02:00 is what gets applied to the fleet. A merged mistake reaches production without anyone running it.
- Blast radius is the whole declared surface on every host, every night, by default.
- The out-of-band change gets reverted mid-incident. Someone applying an emergency fix by hand at 01:30 finds it undone at 02:00, and diagnosing that while the outage is live is genuinely awful.
The middle position is often the right one: converge the boring parts, report on the risky parts. Monitoring agents, sudoers, SSH configuration and package baselines are converged unattended; the application tier is reported and enforced deliberately. That splits by consequence rather than by tooling, which is the same instinct the wave work applies to hosts.
Knowledge check
Knowledge check · 4 questions
Q1. A drift report shows that a host has a sysctl set to a different value than the role declares. An engineer changed it deliberately last month after a performance investigation, and it was the right change. What is the correct outcome?
Q2. Why is writing a separate audit playbook full of assert and command checks a poor way to separate audit from enforcement?
Q3. Which are genuine costs of turning on unattended scheduled convergence? Select all that apply.
Q4. Reverting the commit and re-running the roles restores the configuration, but does not undo a service restart or a package upgraded by state: latest.
Passing score: 75%. Answers are checked in this browser.