Skip to main content
RunBook Academy

AnsibleXXXVI · Drift and ConvergenceDrift and convergence

Detect, decide, enforce

Advanced⏱ ~24 minansible-playbook

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

Not yet marked complete on this device.

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

StageWhat runsBlast radiusWho decides
DetectThe roles, --check --diff, whole scope, scheduledNone (see the caveats in lesson 3)Nobody — it is a report
DecideA person reading the reportNoneNamed owner
EnforceThe roles, for real, against a chosen scopeReal, and stated in advanceChange 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:

Read-only / Safeaudit.yml
- name: audit the web tier against its declaration
hosts: "{{ target | default('env_prod') }}"
gather_facts: true
roles:
  - common
  - nginx
  - monitoring_agent
Service impact possiblesite.yml
- name: converge the web tier
hosts: "{{ target | mandatory }}"
gather_facts: true
roles:
  - common
  - nginx
  - monitoring_agent

Two 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.

Service impact possibleremediate one finding, one wave at a time
# 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 nginx

Narrowing 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=0 remains 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

  1. 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?

  2. Q2. Why is writing a separate audit playbook full of assert and command checks a poor way to separate audit from enforcement?

  3. Q3. Which are genuine costs of turning on unattended scheduled convergence? Select all that apply.

  4. 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.