Ansible architecture: inventory, modules, handlers and repeatable change
Understand how an Ansible control node targets inventory and executes modules. Work through configuration change, handlers, check mode and rolling execution without assuming every task is idempotent.
TL;DR: Ansible evaluates a playbook on a control node, selects hosts from inventory and executes tasks through connection plugins and modules. Repeatability depends on task behavior and inputs. A playbook written in YAML is not automatically idempotent or safe to run across the entire fleet at once.
Inventory connects intent to actual machines
Inventory supplies hosts, groups and variables. It can be static or obtained from dynamic sources such as a cloud inventory plugin. A play selects a host pattern and defines tasks under an execution strategy. Connection plugins determine how Ansible reaches the target, commonly SSH for Linux, with other transports for Windows and network devices.
The control node is where the automation is evaluated. Managed nodes execute supported module operations and return results. Some actions are delegated or execute locally, so determine where a particular task really runs before assuming the control node's filesystem or credentials are available on the target.
The Ansible basic concepts guide defines inventory, plays, tasks and modules. Agentless operation still requires an authenticated connection and the dependencies required by the chosen modules.
A small configuration change
This excerpt assumes Linux hosts in inventory group web, privilege escalation configured by policy, Nginx already installed, and a reviewed template file. It updates configuration one host at a time. The template is a full Nginx configuration suitable for the validation command.
- name: Update the web tier configuration
hosts: web
become: true
serial: 1
tasks:
- name: Render validated Nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c %s'
notify: Reload nginx
- name: Apply notified changes before verification
ansible.builtin.meta: flush_handlers
- name: Verify local HTTP health
ansible.builtin.uri:
url: http://127.0.0.1/health
status_code: 200
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
The example explicitly flushes handlers before the health check; otherwise a later handler could apply the change after a premature verification. The endpoint and reload behavior are assumptions to verify for the service you operate. A local HTTP check also does not prove the host has rejoined the external load balancer correctly.
Changed status drives later behavior
Many state-oriented modules compare the current state with the requested state and report whether a change occurred. A notified handler can then reload a service only when the relevant task reports a change. This avoids unnecessary restarts, but depends on accurate task results.
| Task design | Repeat-run behavior to establish | Risk |
|---|---|---|
| Ensure a package is at an explicitly selected version | Already-matching host stays unchanged | Repository availability and downgrade policy still matter |
| Render a stable template | Same rendered bytes require no update | Volatile template values can trigger constant changes |
| Run an arbitrary shell command | Depends entirely on the command | Appending a line or creating users may repeat effects |
| Invoke an external API | Depends on API identity and retry design | Lost responses can cause duplicate effects |
The playbook introduction distinguishes idempotent modules from playbooks that do not preserve that property. A changed_when: false statement changes reporting; it does not make the underlying command safe to repeat.
Check mode has a boundary
Check mode predicts changes for modules that support it. Tasks with unsupported behavior, runtime dependencies or external side effects may not produce a complete simulation. Inspect module support and validate in a disposable environment before relying on the output for a fleet-wide action.
Diff output can expose credentials embedded in templates. Restrict logs and use appropriate secret-handling settings. Ansible Vault encrypts selected data at rest; it does not prevent a task from printing decrypted material during execution. Distinguish that feature from HashiCorp Vault, a separate secret-management service.
Rolling changes still need failure policy
serial: 1 bounds the play's batch size. It does not automatically drain customer traffic or prove the remaining hosts have capacity. A production sequence may remove a host from service, update it, verify it and rejoin it, with an explicit decision on whether the next batch can proceed.
Choose what happens after a failed validation. Continuing through every host can turn a single configuration mistake into a fleet outage. Conversely, stopping safely requires knowing whether a failed host remains drained and who restores it. Record per-host outcomes so a retry can distinguish unchanged, completed and partially changed machines.
Check the repeatability claim
For reusable automation, roles and collections define the package boundary, caller inputs and dependency versions.
Self-check: a playbook uses shell: echo setting=true >> /etc/example.conf, and its author says a second run is safe because the task has changed_when: false. What happens?
The line is appended again. Reporting no change does not undo the write. Use a module or template that declares the required file state, preserve any unrelated configuration deliberately, and verify that two runs produce the same intended result.
Compare Ansible's host configuration role with Terraform provisioning, and use the idempotency concept to reason about retries that cross external systems.