Ansible Interview Questions: Real DevOps Practice Quiz

Reviewed by Mark Dickie · Last updated

13 questions on this page you can answer and see marked on the spot. Go to the first one

Ansible is an open-source configuration management and automation tool that uses declarative YAML playbooks to define the desired state of systems across one or many hosts. For an interview, you should know how playbooks, roles, and modules fit together, how inventory grouping resolves variables per host, and how Jinja2 templating turns those variables into config files. Interviewers also test whether you understand idempotency, the difference between check mode and a live run, and when to pick Ansible over Terraform or a plain shell script.

What topics does an Ansible interview cover?

ConceptWhat to know
PlaybooksYAML files defining ordered task lists; understand handlers, tags, and conditionals
RolesReusable units bundling tasks, handlers, vars, and files; know the default directory layout
InventoryStatic or dynamic host lists; know how group vars and host vars resolve
ModulesThe building blocks of tasks; be able to describe common ones like copy, template, apt, service
VariablesDefined at multiple precedence levels; know how Jinja2 pulls them into templates

How should you prepare for an Ansible interview?

  1. Write a role from scratch that installs and configures a web server, then run it with ansible-playbook --check to confirm idempotency.
  2. Study variable precedence: know the order from host vars up through extra vars passed on the command line.
  3. Practice reading and debugging failed task output, because interviewers often hand you a broken playbook and ask you to fix it.
  4. Learn when Ansible is the wrong tool: provisioning fresh cloud infrastructure is usually Terraform territory, while configuring what already exists is where Ansible is strongest.
  5. Review dynamic inventory plugins for AWS or VMware, since real environments rarely rely on a static hosts file.

Key facts

  • Tarmac has 141 Ansible interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
  • Tarmac tracked 492 job postings asking for Ansible in September 2026.
  • Roles asking for Ansible advertise a median base salary of £75,000, across 88 job postings as of September 2026.
  • Tarmac last reviewed these Ansible interview questions on 20 October 2026.

At a glance

Questions25 shown · 141 in the bank
Difficulty1–5 of 5
FormatsTrue / false, Multiple choice, Short answer, Ordering, Fill in the blank, Multiple answer, Flashcard, Design exercise

What you'll review

  1. ansible vault secrets
  2. ad hoc commands ansible
  3. ansible tower awx concepts
  4. error handling and idempotent retries
  5. dynamic inventory ansible

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

Ansible Vault is an Ansible feature that encrypts sensitive data such as passwords, certificates, and SSH keys so that the data can be safely committed to version control.#

Options

Show answer

True. Ansible Vault is a built-in feature that encrypts sensitive data — passwords, certificates, SSH keys, and other secrets — so those values can be safely stored in files checked into version control. The encrypted content is decrypted at runtime using the vault password.

Why:

Ansible Vault is a built-in Ansible tool designed to encrypt and decrypt sensitive files and variables, allowing teams to store secrets in playbooks or variable files that are checked into version control without exposing the plaintext values.

Ansible Vault uses asymmetric encryption with a public/private key pair to encrypt and decrypt secret data.#

Options

Show answer

False. Ansible Vault uses symmetric AES-256 encryption driven by a single shared password — the same password both encrypts and decrypts the data. It does not use a public/private key pair, which is the hallmark of asymmetric encryption schemes like RSA or SSH keys.

Why:

Ansible Vault uses symmetric encryption (AES256) with a single shared password — the same password encrypts and decrypts the data. It does not use a public/private key pair, which is the hallmark of asymmetric encryption.

You want to run an Ansible ad-hoc command that invokes the ping module against every host listed in your default inventory. Which of the following is the correct syntax?#

Options

Show answer

The correct syntax is ansible all -m ping. Ad-hoc commands use the ansible CLI, all targets every host in the inventory, and the -m flag specifies the module to run — in this case, ping. ansible-playbook is for running playbook files, and -a is for passing arguments to a module, not for selecting one.

Why:

Ansible ad-hoc commands are executed with the ansible CLI (not ansible-playbook, which runs playbooks). The target group or pattern follows the command — all matches every host in the inventory. The -m flag specifies the module to run, so -m ping invokes the ping module. ansible-playbook all -m ping uses the wrong tool; ansible-playbook expects a playbook file, not a module argument. ansible all -a ping uses -a, which passes arguments to a module, not the module name itself. ansible ping --all uses a non-existent flag.

What is the relationship between AWX and Ansible Tower (now part of Red Hat Ansible Automation Platform)?#

Options

Show answer

AWX is the open-source upstream community project, while Ansible Tower (now Red Hat Ansible Automation Platform) is the commercially supported downstream product built from the AWX codebase. AWX releases first as the rapid-iteration community edition, and Tower/AAP packages a stabilized, enterprise-supported version of that same project.

Why:

AWX is the open-source community project that functions as the upstream for Ansible Tower. Ansible Tower (now part of Red Hat Ansible Automation Platform, or AAP) is the commercially supported, enterprise-hardened downstream product derived from AWX. They share the same code lineage — AWX releases first, and Tower/AAP stabilizes and supports a version of that codebase.

When you run a playbook that references an ansible-vault–encrypted file, which single ansible-playbook flag makes Ansible interactively prompt you for the vault password at runtime?#

Show answer

Pass --ask-vault-pass (or its short form -J) to ansible-playbook; Ansible will then prompt you to type the vault password before executing the playbook.

Why:

The --ask-vault-pass flag (short form -J) tells ansible-playbook to interactively prompt for the vault password before the run begins. Ansible uses that password to decrypt any vault-encrypted files it encounters. If the password is wrong, decryption fails before the first task runs.

Which ansible-vault subcommand changes the decryption password of an already-encrypted file without requiring you to first decrypt it to plaintext?#

Show answer

Use ansible-vault rekey <file>. It decrypts the file with the old password and re-encrypts it with a new password in one step, so the file never needs to be stored in plaintext on disk.

Why:

The ansible-vault rekey subcommand takes a vault-encrypted file, decrypts it with the current password, and immediately re-encrypts it with a new password — all in memory. This avoids the security risk of writing the plaintext to disk, which is what you'd have to do if you manually decrypted and then re-encrypted with ansible-vault encrypt.

Which ansible-vault subcommand encrypts a single variable value inline so the resulting ciphertext can be pasted directly into a YAML file, instead of encrypting an entire file?#

Show answer

Use ansible-vault encrypt_string. It encrypts a single string value and outputs YAML with a !vault tag and the ciphertext, which you can paste directly into a variables file or group_vars.

Why:

The ansible-vault encrypt_string subcommand encrypts one value at a time and emits YAML containing the !vault-tagged ciphertext. Unlike ansible-vault encrypt, which encrypts a whole file, encrypt_string lets you keep most of a variables file human-readable while protecting only sensitive values, and the ciphertext integrates seamlessly into existing YAML structures.

An Ansible task polls a service health endpoint with retries: 3, delay: 2, register: result, and until: result.stdout == "HEALTHY". The task also sets changed_when: false so that polling never registers as a configuration change. The service never returns HEALTHY. What is the final outcome?#

Options

Show answer

The task retries 3 times and then fails, halting play execution unless ignore_errors: yes or a rescue block handles it. The retries parameter caps the number of retry attempts, and until is re-evaluated after each one. When until is never satisfied within the retry budget the task is marked failed — changed_when: false only suppresses the changed/ok status and has no effect on the failure caused by the unsatisfied until condition.

Why:

retries: 3 caps the number of additional attempts at three. After each attempt Ansible evaluates the until condition against the registered result; when the condition is never satisfied and the retry budget is exhausted, the task is reported as failed. changed_when: false controls only the changed/ok status — it does not suppress the failure that results from until not being met. Without ignore_errors: yes or a block/rescue handler, Ansible halts execution on the failed task. The claim that the task is marked changed because it performed multiple attempts is wrong because changed_when: false prevents the changed status. The claim that the task is marked ok after the first attempt because changed_when: false suppresses both the changed status and the failure is wrong because changed_when does not affect failure semantics. The claim that the task retries indefinitely until the service becomes healthy is wrong because retries: 3 bounds the total attempts; Ansible does not loop indefinitely.

Order the steps below to show the sequence in which Ansible processes a dynamic inventory plugin during a single playbook run — from the moment the ansible-playbook command is issued to the moment the first task is sent to a managed host.#

Put these in order

Show answer

Ansible first reads its configuration and -i arguments to identify inventory sources, then loads the dynamic inventory plugin, calls its parse() method to query the external source and populate the in-memory inventory, matches the play's host pattern against that populated inventory, and finally opens connections and sends the first task to each matched host. Each step depends on the previous one completing first.

Why:

Configuration is read first so Ansible knows which inventory sources to load. The inventory manager then discovers and loads the appropriate plugin for that source. The plugin's parse() method runs, querying the external source and populating the in-memory inventory with hosts and groups. Only after the inventory is populated can the play's host pattern be matched. Finally, connections are opened and the first task is executed on matched hosts. Each step strictly depends on the previous one completing.

Order the steps below to show the correct workflow for creating and deploying a custom dynamic inventory script in a classic (pre-plugin) Ansible setup.#

Put these in order

Show answer

Write the inventory script to emit JSON in Ansible's expected format (top-level groups and a _meta section), make it executable with chmod +x, test it by running ./script.py --list to verify the JSON, reference it as an inventory source via -i or ansible.cfg, and then run the playbook. Each step builds on the prior one — the script must exist, be executable, be tested, be configured, and only then be invoked by a playbook run.

Why:

The script must be written first — without a valid script there is nothing to run. Making it executable is required before it can be invoked as ./dynamic_inv.py --list for testing, since Ansible and the test command both rely on the executable bit to run it as a subprocess. Once the JSON output is verified, the script is registered as an inventory source in Ansible's configuration. Only then can the playbook be run, which triggers Ansible to execute the script, parse the JSON, and proceed against the discovered hosts.

Place the following steps in the correct order of the Ansible dynamic inventory plugin lifecycle, from plugin discovery through inventory availability to the playbook.#

Put these in order

Show answer

The correct order is: (1) Ansible reads the inventory source file and identifies the plugin: key, (2) Ansible loads the plugin class, (3) Ansible calls the plugin's parse() method, (4) the plugin populates InventoryData with hosts, groups, and variables, and (5) the populated inventory becomes available to the playbook. Each step depends on the completion of the previous one — you cannot call a plugin you have not loaded, and you cannot use inventory data that has not yet been populated inside parse().

Why:

Each step has a strict dependency on the previous one. Ansible must first read the inventory source file to find the plugin: key, which tells it which plugin class to load. The plugin class must be loaded before Ansible can call parse() on an instance of it. Inside parse(), the plugin fetches data from the external source and populates the InventoryData object with the resolved hosts, groups, and variables — this population is the output of parse(), so it strictly follows the method call. Only after parse() completes does the populated inventory become available to the playbook for host selection and task execution. The previous version of this question split the internal parse() work into separate 'read config' and 'fetch data' steps that are not universally ordered, since a plugin author may interleave them freely within parse(); the revised steps use the framework-level boundaries that have an uncontested causal order.

In Ansible, the command ansible-_____ --list is used to display the complete inventory tree — all groups, hosts, host variables, and group variables — exactly as resolved from a dynamic inventory source. It is the recommended tool for debugging and verifying the output of an inventory plugin or a legacy inventory script before running a playbook.#

Show answer

In Ansible, the command ansible-**inventory** --list is used to display the complete inventory tree — all groups, hosts, host variables, and group variables — exactly as resolved from a dynamic inventory source. It is the recommended tool for debugging and verifying the output of an inventory plugin or a legacy inventory script before running a playbook.

Why:

The ansible-inventory command (introduced in Ansible 2.4) is the standard tool for inspecting inventory. With the --list flag it outputs the fully-resolved inventory as JSON, including all groups, hosts, and variables — regardless of whether the source is a static file, an inventory plugin, or a legacy executable script. This makes it the go-to debugging tool for dynamic inventory at scale.

To enable a dynamic inventory plugin in Ansible (for example aws_ec2 or gcp_compute from a collection), you must list it under the enable______ directive inside the [inventory] section of ansible.cfg. This directive specifies the ordered list of inventory plugins Ansible will attempt to use when parsing inventory sources. If a plugin is not listed there, Ansible will not load it even if the plugin code is installed and available on the controller.#

Show answer

To enable a dynamic inventory plugin in Ansible (for example aws_ec2 or gcp_compute from a collection), you must list it under the enable_**plugins** directive inside the [inventory] section of ansible.cfg. This directive specifies the ordered list of inventory plugins Ansible will attempt to use when parsing inventory sources. If a plugin is not listed there, Ansible will not load it even if the plugin code is installed and available on the controller.

Why:

The enable_plugins directive in the [inventory] section of ansible.cfg controls which inventory plugins are active and the order in which Ansible tries them. Without an entry in this list, a dynamic inventory plugin — even if installed — will not be invoked, causing Ansible to fall back to the default advanced_hosts_list or yaml plugin and ignore the dynamic source entirely.

When a legacy dynamic inventory script is called with the --list argument, it can include a top-level key named _____ containing a sub-key called hostvars with all per-host variables pre-populated. When this key is present, Ansible skips calling --host <hostname> for each individual host, avoiding N+1 script invocations and dramatically improving performance at scale when managing thousands of hosts.#

Show answer

When a legacy dynamic inventory script is called with the --list argument, it can include a top-level key named **_meta** containing a sub-key called hostvars with all per-host variables pre-populated. When this key is present, Ansible skips calling --host <hostname> for each individual host, avoiding N+1 script invocations and dramatically improving performance at scale when managing thousands of hosts.

Why:

The _meta top-level key with its hostvars sub-key is an optimization in the dynamic inventory script contract. Without it, Ansible must call --host <hostname> for every host to gather that host's variables, resulting in one invocation per host. By embedding all host variables under _meta.hostvars in the --list output, Ansible can read everything in a single call — critical when the inventory source is a cloud API or CMDB that is slow or rate-limited.

You are designing an Ansible Tower / AWX deployment and reviewing its core building blocks. Which of the following statements about Tower/AWX components are correct?#

Options

Pick every one that applies.

Show answer

Ansible Tower/AWX Job Templates bind a playbook (from a Project) to an Inventory and a Credential; Workflow Templates chain Job Templates with success/failure branching; and Surveys collect extra_vars at launch. Credentials are encrypted at rest, not plaintext, and dynamic inventories can be scheduled or synced on project update—not only manually.

Why:

A Job Template is the Tower/AWX resource that binds a playbook (from a Project) to an Inventory and a Credential, plus other launch-time settings. Workflow Templates orchestrate multiple Job Templates as a directed graph with success/failure branching. Surveys are launch-time forms that collect extra_vars. c is incorrect: Tower encrypts credentials at rest (AES-256). e is incorrect: dynamic inventory sources in Tower can be synced on a schedule, on Project update, or via the API—not only by manual admin sync.

When running a playbook against a large fleet of hosts, which of the following configuration choices genuinely help reduce overall playbook runtime or improve control?#

Options

Pick every one that applies.

Show answer

strategy: free lets each host proceed independently; serial batches hosts for controlled rolling updates and limits the blast radius of failures; and fact caching (Redis or JSON files) avoids re-gathering facts every run when gather_facts is on. Pipelining reduces per-task SSH overhead but does not eliminate the value of ControlMaster/ControlPersist, and increasing forks past the host count yields sub-linear, not linear, gains.

Why:

With strategy: free, hosts no longer synchronize at each task boundary, so faster hosts are not gated by slower ones. serial batches hosts for rolling updates and limits how many hosts a single failed task can affect. Fact caching (Redis or a JSON-file backend) lets gather_facts reuse cached facts instead of collecting them every run. c is wrong because pipelining reduces the number of SSH operations per task but does not remove the value of ControlMaster/ControlPersist; the two are independent mechanisms. e is wrong because throughput from forks is bounded by CPU, network, and SSH/session limits, so gains are sub-linear past the host count.

You are configuring role-based access control (RBAC) in Ansible Tower/AWX. Which of the following statements about Tower/AWX RBAC are correct?#

Options

Pick every one that applies.

Show answer

Organizations are Tower's top-level scope for users, teams, projects, and inventories; the Use role on a credential (granted to a Team) permits attaching it to a Job Template; and Tower RBAC is per-resource—admin on one Inventory does not transfer to others. The Auditor role is read-only and cannot launch or modify jobs, and credentials are not auto-shared inside an Organization without an explicit Use grant.

Why:

Organizations are the top-level tenancy scope for Users, Teams, Projects, and Inventories. The Use role on a credential is what permits a user (directly or via Team membership) to attach it to a Job Template—without Use, the credential is not selectable. RBAC is per-resource, so admin rights are scoped per Inventory/Project/Credential, not inherited across resources. c is wrong because the Auditor role is read-only across the deployment and cannot launch or modify jobs. e is wrong because credentials require an explicit Use (or Admin) grant; they are not auto-shared inside an Organization.

What cryptographic scheme does Ansible Vault use to protect secrets, and how is the vault password strengthened against brute-force attacks?#

Show answer

Ansible Vault encrypts with AES-256 in CTR mode and authenticates with HMAC-SHA256. The vault password is stretched via PBKDF2-HMAC-SHA256 (since Ansible 2.6) using a per-file random salt, deriving separate AES and HMAC keys. The file begins with the header $ANSIBLE_VAULT;1.1;AES256 followed by hex-encoded salt, HMAC digest, and ciphertext.

Why:

This flashcard tests senior-level knowledge of Ansible Vault's internal cryptography. Ansible Vault uses AES-256 in CTR mode for confidentiality and HMAC-SHA256 for integrity/authentication. Key derivation moved from a plain SHA-256 hash to PBKDF2-HMAC-SHA256 in Ansible 2.6, using a random salt stored per vault file. The vault file format 1.1 stores a header line, then hex-encoded salt, HMAC, and ciphertext. Understanding this is essential for reasoning about security posture, key rotation, and compliance at scale.

When multiple --vault-id flags are supplied (e.g., --vault-id dev@dev_pw --vault-id prod@prod_pw), how does Ansible decide which password to try when decrypting a vault-encrypted file, and what configuration changes this behavior?#

Show answer

Each vault-encrypted file carries a vault ID label in its header (e.g., dev, prod). On decrypt, Ansible first tries the password whose label matches the file's vault ID, then falls back to trying every other supplied vault password. Setting DEFAULT_VAULT_ID_MATCH = True in ansible.cfg disables the fallback so only the matching label's password is attempted—preventing accidental cross-environment decryption when many vault passwords are in play.

Why:

This flashcard covers the vault ID matching mechanism that becomes critical at scale when many vault passwords coexist. The vault ID label is embedded in the encrypted file header. By default Ansible tries the matching label first, then falls back to all other passwords. DEFAULT_VAULT_ID_MATCH = True restricts decryption to only the matching vault ID, which prevents a developer's vault password from silently decrypting production secrets.

In Ansible Automation Platform 2 (and AWX 19+), execution environments — OCI-compliant container images built with ansible-builder that bundle ansible-core, collections, and system-level dependencies — replaced the older custom-virtualenv mechanism as the sole mechanism for playbook execution isolation; virtualenvs are no longer supported for job execution in this platform generation.#

Options

Show answer

True. Ansible Automation Platform 2 (AWX 19+) replaced the legacy custom-virtualenv isolation model with execution environments — OCI-compatible container images built with ansible-builder that bundle ansible-core, collections, Python, and system-level dependencies. Virtualenvs are no longer supported for job execution isolation in AAP 2; EEs are the sole mechanism.

Why:

Prior to AAP 2 / AWX 19, Tower/AWX relied on custom Python virtualenvs on the control node to isolate dependencies for different job templates. AAP 2 introduced execution environments (EEs): OCI-compatible container images (built with ansible-builder, runnable via Podman or Docker) that encapsulate ansible-core, required collections, Python packages, and system-level OS dependencies. In AAP 2, virtualenvs are no longer a supported mechanism for execution isolation — EEs are the sole mechanism. This is a definitive, documented architectural change, not a configuration choice.

In Ansible Tower/AWX, when a job template has 'job slicing' set to a value of N, the controller partitions the inventory into N groups (as evenly as possible), launches a concurrent child job for each group — each running the same playbook against its inventory subset — and aggregates all child-job results under a single parent job record, subject to available capacity and license seat counts.#

Options

Show answer

True. Job slicing in Tower/AWX, when set to N, partitions the inventory into N evenly-sized subsets and launches a concurrent child job per slice — each running the same playbook against its inventory portion — with all results aggregated under a single parent job record, subject to available capacity and license seat counts.

Why:

Job slicing is a Tower/AWX feature specifically designed to parallelize runs against large inventories. When the slice count is set to N, the controller splits the inventory into N roughly-equal partitions and launches N child jobs (constrained by available capacity and licensed execution seats). Each child job runs the full playbook against only its assigned inventory subset. The parent job aggregates results and overall status from all child jobs into a single record. This is the definitive, documented behavior of the feature.

You are the platform lead for an organization running 50,000+ hosts across AWS, GCP, and an on-prem VMware vSphere estate, spread over 12 business units (tenants). The current setup uses hand-maintained static YAML inventory files and ad-hoc shell scripts for inventory generation. Changes take days to propagate, group membership is frequently stale, and ansible-inventory --list against the full fleet takes over 20 minutes.#

Show answer

I would replace the static YAML and ad-hoc scripts with native Ansible inventory plugins — one per cloud: awsec2 for AWS, gcp_compute for GCP, and vmware_vm_inventory for the vSphere estate. Each is configured via a per-tenant YAML inventory source file. Plugins run in-process, so they can use the Ansible cache framework directly and populate host_vars/_meta natively without the JSON re-parse overhead of an external script. They also support constructed features like keyed_groups and compose for tag-to-group mapping without a second pass.

For caching, I would enable cache_plugin: redis (or jsonfile on smaller tenants) on every inventory plugin with a 5-minute TTL. The cache key is scoped per tenant + region + source so a refresh for tenant Acme does not invalidate tenant Globex's cache. In CI, where freshness is critical, I set cache: false or pass --flush-cache to force a live API fetch; for interactive operator use, the cached data keeps ansible-inventory --list under 30 seconds instead of 20 minutes. The cache_connection points to a shared Redis instance reachable from all control nodes.

The parse-time bottleneck is serial pagination across three cloud APIs for 50k hosts. I would shard the inventory by region/account: each region in each cloud is its own inventory source file/plugin instance with a scoped regions or instance_filters parameter. In CI, the inventory refresh is a parallelized pipeline stage — one ansible-inventory --list --export per source, all writing to the shared Redis cache simultaneously. The vmware_vm_inventory and gcp_compute plugins support batch fetching natively; for awsec2, setting regions explicitly and using instance_filters limits each instance's scope.

For grouping, I'd use keyed_groups in each plugin's constructed section: keyed_groups: [{prefix: tenant, key: tags.tenant}, {prefix: env, key: tags.env}, {prefix: role, key: tags.role}, {prefix: region, key: placement.region}, {prefix: lifecycle, key: tags.lifecycle}]. This produces groups like tenant_acme, env_prod, role_web, region_us_east_1, lifecycle_in_service. Playbooks then use --limit 'tenant_acme:env_prod:role_web:lifecycle_in_service' to target the intersection. The lifecycle tag (ready, in_service, draining, decommissioned) is maintained by the provisioning pipeline and reflected live by the inventory plugin on each refresh.

For CI safety, all pipeline runs pass --limit to scope to the changed tenant and --check for dry-run validation, with ansible-inventory --graph --vars as a pre-flight step to catch stale or malformed inventory before any playbook executes. Destructive playbooks are gated behind AWX/AAP approval workflows. Write access to the inventory source configuration is restricted to the platform team via Git branch protection on the inventory repository, so tenants can tag their resources but cannot alter the inventory plugin configuration.

Why:

This is a staff-level design question because the candidate must reason about the interaction between Ansible's inventory plugin framework, its cache subsystem, cloud API pagination costs, and organizational multi-tenancy — none of which has a single canonical answer. The rubric rewards the specific, verifiable architectural decisions (plugin over script, Redis cache with TTL, keyed_groups from tags, sharded parallel refresh, CI gating) rather than vague scalability platitudes.

Your team manages fleet-wide OS patching and configuration enforcement for 10,000+ Linux hosts using Ansible. A single control node runs nightly playbooks in serial batches of 50 with the default forks: 5, and the full job takes 8+ hours. SSH connection setup is the dominant per-host cost, and many hosts are behind bastion/jump hosts. The dynamic inventory is correct and current, but the execution model is the bottleneck.#

Show answer

I would move from a single control node to AAP/AWX with distributed execution environments, routing jobs to multiple isolated nodes or containerized runners via capacity-based job routing. The fleet is sharded across N execution nodes by inventory group — each node is assigned one or more region/tenant groups from the dynamic inventory, and the job template's --limit ensures each node only operates on its shard with no overlap. This distributes both the control-plane load and the SSH fan-out. With 10 nodes, each handles ~1,000 hosts, bringing the total wall-clock from 8 hours to under 30 minutes.

On each execution node, I'd set forks: 200 (up from the default 5) with serial: "10%" for ordered patching. serial: "10%" means each batch processes 10% of the in-service hosts concurrently, with up to 200 forks available per batch. For patching where order does not matter (e.g., security baseline enforcement), I'd use strategy: free for maximum parallelism — each host runs through all tasks as fast as its own SSH allows without waiting for the rest of the batch. The trade-off: forks: 200 consumes memory on the control node (roughly 30-50 MB per fork with complex module sets), so I'd monitor RSS and tune down if the node approaches 80% memory utilization, with a fallback of forks: 100 + serial: "5%".

For SSH, I'd enable pipelining: true in ansible.cfg — this eliminates the per-task SCP of the module file by piping the module over the existing SSH stdin, cutting per-task overhead from ~200ms to near zero. I'd add ssh_args: '-o ControlMaster=auto -o ControlPersist=60s' for connection multiplexing so the SSH handshake happens once per host per playbook run, not once per task. For bastion hosts, ansible_ssh_common_args: '-J [email protected]' (ProxyJump) routes all SSH through the bastion without a separate config. I'd also set transport = ssh (openssh), not paramiko, which is deprecated, slower, and does not support pipelining.

The dynamic inventory would expose a serial_batch host var computed from the group size (e.g., 10% of the in-service count for the current role) and a patch_window group that defines the rollout order. Playbooks would use serial: "{{ serial_batch | int }}%" so the batch size is data-driven. Hosts with lifecycle: draining or decommissioned are excluded automatically via group membership. Playbooks use idempotent modules (package, template, lineinfile) with a --check pre-flight pass against the full inventory to validate what would change before the live run.

For visibility, I'd deploy a custom callback_plugin that emits per-host task results to Datadog/ELK in real time, alongside AAP job events for operator tracking. For rollback, each batch is wrapped in block/rescue: before the change, the playbook snapshots the current package set (e.g., rpm -qa output) to a temp file; on failure in the rescue block, it reinstalls the previous package versions and re-syncs configs from the last-known-good Git commit tag. max_fail_percentage: 5 on the playbook halts the entire rollout if more than 5% of the in-service fleet fails, preventing a bad change from propagating across the full estate.

Why:

This is a staff-level question because the candidate must reason about the interplay of Ansible's fork model, SSH transport internals (pipelining, multiplexing), distributed control-plane design, and data-driven batching — all under a hard latency budget. The rubric rewards concrete, verifiable architectural decisions (AAP isolated nodes, forks=200, pipelining, serial from inventory vars, callback + block/rescue) rather than generic 'use more parallelism' advice.

You run a large microservices platform with a mix of Kubernetes workloads and bare-metal VMs behind hardware and cloud load balancers. Each service instance moves through lifecycle states: provisioning → ready → in_service → draining → decommissioned. You need to orchestrate a rolling configuration change across 800+ in-service instances using Ansible, where a dynamic inventory reflects the current lifecycle state of each instance in real time.#

Show answer

I'd configure the dynamic inventory plugin to expose each instance's lifecycle state as a host fact (lifecycle: in_service) and use keyed_groups to create Ansible groups per state: lifecycle_ready, lifecycle_in_service, lifecycle_draining, lifecycle_decommissioned. The inventory plugin — either a custom Python plugin or the constructed inventory applied on top of a cloud/CMDB plugin — queries the CMDB or cloud API for current state on each run, so the inventory reflects real-time lifecycle truth. Playbooks target the lifecycle_in_service group for the rolling change, with when: lifecycle == 'in_service' as a per-host guard. Hosts in draining or decommissioned are automatically excluded from the change by group membership — they are not in lifecycle_in_service.

For the rolling update, I'd set serial: "25%" on the lifecycle_in_service group in the playbook. This means Ansible processes at most 25% of the current in-service instances per batch, with forks high enough to run all hosts in each batch concurrently. The N% cap is relative to the in-service count because serial is applied to the lifecycle_in_service group — not the full inventory — so if 800 instances are in-service, each batch is 200 hosts. If the fleet shrinks to 400 in-service, the batch automatically shrinks to 100.

For load balancer integration, the playbook per batch is structured as a sequential series of plays/tasks:

  1. Drain: Set the instance to draining in the LB API — for AWS, community.aws.elb_target with state: draining on the target group; for K8s, kubectl cordon the node or scale the Deployment; for F5, the f5networks.f5_modules module to set the pool member to disabled. Then wait_for with a loop checking active connection count to drain to zero before proceeding.
  2. Change: Apply the config change with an idempotent module (template, k8s, copy) — the actual rolling config update.
  3. Health check: uri module against the instance's health endpoint with status_code: 200 and retries: 10 — wait for the service to report healthy before re-adding.
  4. Re-add: Set the instance back to in_service in the LB — community.aws.elb_target with state: present, or kubectl uncordon, or F5 pool member enabled.

For rollback, each batch is wrapped in block/rescue: if the health check in step 3 fails, the rescue block reverts the config change (re-applies the previous template version from the last-known-good Git tag), re-adds the instance to the LB in its previous state, and marks the host as failed in a hostvars-based tracking var. max_fail_percentage: 5 on the playbook means that if more than 5% of the in-service fleet fails health checks, the entire playbook halts — no further batches run — preventing a bad change from rolling across the full estate.

For idempotency, the playbook uses when: "'lifecycle_in_service' in group_names and not (config_version | default('none')) == target_version" to skip hosts that have already been successfully updated to the target config version. A per-host fact (written to a fact cache or a state file on the host) records the applied config_version. On re-run against the dynamic inventory, hosts already at the target version are skipped entirely — no drain, no change, no re-add — so the playbook reconciles drift without touching healthy, already-updated hosts.

Why:

This is a staff-level question because it requires the candidate to integrate three systems — dynamic inventory lifecycle state, Ansible's serial/strategy execution model, and external load balancer drain/health APIs — into a single coherent orchestration with rollback safety. The rubric rewards the specific, verifiable design decisions (keyed_groups from lifecycle, serial as percentage on in_service group, explicit drain→change→health→re-add sequence with named modules, block/rescue with max_fail_percentage, idempotent skip via per-host config_version fact) rather than vague 'orchestrate a rolling deploy' advice.

Related interview questions

Job market

See ansible salaries and hiring demand from live job postings.

The other 116 questions

This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 116 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

Interview booked? Practise the job ad instead

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.