Configuration Management (Ansible)
Ansible idempotency, roles and their directory layout, variable precedence, module choices, Vault, serial rollout, and the imperative-vs-declarative question.
8 questions
JuniorTheoryVery commonWhat is Ansible, and why is idempotency central to how it works?
What is Ansible, and why is idempotency central to how it works?
Ansible is an agentless configuration-management tool that drives a host to a declared desired state over SSH, replacing hand-built snowflake servers. Idempotency is central: a play declares the end state, not steps, so re-running it converges the host and reports changed only when it changed something.
Common mistakes
- ✗Thinking Ansible needs an agent installed on each managed host
- ✗Believing idempotency means tasks never re-run, rather than converging
- ✗Treating a playbook as an ordered script instead of a declared end state
Follow-up questions
- →How does a native module know it has nothing to change on a re-run?
- →What makes a
commandorshelltask break idempotency?
JuniorTheoryOccasionalWhat is an Ansible role, and what is its standard directory layout?
What is an Ansible role, and what is its standard directory layout?
A role is a reusable, self-contained bundle of Ansible automation with a fixed layout, so many playbooks include it by name rather than copy-pasting tasks. Under roles/<name>/ it holds subdirs Ansible auto-loads: tasks/, handlers/, templates/, files/, plus vars/ and defaults/ (lowest precedence) and meta/.
Common mistakes
- ✗Confusing a role with an inventory host group
- ✗Thinking
defaults/holds high-precedence, not lowest-precedence, vars - ✗Not knowing
tasks/main.ymlis auto-loaded by convention
Follow-up questions
- →Where do role
defaults/sit in overall variable precedence? - →How do you pass parameters into a role at include time?
MiddleTheoryOccasionalHow does agentless Ansible connect to a fresh host, and how does push differ from pull?
How does agentless Ansible connect to a fresh host, and how does push differ from pull?
Ansible is push-based and agentless: the control node SSHes into each inventory host, copies the module code over, runs it with the target's Python, and uses become (sudo) for privileged tasks — no daemon is installed. A fresh host needs only SSH and Python. Pull tools like Puppet or Chef instead run an agent per node that polls a central master on a schedule.
Common mistakes
- ✗Thinking Ansible installs an agent or polls like Puppet/Chef
- ✗Forgetting a target needs SSH plus Python for most modules
- ✗Confusing push/pull with source-control rather than who initiates
Follow-up questions
- →How would you bootstrap Python onto a host that lacks it?
- →When does a pull model scale better than push for thousands of nodes?
MiddleTheoryOccasionalWhen do you prefer modules like package, service or copy over a raw command, and why?
When do you prefer modules like package, service or copy over a raw command, and why?
Prefer a native module almost always: package, service, copy, file and template are declarative and idempotent — they check the host's current state and act only if it diverges, reporting changed accurately. A raw command/shell just executes and reports changed every run. Use command only when no module fits, and guard it with creates:.
Common mistakes
- ✗Reaching for
shell/commandwhen a native module exists - ✗Thinking a raw
commandis idempotent without a guard - ✗Not knowing
creates:/changed_when:restore idempotent reporting
Follow-up questions
- →What does
changed_when: falseexpress on a read-only command? - →How does check mode (
--check) behave for modules versuscommand?
MiddleDebuggingOccasionalA task reports changed on every run when it should be idempotent — diagnose and fix it.
A task reports changed on every run when it should be idempotent — diagnose and fix it.
Every task uses shell/command, which just runs and reports changed unconditionally — Ansible cannot see the state behind a raw command, so it never converges. Fix it with native, idempotent modules: file: state=directory, get_url, and apt: name=nginx state=present. When a command is unavoidable, guard it with creates: or changed_when:.
Common mistakes
- ✗Blaming fact cache or
becomerather than the rawshell/command - ✗Thinking
registeralone makes acommandidempotent - ✗Not replacing commands with
file/get_url/aptstate modules
Follow-up questions
- →What does
changed_when: falsedo for an inherently read-only command? - →How would
--checkmode behave on the module version of this play?
MiddleTheoryOccasionalWhat does serial (rolling execution) give you, and how do you avoid downing the whole fleet?
What does serial (rolling execution) give you, and how do you avoid downing the whole fleet?
By default Ansible runs a play across all matching hosts in parallel (up to forks), so a bad change hits all at once. serial: batches the rollout — serial: 1 or serial: 25% updates that many hosts per wave, so you canary a batch, verify, then proceed. Pair it with max_fail_percentage to abort on too many failures, and drain each batch from the load balancer.
Common mistakes
- ✗Thinking Ansible updates hosts one-by-one without
serial - ✗Reading
serialas task ordering or a per-host delay - ✗Skipping
max_fail_percentage, so a bad batch rolls to the whole fleet
Follow-up questions
- →How do
pre_tasks/post_tasksdrain and re-add a host from the LB? - →How does
max_fail_percentageinteract with theserialbatch size?
MiddleTheoryOccasionalWhat is Ansible Vault, and how do you keep secrets out of Git?
What is Ansible Vault, and how do you keep secrets out of Git?
Ansible Vault encrypts sensitive data at rest with AES-256 so it can live safely in the repo: you ansible-vault encrypt a vars file or values, and Ansible decrypts them at runtime given the vault password (via --ask-vault-pass or a password file). Never commit plaintext — commit only the encrypted form, and keep the vault password out of Git.
Common mistakes
- ✗Thinking Vault is access control rather than at-rest encryption
- ✗Committing the vault password alongside the encrypted file
- ✗Assuming Vault only obfuscates (base64), not encrypts, the data
Follow-up questions
- →How do you rotate a leaked vault password across many files?
- →When would you use an external secrets store instead of Vault?
MiddleTheoryRareWhere do extra-vars (-e) sit in Ansible variable precedence versus role defaults?
Where do extra-vars (-e) sit in Ansible variable precedence versus role defaults?
Extra-vars passed with -e have the highest precedence — they override every other source, which suits one-off overrides. Role defaults/ sit at the very bottom, the weakest source, overridden by inventory, group_vars, host_vars or play vars. So -e always wins; a defaults/ value applies only when nothing higher sets it.
Common mistakes
- ✗Thinking role
defaults/override the command line - ✗Believing precedence is decided by file-read order, not the ladder
- ✗Assuming
-emerges with lower sources instead of overriding them
Follow-up questions
- →Where do
group_varsandhost_varsfall relative to play vars? - →When is a one-off
-eoverride preferable to editing group_vars?