Skip to main content
RunBook Academy

AnsibleI · Why Configuration Management ExistsThe four approaches

The configuration management model

Foundation⏱ ~17 minbash

What you'll learn

  • State the configuration management model in terms that do not mention any tool
  • Distinguish declared state, convergence and the divergence report as three separate things
  • Explain what the model costs on an ongoing basis, not just to adopt
  • Recognise the failure mode in which the automation itself becomes the snowflake

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.

Configuration management is three things, and it is worth being able to name them separately because tools blur them and most confusion about the model comes from that blurring.

  1. A declaration of desired state. A machine-readable statement of what should be true on a host. Not what to do — what should be true.
  2. A convergence run. A process that reads the declaration, inspects the actual state, and changes only what differs.
  3. A report of what differed. The output of the run: which hosts were already correct, which were changed, what changed on them, and which could not be reached.

None of that mentions Ansible, YAML, SSH or agents. Puppet, Chef, Salt, CFEngine and Ansible are all implementations of this model with different opinions about transport, language and scheduling. Learning the model rather than the tool is what lets you evaluate the tool.

Declared state versus procedure

The distinction is the whole thing, so here it is concretely.

A procedure says what to do:

apt-get install -y chrony
sed -i 's/^pool .*/pool ntp.example.com iburst/' /etc/chrony/chrony.conf
systemctl enable chrony
systemctl start chrony

A declaration says what should be true:

- name: Time synchronisation is configured and running
  hosts: all
  tasks:
    - name: chrony is installed
      ansible.builtin.package:
        name: chrony
        state: present

    - name: chrony uses the estate NTP pool
      ansible.builtin.lineinfile:
        path: /etc/chrony/chrony.conf
        regexp: '^pool '
        line: 'pool ntp.example.com iburst'
      notify: restart chrony

    - name: chrony is enabled and running
      ansible.builtin.service:
        name: chrony
        state: started
        enabled: true

  handlers:
    - name: restart chrony
      ansible.builtin.service:
        name: chrony
        state: restarted

These are not the same thing written in two syntaxes. Read the differences:

  • The procedure installs the package. The declaration says the package should be present, and does nothing if it already is.
  • The procedure restarts the service unconditionally. The declaration restarts it only if the configuration file changed — that is what notify means, and it is a direct consequence of the declaration knowing whether it changed anything.
  • The procedure can be run. The declaration can be run, and it can also be evaluated without running, because “what should be true” is a question you can ask about a host without changing it.

That third point is the one that generates most of the operational value, and it is the one that is easiest to miss.

Convergence

A convergence run is the second element, and its defining property is that it is conditional at every step.

For each declared item, the run:

  1. Determines the current state of that item on that host.
  2. Compares it to the declared state.
  3. If they match, does nothing and records ok.
  4. If they differ, makes the change and records changed.
  5. If it cannot determine or cannot change, records failed.
flowchart LR
  D["Declared state"] --> C{"Compare"}
  A["Actual state<br/>on this host"] --> C
  C -->|"match"| OK["ok<br/>no action"]
  C -->|"differ"| CH["changed<br/>action taken"]
  C -->|"cannot determine"| F["failed"]

The diagram is small but it carries the model. Every task in every playbook you will write for the rest of this course goes through that decision, and the three outcomes on the right are the three columns you will read in a play recap.

The important consequence is that a convergence run is safe to repeat. Running it against a fleet that is already correct changes nothing and takes a few seconds per host. That is what makes all of the following possible:

  • Running it on a schedule to correct drift automatically.
  • Running it to check whether a fleet is correct, without changing anything.
  • Resuming after a run stopped partway, without needing to know which hosts were already done.
  • Adding a new host to a group and getting it configured by the same artefact that configured the others.

None of those work if the artefact is a procedure, because a procedure does not know what it already did.

The divergence report is the product

Here is the part that is genuinely underrated. Most people adopt configuration management for element 1 and element 2 — write it down, apply it — and discover element 3 afterwards.

The report is what turns automation into an instrument. A convergence run over a fleet answers, per host: were you already correct?

Read-only / Safeconvergence run in check mode - measure, do not correct
$ ansible-playbook -i inventories/production site.yml --check --diff
PLAY RECAP *********************************************************************
web01   : ok=14  changed=0  unreachable=0  failed=0  skipped=2
web02   : ok=14  changed=0  unreachable=0  failed=0  skipped=2
web03   : ok=13  changed=1  unreachable=0  failed=0  skipped=2
web04   : ok=14  changed=0  unreachable=0  failed=0  skipped=2
db01    : ok=0   changed=0  unreachable=1  failed=0  skipped=0

Illustrative output

That output is a drift measurement, produced in seconds, over the whole fleet. web03 differs in one respect. db01 could not be reached and therefore its state is unknown — not correct, not incorrect, unknown. Reading unreachable as “fine, we will get it next time” is one of the recurring mistakes this course keeps returning to.

What the model costs

This is where honest treatments diverge from marketing ones. The model has ongoing costs, not just adoption costs, and every one of them is a recurring commitment rather than a one-off.

The declaration must be maintained

The declaration is a second copy of the truth about your estate. Every time reality changes deliberately — a new package, a tuned parameter, a different certificate path — the declaration must change too, or it becomes wrong.

This is not overhead you can optimise away. It is the price of having a comparable statement of intent at all. The alternative to maintaining a declaration is not having one.

Someone must own it

A repository with no owner rots in a specific way: it accumulates conditionals. Each conditional was added because the change broke on one host, and rather than fixing the host or recording the exception, somebody added a when: clause. After two years the declaration describes not the intended state but the history of every host that ever resisted it.

It must be reviewed

An unreviewed change to a fleet-wide declaration is a fleet-wide change made by one person. The review is not bureaucracy; it is the only point at which a second human sees the blast radius before it is applied.

It needs somewhere to be tested

“Test it on a non-production host” requires a non-production host that resembles production. Estates that do not have one either test in production or do not test. Both happen, and the second one is more common than teams admit.

The whole thing must actually run

A declaration that is not applied is not a declaration; it is a document. This is the cost that is most often unpaid, and it produces the failure mode below.

The code becomes the snowflake

The failure mode above generalises, and it is worth stating as a principle because it is the thing that most often kills a configuration management adoption.

The declaration is only authoritative if it is applied. An unapplied declaration does not describe production; it describes a past intention. But it looks authoritative — it is in Git, it has review history, it is well written — so people continue to believe it.

At that point the repository has become exactly what it was built to eliminate: an artefact whose relationship to reality nobody can state, that cannot safely be reproduced from, and that no single person fully understands. A snowflake, in code.

Where the model does not fit

Stated here so lesson 8 can use it. Configuration management assumes:

  • Hosts are long-lived enough that converging them is worthwhile. Converging a host that will be destroyed in an hour is wasted work.
  • State is inspectable. The tool must be able to determine current state. Anything opaque — a firmware blob, a proprietary appliance database — cannot participate.
  • Change is incremental. The model corrects deltas. If the correct response to any divergence is “rebuild the host”, you want a different model, and lesson 6 covers it.
  • Someone owns the declaration for the lifetime of the estate. Not for the project, for the lifetime.

Estates that fail on the last point fail regardless of tool choice. It is worth checking before choosing between tools.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Which of these is the element of the configuration management model that teams most often adopt last, despite it producing much of the operational value?

  2. Q2. Because the convergence model is declarative, a playbook written in it is necessarily idempotent.

  3. Q3. A declaration has not been applied to production for eight months, and production has moved on in several undocumented ways. Which statements are accurate? Select all that apply.

  4. Q4. A team wants a single metric for whether their configuration management estate is healthy. Which is the most informative?

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