Module 14 — Full Interview Simulation

Updated 20 August 2026

Module 14 · Full Interview Simulation

The final module. Four rounds in the shape a real DevOps interview takes: rapid-fire concepts, scenario design, debugging under pressure, and system design.

How to use this one is different. Do not read it. Answer out loud first, then open the toggle. Reading the answers produces recognition; producing them produces recall, and only recall survives an interview room.

Prerequisite: Modules 01–13.


Part A · How Ansible interviews are actually structured

The analogy. Think of the difference between reading about swimming and getting into the water. You can know the theory of a stroke perfectly and still flounder the first time, because reading builds recognition — the comfortable feeling of "yes, that is correct" — while an interview demands recall, which is producing the answer out of nothing with someone watching you.

Those are two different skills, and only one of them is trained by reading. So do not read this module. Answer each question aloud first, then open the toggle. The gap between what came out of your mouth and what is written there is precisely the thing worth working on — and it is a gap you cannot see any other way.

RoundWhat is askedWhat is actually assessed
Rapid-fireDefinitions, mechanisms, "what is the difference between"Whether you used it or read about it
Scenario"How would you roll out X to 500 servers?"Judgement, risk awareness, and whether you volunteer the failure modes
DebuggingA broken playbook or a described incidentMethod — do you classify before investigating, or guess
System design"Design automation for a 2,000-host estate"Whether you think about people and process, not just YAML
Three habits that raise every answer, regardless of the question:

1. Name the trade-off unprompted. "strategy: free is faster, but it removes every cross-host ordering guarantee." Volunteering the cost is the clearest signal of real use.

2. Reach for the concrete failure. "I've had that fail because sudo changed $HOME and Ansible looked in /root/.ssh." A specific scar beats a correct definition.

3. Say what you would check, not just what you would conclude. Interviewers hire people who can diagnose, not people who happen to know this answer.


Part B · Round 1 — Rapid-fire

Twenty questions. Give yourself 60 seconds each, out loud. Open the toggle only after answering.
1. Explain Ansible in one sentence, then unpack it.

A push-based, agentless, declarative-ish configuration management and orchestration engine.

Push — the control node initiates, unlike Puppet's pull. Agentless — nothing on the targets but SSH and Python. Declarative-ish — you declare state but tasks execute in order, so it is not a dependency graph like Terraform. Orchestration — it can sequence across hosts, which a bash loop cannot. (Module 01)

2. Walk me through what happens when a task runs.

Parse inputs and build the host list → select the module → build the AnsiballZ payload (module + arguments, zipped and base64-encoded into one file) → open or reuse the SSH connection → copy it to a temp dir on the target → execute with the target's Python, optionally via sudo → module prints JSON to stdout → control node parses it and deletes the temp dir → render ok/changed/failed/skipped → next task.

The detail that lands: the target's Python, which is why the managed node needs Python at all. (Module 01)

3. Does Ansible run all tasks on host 1 then move to host 2?

The opposite — task 1 on every host in batches of forks, an implicit barrier, then task 2. That barrier is what makes orchestration possible. strategy: free removes it; serial: N batches hosts through the whole play. (Modules 01, 03, 08)

4. What does ansible all -m ping test?

Not ICMP. It opens SSH, pushes the ping module, runs it with the remote Python, and expects pong — so it tests network reachability, authentication, and a working Python, all at once. (Module 01)

5. Four ansible.cfg locations, in order. Do they merge?

ANSIBLE_CONFIG./ansible.cfg~/.ansible.cfg/etc/ansible/ansible.cfg. First match wins, no merging. It also skips world-writable directories. ansible --version shows which won. (Module 01)

6. Highest and lowest variable precedence?

Lowest real source: role defaults. Highest: extra vars (-e), which cannot be overridden from inside a playbook, not even by task-level vars:.

In between, more specific beats general. The asymmetry worth naming: a role's vars/main.yml at level 15 sits far above its defaults/ at level 2. (Modules 02, 05)

7. set_fact versus register versus vars:?

register captures a module's full result object. set_fact assigns a value you computed. Both are host-scoped and persist across later plays in the same run, both at level 19. vars: is play-scoped and disappears when the play ends — which is why set_fact is how you pass data between plays. (Module 02)

8. Why is when: myvar true when the value is false?

It is the string "false", and non-empty strings are truthy. Usually from -e, which stringifies everything. Fix with | bool, or pass JSON: -e '{"myvar": false}'. Diagnose with type_debug. (Modules 02, 13)

9. Three ways to make a shell task idempotent — and the better question.

creates:/removes:, changed_when:/failed_when:, or a when: guard from a registered fact.

The better question first: does a real module already exist? shell is a last resort. (Module 01)

10. What happens after a successful rescue?

The host is no longer failed — the recap shows rescued=1, failed=0 and the play continues.

Correct for a genuine recovery, dangerous if the rescue only logs, because a real failure becomes a green build. If it does not restore good state, end it with fail. (Module 03)

11. import_tasks versus include_tasks — three differences.

Import is parse-time and static; include is run-time and dynamic. Only include takes a variable filename or a loop. Only import is visible to --list-tasks and --start-at-task.

Plus: tags and when: are applied to every inner task on an import, but to the include as a unit on an include. (Module 03)

12. Where does a Jinja2 template render?

On the control node, always. The target receives finished text. That is why lookup('file', ...) inside a template reads the control node's filesystem, and why a value that exists only on the target must be captured with register first. (Module 04)

13. defaults/ versus vars/ in a role, and why it matters.

defaults/ is level 2 — designed to be overridden, so it is the role's public API. vars/ is level 15, above inventory and group_vars.

A user-facing setting placed in vars/ is silently un-overridable from inventory, producing hours of debugging on configuration that is actually correct. Almost everything belongs in defaults/. (Module 05)

14. What can a collection hold that a role cannot?

Modules, module_utils, and plugins of every kind — filter, lookup, inventory, callback, connection, action — plus playbooks. A role is limited to tasks, handlers, templates, files, defaults and vars. (Modules 06, 10)

15. What does Ansible Vault protect against, and what does it not?

Protects against someone reading your repository — AES256 at rest.

Does not protect against anyone who can run your playbooks, offers no rotation, audit or per-user access, values are plaintext in memory, and rekeying does not protect history. Real revocation means rotating the underlying secrets. (Module 07)

16. Three ways to reduce fact-gathering cost, in order of impact.

Fact caching with gathering = smart — removes it entirely on repeat runs. gather_subset excluding hardware, which probes block devices and typically halves it. gather_facts: false where no ansible_* variable is used.

And profile first — Gathering Facts is usually the largest single item but not always. (Module 08)

17. What is keyed_groups and what follows from it?

It turns instance tags into Ansible groups — tags.Role=web becomes role_web — so hosts: role_web stays correct as an autoscaling group changes size.

The consequence: your tagging discipline becomes your inventory, and an untagged instance is an unmanaged one. (Module 09)

18. Which plugin types run on the target?

Only modules. Filter, lookup, test, callback, inventory, action, connection and strategy plugins all run on the control node. (Module 10)

19. What can only Molecule prove?

Idempotency. Its idempotence step applies the role twice and fails the build on any changed, naming the task. No linter or --check run can establish that, because it requires actually applying it twice to something real. (Module 11)

20. What does AWX add that ansible-playbook does not?

Nothing to the run — same engine. It adds RBAC, a write-only credential store enabling execute-without-read, an audit trail with the exact commit SHA, scheduling, queueing, surveys and Execution Environments.

You adopt it when the constraint becomes "who may run this, and can we prove what happened". (Module 12)


Part C · Round 2 — Scenario design

Take 3–5 minutes each, out loud. Structure beats completeness — an interviewer is listening for the shape of your thinking.
S1. Roll out a config change to 500 web servers with zero downtime.

Batching: serial: [1, 10, "25%"] — a canary of one, then progressive batches. max_fail_percentage: 0 so the rollout halts at the first failure rather than marching through 500 hosts.

Per batch: drain from the load balancer with delegate_to: localhost, pause for connections to finish, deploy inside a block, meta: flush_handlers to apply the restart before verifying, health-check with until/retries, and always: to re-enable.

Rollback: rescue performs it and must end with fail — otherwise the recovered state clears the failure, max_fail_percentage never fires, and the rollout continues through a broken release.

Before any of it: --check --diff --limit one-host, and --list-hosts to confirm the blast radius.

The interaction between rescue clearing failure state and max_fail_percentage depending on it is the detail that marks experience. (Modules 03, 08)

S2. Give the support desk the ability to restart a production service, with no production access.

An AWX job template running a narrowly scoped playbook. A multiple-choice survey listing only the permitted services — not free text, because survey answers are extra vars at precedence 22 and free text would let them set any variable. ask_limit_on_launch: false so they cannot widen the host scope. The support team granted Execute on that one template and nothing else.

Defence in depth: an assert in the playbook validating the service name, because the survey constrains the UI but not the API, which someone with a token could call directly.

They can launch it; they cannot read credentials, edit the playbook, change the inventory, or run anything else — and every launch is attributed and logged. (Module 12)

S3. Your team's playbooks work locally and fail in CI. Design the fix.

First, diagnose the class: it is almost always environment, not logic. Three usual causes — the CI user has a different $HOME so the SSH key is not found; a Python library like boto3 is on the laptop and not the runner; or an unpinned collection resolved to a different version.

The incremental fix: pin everything in requirements.yml, commit it, install it as a CI step, and pin ansible-core in a requirements.txt.

The complete fix: an Execution Environment built with ansible-builder, pinning ansible-core, collections and Python dependencies into one image, run via ansible-navigator or AWX. That is the only answer that closes the gap entirely, because requirements.yml cannot pin the engine or the Python libraries. (Modules 06, 11)

S4. Manage 3,000 hosts across three clouds and two data centres.

Inventory: dynamic per source — aws_ec2, azure_rm, gcp_compute — plus a static file for the data centres, merged by pointing -i at a directory with numeric filename prefixes, and a constructed source unifying them into common groups. Tagging becomes the contract, with default_value catching untagged hosts into a needs_attention group that CI fails on.

Performance: forks 50 with ulimit -n raised, pipelining on, fact caching with gathering = smart, gather_subset narrowed. Inventory caching too, since DescribeInstances is slow at that scale.

Structure: group_vars inside each inventory directory so environments are isolated by the filesystem rather than by a flag.

Execution: AWX with instance groups per network zone, so jobs run from a node that can actually reach the target — which also solves the bastion problem.

Honest caveat: at 3,000 hosts a push model is near its practical ceiling; ansible-pull becomes worth considering, at the cost of orchestration and central visibility. (Modules 06, 08, 09, 12)

S5. Design secrets management for a regulated environment.

Start by naming what Vault cannot do: no rotation, no audit trail, no per-user access, and rekeying does not protect history — all of which a regulator will ask about.

The design: secrets in HashiCorp Vault or AWS Secrets Manager, fetched at run time by a lookup or, in AWX, by a credential plugin so the secret never lives in AWX's database either. Ansible Vault retained only for low-sensitivity configuration, with the vault password itself fetched by a client script from the real secret store.

Run-time hygiene: no_log: true on every task touching a secret, loop_control: label: on loops over credential dictionaries, and never -vvv in production.

Access: AWX RBAC so operators get execute-without-read; every run attributed and retained.

Offboarding: rotate the underlying credentials, not just the vault password. (Modules 07, 12)

S6. A role you maintain is used by six teams. How do you change a default without breaking them?

Versioning is the mechanism. The role lives in a versioned collection; consumers pin ranges in requirements.yml. A changed default is a breaking change, so it is a MAJOR bump — shipping it as a minor silently breaks everyone who pinned correctly, which is how you lose a team's trust.

Migration path: keep the old behaviour available, add a deprecation warning with module.deprecate or a debug warning in the role, document it in the changelog, and give teams a release window before removal. meta/runtime.yml plugin_routing handles renames of modules and plugins the same way.

And test it: a Molecule upgrade scenario that converges the old version then the new one, proving the transition does not destroy existing state. (Modules 05, 06, 11)


Part D · Round 3 — Debugging

The interviewer shows you something broken. Say your method before your answer — that is what is being scored.
D1. Find every bug in this playbook.
yaml
---
- hosts: all
  vars:
    enable_ssl: "false"
    packages: [nginx, git]
  tasks:
    - shell: systemctl restart nginx

    - copy:
        src: app.conf
        dest: /etc/app.conf
        mode: 0644

    - package: name={{ item }} state=present
      loop: "{{ packages }}"

    - name: Configure SSL
      template:
        src: ssl.conf.j2
        dest: /etc/nginx/ssl.conf
      when: enable_ssl

    - user:
        name: deploy
        password: "{{ 'secret' | password_hash('sha512') }}"

Nine problems:

  1. Play has no name: — unreadable output, ansible-lint name[play].
  2. Tasks 1, 2, 3, 5 have no name: — same, plus --start-at-task becomes unusable.
  3. No FQCNs — ambiguous, linted against since 2.10.
  4. shell: systemctl restart nginx — use the service module; also not idempotent and has no changed_when.
  5. mode: 0644 unquoted — can produce the wrong permission.
  6. loop over package — the module takes a list; this is three round trips instead of one.
  7. when: enable_ssl on the string "false" — truthy, so SSL config deploys when it should not. Needs | bool.
  8. password_hash with no salt — a new hash every run, so the password resets on every single execution.
  9. No no_log on the user task — the hash reaches the log.

Method to state first: "I'd run ansible-lint which catches six of these mechanically, then look for the two it cannot — the truthy string and the unsalted hash — because those produce green runs with wrong outcomes." (Modules 01, 02, 03, 04, 07, 11)

D2. "It worked yesterday. Today: Permission denied (publickey) on every host."

Method: classify first — this is UNREACHABLE, so the module never ran and the playbook cannot be the cause.

Then, in order:

  1. ssh -vvv deploy@host — reproduce outside Ansible. If that fails too, Ansible is irrelevant.
  2. ansible web01 -m ping -vvv | grep IdentityFile — which key did it actually offer?
  3. Is someone running it under sudo? That changes $HOME to /root and Ansible reads /root/.ssh. This is the single most common cause of "it worked yesterday", because the difference is in how it was invoked, not what changed.
  4. Same question for CI — a runner executes as a different user with a different home.
  5. Permissions: ~ no looser than 755, ~/.ssh 700, authorized_keys 600.
  6. journalctl -u sshd on the targetsshd will not tell the client why it refused; the real reason is only there.
  7. Did the host get rebuilt? Then it is a host-key problem wearing a publickey error's clothes.

(Modules 01, 13)

D3. "The nightly job reports success but nothing is patched."

This is the dangerous category — green run, wrong outcome, so nothing announces itself.

Method: establish what would run before asking why it did not work.

bash
ansible-playbook patch.yml --list-tasks
ansible-playbook patch.yml --list-tasks --tags patch     # compare the two
ansible-inventory -i inventory/ --graph                   # are the hosts even there?

Four candidates, in likelihood order:

  1. A tag on an include_role that never reached the inner tasks — the play runs, reports ok=1, executes nothing. Needs apply:.
  2. An empty inventory — "no hosts matched" is a successful run. Usually an inventory plugin filename problem; confirm with -vvvv | grep declined.
  3. A when: on a string "false" or an undefined variable skipping everything.
  4. A rescue that logged and did not re-raise — check the recap for rescued= and ignored= counts.

Then the question that solves most incidents: git log --since="when it last worked", including requirements.yml, since an unpinned collection can change behaviour with no commit to your code. (Modules 03, 05, 09, 13)

D4. "This template task reports changed on every run."

Method: --diff first. It shows the exact line that differs, which usually names the cause immediately.

Four causes:

  1. A timestamp in the content — including a customised ansible_managed containing one.
  2. password_hash without a fixed salt — new hash every render.
  3. Unsorted iteration over a dictionary or a set, where ordering is not stable between runs. Fix with | dictsort or | sort.
  4. random without a seed.

Why it matters beyond tidiness: the task fires its handler every run, so the service restarts nightly for no reason, and changed=0 stops being a meaningful drift signal in CI. (Module 04)


Part E · Round 4 — System design

X1. Design Ansible adoption for a company with no automation, 400 servers, and eight engineers.

This question is about sequencing and people, not features. A candidate who starts with roles and collections has missed it.

Phase 1 — visibility, change nothing. Build the inventory first, dynamic where the estate is cloud. Run read-only ad-hoc commands to answer questions people already have: what OS, what versions, who is out of disk. This earns trust and produces a correct inventory as a by-product.

Phase 2 — one painful, low-risk task. Pick something the team does manually and dislikes — user onboarding, certificate renewal. One playbook, in Git, reviewed. Not the whole estate.

Phase 3 — structure. Repository layout with per-environment inventories, group_vars inside each, roles for things now used twice, requirements.yml pinned. Lint in CI at min, raised over time.

Phase 4 — confidence. Molecule on the roles that matter, --check --diff against staging automatically, manual gate on production.

Phase 5 — platform. AWX when the constraint becomes access and audit rather than capability — RBAC, credentials off laptops, scheduling, surveys for the support desk.

The things to volunteer: never start with the biggest problem; a failed first automation sets adoption back a year. Automate what you understand, not what you wish you understood. And the hardest part is not Ansible — it is persuading people to stop making manual changes, which is why read-only visibility first matters so much.

X2. Ansible or Terraform or Puppet — when each, and can you justify not choosing Ansible?

Terraform for resource lifecycle — it holds state, so it can diff and destroy. Ansible holds none, so it cannot reliably destroy or detect orphans. Provision with Terraform, configure with Ansible, joined by tags and dynamic inventory. Avoid local-exec calling ansible-playbook: Terraform then owns a step it cannot observe, retry or roll back.

Puppet or Chef when you need continuous drift correction with no central scheduler — an agent re-applying state every 30 minutes — or at a scale where push saturates the control node. Ansible's push model is a poor fit for tens of thousands of nodes.

Ansible for orchestration across hosts, network devices, ad-hoc operations, and low onboarding cost — agentless matters enormously when you cannot install software on the targets.

Being willing to say "not Ansible" is the point of the question. A candidate who claims Ansible is always right has not operated at a scale where it stops being.

X3. How do you make automation safe when anyone can run it?

Layered, and it is mostly not technical.

Prevent: RBAC in AWX — execute-without-read on credentials, templates scoped narrowly, surveys with multiple-choice rather than free text. assert blocks validating inputs at the API layer, not just the UI.

Constrain the blast radius: serial with max_fail_percentage: 0, --limit in job templates, approval nodes before production workflow nodes.

Verify before acting: --check --diff against staging automatically in the pipeline, and as a personal habit before any unfamiliar production run.

Prove after: idempotent playbooks so changed=0 is a meaningful drift signal, an audit trail with the commit SHA, and notifications on failure.

And the cultural part: the automation must be easier than doing it by hand, or people route around it — which is how you get a fleet where half the changes are manual and the inventory is fiction.

X4. What would you fix first in an Ansible repository you have just inherited?

Look before judging. ansible --version, ansible-config dump --only-changed, ansible-inventory --graph, --list-tasks, and git log to see how it has been maintained.

Then, in priority order:

  1. Secrets in plaintext. git grep -nE '(password|token|secret)\s*:\s*[^"{ ]'. If anything turns up, the credential must be rotated, not merely encrypted — the history retains it.
  2. Unpinned dependencies in requirements.yml — the repo is not reproducible and breaks on someone else's machine for no visible reason.
  3. group_vars at the repository root rather than per-inventory — environments are silently sharing variables.
  4. ansible.cfg defaulting the inventory to production — a forgotten -i becomes an incident.
  5. Lint in CI at min, raised over time — stop it getting worse before improving it.
  6. Molecule on the two or three roles that touch production most.

Deliberately not first: rewriting playbooks into elegant roles. It is the most satisfying and the least valuable — nothing on that list is about style, and all of it is about risk.


Part F · Closing the loop

F1 · The answers worth having ready verbatim

If askedLead with
"Explain Ansible"Push-based, agentless, declarative-ish, orchestration — then unpack each word
"What happens when a task runs"The ten steps, emphasising the target's Python
"Variable precedence"Role defaults lowest, extra vars highest, specific beats general — plus the vars/ versus defaults/ asymmetry
"How do you know it is idempotent"Molecule's idempotence step, on every commit
"Zero-downtime rollout"serial canary + max_fail_percentage: 0 • block/rescue/always + fail in the rescue
"How do you debug"Classify into parse / connection / execution / logic first
"Secrets"What Vault cannot do, then the real secrets manager, then no_log
"Have you written a module""Rarely, and deliberately" — then the four things you check first

F2 · The questions to ask them

Good questions demonstrate more than good answers, because they reveal what you consider normal.
  • "How do you test Ansible changes before production — Molecule, a staging run, or --check?"
  • "Is your inventory static or dynamic, and how do you handle drift between it and reality?"
  • "Where do secrets live — Ansible Vault, or something with rotation and audit?"
  • "Who is allowed to run automation against production, and how is that enforced?"
  • "Is your Ansible repository pinned and reproducible, or does it depend on what is installed locally?"
  • "How much of the estate is still changed by hand?"

That last one is the most revealing question you can ask, and the answer tells you what the job is actually going to be.

F3 · Final self-assessment

You are ready when you can answer all twenty rapid-fire questions in under a minute each without opening a toggle, structure any three scenario answers with the trade-offs volunteered rather than extracted, state the four debugging categories and the first move for each, and name at least one concrete failure you have personally caused or fixed.

That last item is the one that cannot be revised for — which is why the exercises throughout this track were built around deliberately breaking things.


That completes the track. Modules 01–13 build the knowledge; this module converts it into recall.

The Daily Life Commands page collects every ⭐-marked command from all fourteen modules into one reference for actual work.

📚 Sources

Question selection cross-referenced against publicly published 2026 Ansible interview question sets:

All technical content verified against the official Ansible documentation. Answers were written rather than reproduced — published versions are usually correct but shallow, and the operational detail is what differentiates a candidate in the room.

Spotted a mistake or want something added? Send me a note.