Ansible remains a widely used automation system for configuration, orchestration and infrastructure tasks. In September 2026, the current supported core line is ansible-core 2.21. Its agentless control model and YAML playbooks are approachable, but production interviews probe idempotency, precedence, secret exposure, partial failure and execution at scale.
This guide covers what actually comes up in DevOps interviews: not just syntax, but the patterns and principles that separate beginners from experienced practitioners.
Table of Contents
- Ansible Fundamentals Questions
- Ansible vs Other Tools Questions
- Inventory Management Questions
- Playbook Structure Questions
- Module and Task Questions
- Conditionals and Loops Questions
- Handlers Questions
- Roles and Galaxy Questions
- Variables and Precedence Questions
- Templates and Jinja2 Questions
- Ansible Vault Questions
- Best Practices and Patterns Questions
- Scaling and Performance Questions
- Debugging and Troubleshooting Questions
Ansible Fundamentals Questions
Understanding the core concepts of configuration management and Ansible's architecture is essential for any DevOps interview.
What is configuration management and why is it important?
Configuration management ensures servers are configured consistently and correctly across your infrastructure. Instead of manually SSHing into servers and running commands, you define the desired state in code, which can be version-controlled, reviewed, and automatically applied.
This approach improves repeatability, reviewability and auditability. Version control can restore an earlier automation definition, but it does not roll back target state automatically: package upgrades, database changes, external APIs and partial runs may need an explicit backward or forward recovery procedure. Re-running can correct modeled drift only where tasks actually describe and own that state.
What is Ansible and how does it work?
Ansible is an automation system that executes ordered tasks through modules and plugins. A control node commonly connects to POSIX targets over SSH and to Windows over WinRM or PSRP, while network devices and cloud APIs use their own connection or local execution plugins. Most managed POSIX modules require a compatible Python runtime; raw is a notable bootstrap exception.
The architecture consists of a control node (where Ansible runs), managed nodes (target servers), an inventory (list of managed nodes), playbooks (YAML files defining tasks), and modules (units of code that perform specific actions). This simplicity is one of Ansible's greatest strengths—if you can SSH to a server, Ansible can manage it.
flowchart LR
subgraph control["Control Node"]
A["Ansible<br/>Engine"]
end
subgraph managed["Managed Nodes"]
S1["Server 1"]
S2["Server 2"]
S3["Server 3"]
end
A -->|SSH| S1
A -->|SSH| S2
A -->|SSH| S3Why is Ansible agentless and what are the advantages?
Ansible's agentless architecture means no software needs to be installed on managed nodes—it connects via SSH (Linux) or WinRM (Windows). This design decision has significant implications for security, maintenance, and usability.
The design removes a persistent Ansible-agent lifecycle, but it does not eliminate attack surface. The control node or automation controller holds powerful credentials and code; SSH/WinRM/PSRP, bastions, host-key verification, temporary module files, privilege escalation, audit logs and target runtimes all need protection. Push execution also requires reliable reachability and deliberate scheduling; ansible-pull exists for a different ownership model but introduces its own distribution and trust questions.
Ansible vs Other Tools Questions
Understanding how Ansible compares to other tools shows architectural awareness that interviewers value.
How does Ansible compare to Puppet, Chef, and Salt?
The configuration management landscape includes several tools with different approaches. Understanding the architectural differences helps you choose the right tool for specific situations and demonstrates broad knowledge in interviews.
The products support more modes and integrations than a four-row stereotype captures. Compare the exact current editions by execution topology, convergence model, offline behavior, state/reporting, policy, secrets, target support, controller availability, ecosystem and team expertise. Syntax familiarity or the presence of an agent is only one operational dimension.
| Tool | Architecture | Language | Model |
|---|---|---|---|
| Ansible | Agentless (SSH) | YAML | Push |
| Puppet | Agent-based | Puppet DSL | Pull |
| Chef | Agent-based | Ruby | Pull |
| Salt | Agent or agentless | YAML | Push/Pull |
What is the difference between Ansible and Terraform?
This question comes up constantly because both tools are used in infrastructure automation, but they solve fundamentally different problems. Understanding this distinction is crucial.
Terraform builds a resource dependency graph and reconciles provider objects using recorded state. Ansible executes plays and ordered tasks, and modules commonly express desired state, but Ansible has no Terraform-style global state file; it still uses inventory, facts, registered results, caches and remote API state. Both can create cloud resources and configure systems. Choose ownership by lifecycle and provider semantics, and avoid two tools concurrently managing the same property.
| Aspect | Terraform | Ansible |
|---|---|---|
| Purpose | Infrastructure provisioning | Configuration management |
| Model | Resource graph and provider reconciliation | Ordered plays/tasks with module-specific convergence |
| State | Persisted Terraform state | Inventory/facts/results and live target state; no equivalent global state file |
| Scope | Strong provider-managed resource lifecycle | Configuration, orchestration and supported infrastructure modules |
| Idempotency | Built-in via state | Module-dependent |
Typical workflow:
# 1. Terraform creates infrastructure
terraform apply
# 2. Ansible configures it
ansible-playbook -i inventory configure.ymlInventory Management Questions
The inventory defines what Ansible manages. Understanding static and dynamic inventory patterns is essential.
What is an Ansible inventory and what formats does it support?
The inventory is a file or script that defines the hosts and groups Ansible will manage. It's the foundation of targeting—without it, Ansible doesn't know what servers to configure.
Ansible supports two main inventory formats: INI (simple, legacy format) and YAML (more structured, recommended for complex setups). The inventory also defines variables at the host and group level, enabling different configurations for different environments.
INI format:
# inventory/hosts.ini
[webservers]
web1.example.com
web2.example.com
web3.example.com
[databases]
db1.example.com
db2.example.com
[production:children]
webservers
databases
[webservers:vars]
http_port=80YAML format:
# inventory/hosts.yml
all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
vars:
http_port: 80
databases:
hosts:
db1.example.com:
db_port: 5432
db2.example.com:
db_port: 5432How do you organize host and group variables in Ansible?
Rather than cramming all variables into the inventory file, Ansible supports a directory structure that separates variables by host and group. This organization becomes essential as your infrastructure grows and you need to manage variables for different environments.
The convention is to create group_vars/ and host_vars/ directories alongside your inventory. Files in group_vars/ apply to all hosts in that group, while files in host_vars/ apply to specific hosts. This pattern keeps your inventory clean and variables organized.
inventory/
├── hosts.yml
├── group_vars/
│ ├── all.yml # All hosts
│ ├── webservers.yml # Webserver group
│ └── production.yml # Production group
└── host_vars/
├── web1.example.com.yml
└── db1.example.com.yml
# group_vars/webservers.yml
http_port: 80
nginx_worker_processes: auto
ssl_enabled: true
# host_vars/web1.example.com.yml
nginx_worker_processes: 4 # Override for this hostWhat is dynamic inventory and when would you use it?
Dynamic inventory plugins query cloud providers, CMDBs or other authorities instead of relying only on a hand-maintained host list. They are useful in changing environments, but are not mandatory if another controlled inventory pipeline produces the required snapshot.
Each inventory evaluation reflects the plugin's API view, permissions, filters and cache, so eventual consistency or bad tags can change the target set between runs. Restrict credentials and filters, sanitize dynamic group names, validate the resolved inventory before destructive operations, and prefer private addresses or bastions when the network design allows.
# aws_ec2.yml - AWS EC2 dynamic inventory plugin
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-west-2
filters:
tag:Environment: production
keyed_groups:
- key: tags.Role
prefix: role
- key: placement.availability_zone
prefix: az
compose:
ansible_host: private_ip_address# Using dynamic inventory
ansible-inventory -i aws_ec2.yml --list
ansible-playbook -i aws_ec2.yml playbook.ymlWhat inventory patterns can you use to target specific hosts?
Ansible provides powerful patterns for targeting subsets of your inventory. This is useful when you want to run a playbook against specific groups, combinations of groups, or individual hosts.
Understanding these patterns enables precise targeting for maintenance windows, rolling deployments, and testing changes on subsets before full rollout.
# Target specific group
ansible webservers -m ping
# Multiple groups (union)
ansible 'webservers:databases' -m ping
# Intersection (hosts in both groups)
ansible 'webservers:&production' -m ping
# Exclusion (webservers except web3)
ansible 'webservers:!web3.example.com' -m ping
# Regex pattern
ansible '~web[0-9]+\.example\.com' -m pingPlaybook Structure Questions
Playbooks are the core of Ansible automation. Understanding their structure is fundamental.
What is the structure of an Ansible playbook?
A playbook is a YAML file containing one or more plays. Each play targets a group of hosts and defines tasks to execute on them. Understanding the structure helps you organize automation effectively and troubleshoot issues.
The key components are: plays (target hosts with tasks), tasks (individual actions using modules), handlers (tasks triggered by notifications), and variables (data used in tasks and templates).
# playbook.yml
---
- name: Configure web servers
hosts: webservers
become: true
vars:
http_port: 80
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restartedWhat does "become" mean in Ansible and when do you use it?
The become directive requests privilege escalation through a configured become plugin, often sudo on POSIX. It does not grant privileges by itself and does not automatically apply to all connection or lookup activity. Use a dedicated automation identity, the least commands and target hosts required, protected credentials and auditable controller policy.
You can set become: true at play, block or task scope and specify become_user/become_method where needed. Task-level escalation limits exposure better than making every operation root, but keep the policy understandable and test failure behavior when elevation is unavailable.
Module and Task Questions
Modules are the building blocks of Ansible tasks. Knowing the right module for each job is essential.
What are the essential Ansible modules for package management?
Ansible provides collection-qualified modules for package managers plus ansible.builtin.package, which selects a platform implementation. A module can model desired package state, but repository metadata, versions, locks, maintainer scripts and state: latest still affect repeatability and rollout risk.
Use ansible.builtin.apt for Debian-family systems, ansible.builtin.dnf for current RPM/DNF systems, or ansible.builtin.package when the common option set is sufficient. Pin reviewed versions when reproducibility matters and separate repository updates from service rollout.
# Debian/Ubuntu
- name: Install packages
ansible.builtin.apt:
name:
- nginx
- postgresql
- python3
state: present
update_cache: true
# RHEL/CentOS
- name: Install packages
ansible.builtin.dnf:
name: nginx
state: present
# Generic (detects OS)
- name: Install package
ansible.builtin.package:
name: nginx
state: presentWhat is the difference between copy and template modules?
Both modules transfer files to managed nodes, but template processes Jinja2 templates while copy transfers files as-is. Understanding when to use each is important for configuration management.
Use copy for static files that don't need variable substitution. Use template when you need to inject variables, use conditionals, or generate dynamic content based on host facts or inventory data.
# Copy static file
- name: Copy config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart nginx
# Template with Jinja2
- name: Deploy config from template
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginxWhen should you use command or shell modules vs native modules?
Prefer a purpose-built module when it correctly models the resource and its check/diff behavior. ansible.builtin.command and ansible.builtin.shell report changed by default because Ansible cannot infer arbitrary command semantics, but creates, removes, changed_when and explicit probes can model the outcome. A purpose-built module is not automatically idempotent for every parameter or external API.
Use command rather than shell unless shell parsing, pipes or redirects are genuinely required; passing untrusted data through a shell adds injection risk. Add creates or removes only when that filesystem condition truly represents completion, and use changed_when: false only for a read-only command whose exit and output are also validated.
# Avoid when possible - not idempotent
- name: Run script
command: /opt/scripts/setup.sh
args:
creates: /opt/app/.installed # The script must create this durable marker on success.
# Shell for pipes and redirects
- name: Read filesystem usage
command: df -h /srv
register: disk_result
changed_when: false
failed_when: disk_result.rc != 0Conditionals and Loops Questions
Conditionals and loops enable dynamic playbook behavior based on facts and data.
How do you use conditionals in Ansible tasks?
The when directive enables conditional task execution based on variables, facts, or previous task results. This is essential for handling differences between operating systems, environments, or configurations.
Conditions can be simple boolean checks, comparisons, or complex expressions combining multiple conditions. You can also base conditions on registered results from previous tasks.
- name: Install Apache on Debian
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install Apache on RedHat
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
# Multiple conditions (AND)
- name: Configure production
template:
src: prod.conf.j2
dest: /etc/app/config
when:
- env == "production"
- ansible_memory_mb.real.total > 4096
# Based on previous task result
- name: Check if app exists
stat:
path: /opt/app
register: app_stat
- name: Install app
command: /opt/install.sh
when: not app_stat.stat.existsHow do you implement loops in Ansible?
The loop directive (replacing the older with_items) iterates over lists and dictionaries. This enables creating multiple users, installing multiple packages, or configuring multiple virtual hosts with a single task.
You can loop over simple lists, lists of dictionaries, or use filters like dict2items to iterate over dictionary key-value pairs. Loop control provides access to the index and other metadata.
# Simple list
- name: Create users
user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
# List of dictionaries
- name: Create users with groups
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: 'alice', groups: 'admin' }
- { name: 'bob', groups: 'developers' }
# Dictionary iteration
- name: Set sysctl values
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
loop: "{{ sysctl_settings | dict2items }}"
vars:
sysctl_settings:
net.ipv4.ip_forward: 1
net.core.somaxconn: 65535Handlers Questions
Handlers are special tasks that run only when notified, typically for service restarts.
What are handlers and how do they work?
Handlers are named tasks queued by notifications from changed tasks. They are commonly used for restarts, reloads and post-change validation.
The same handler normally runs once for a group of duplicate notifications and follows handler insertion order, not notification order. Ansible flushes handlers at defined points in play execution and meta: flush_handlers can add another boundary. If a later task fails before a pending handler runs, behavior depends on failure handling and force_handlers; design service safety explicitly.
tasks:
- name: Update nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- Validate nginx config
- Restart nginx
- name: Update SSL cert
copy:
src: ssl.crt
dest: /etc/nginx/ssl/
notify: Restart nginx # Same handler, runs once
handlers:
- name: Validate nginx config
command: nginx -t
changed_when: false
- name: Restart nginx
service:
name: nginx
state: restartedHow do you force handlers to run immediately?
meta: flush_handlers runs all currently pending handlers at that point. Use it when later tasks depend on their effects, but remember that it flushes the shared pending queue, not just the notification immediately above it.
This pattern is useful when subsequent tasks depend on the service being restarted, such as health checks or tests that need the new configuration active.
- name: Update config
template:
src: app.conf.j2
dest: /etc/app/config
notify: Restart app
- name: Force handlers now
meta: flush_handlers
- name: Continue with app running
uri:
url: http://localhost:8080/healthRoles and Galaxy Questions
Roles are Ansible's mechanism for reusable, shareable automation.
What is an Ansible role and how is it structured?
A role is a standardized directory structure that packages related tasks, handlers, variables, templates, and files into a reusable unit. Roles enable code reuse across projects and sharing through Ansible Galaxy.
The structure follows conventions that Ansible automatically understands: tasks go in tasks/, templates in templates/, variables in vars/ or defaults/, and so on. This organization makes roles self-contained and easy to understand.
roles/
└── nginx/
├── defaults/
│ └── main.yml # Default variables (lowest precedence)
├── vars/
│ └── main.yml # Role variables (high precedence)
├── tasks/
│ └── main.yml # Main task list
├── handlers/
│ └── main.yml # Handlers
├── templates/
│ └── nginx.conf.j2 # Jinja2 templates
├── files/
│ └── index.html # Static files
├── meta/
│ └── main.yml # Role metadata, dependencies
└── README.md
When should you use roles vs standalone playbooks?
Playbooks define orchestration across hosts and roles; roles package a reusable unit behind documented defaults, variables, handlers, files and dependencies. Even a small production play can orchestrate roles, while a one-off operation may remain a reviewed playbook. Choose the boundary from ownership and reuse rather than line count.
Repeated copied tasks are a signal to consider a role, collection or custom module. Keep the public variable interface small, use role defaults for caller overrides, avoid high-precedence role vars unless intentional, and test supported operating-system/version combinations.
How do you use Ansible Galaxy for role management?
Ansible Galaxy distributes community roles and collections, while private automation hubs or Git sources may serve controlled organizations. Treat third-party content as executable supply-chain input: review ownership, signatures or provenance where available, transitive dependencies, licenses and release history.
A requirements file records requested versions and sources, but reproducibility requires immutable pins, controlled repositories, integrity/provenance checks and a locked execution environment. A range such as >=5.0.0 allows future changes and is not a reproducible pin.
# Install role from Galaxy
ansible-galaxy install geerlingguy.nginx
# Install from requirements file
ansible-galaxy install -r requirements.yml
# Create role skeleton
ansible-galaxy init my_role# requirements.yml
roles:
- name: geerlingguy.nginx
version: "3.1.0"
- name: geerlingguy.postgresql
version: "3.4.0"
- src: https://github.com/org/ansible-role-app.git
scm: git
version: v1.2.0
name: app
collections:
- name: amazon.aws
version: "10.1.2" # Example only: pin a reviewed version current for your project.How do you use roles in a playbook?
Roles are invoked in the playbook using the roles directive. You can pass variables to customize role behavior and use conditionals to apply roles selectively.
Role dependencies can be defined in meta/main.yml, ensuring prerequisite roles run first. This enables composing complex configurations from smaller, tested components.
# playbook.yml
---
- name: Configure web servers
hosts: webservers
become: true
roles:
- nginx
- { role: app, app_port: 3000 }
- role: monitoring
vars:
monitoring_enabled: true
when: env == "production"Variables and Precedence Questions
Understanding variable precedence is crucial for predictable playbook behavior.
How does Ansible variable precedence work?
Do not begin with a memorized “22 levels” slogan. Ansible first distinguishes broad categories: configuration settings, command-line options, playbook keywords, variables and direct assignment. A command-line option such as -u overrides configuration but can still be overridden by a playbook keyword or connection variable; -e is special because it creates an extra variable.
Within variable precedence, role defaults are low and extra vars have the highest variable precedence. Inventory group/host variables, facts, play variables, role variables, block/task variables, include parameters, registered values and set_fact occupy documented positions, with additional rules for scope and inventory group hierarchy. “Host always beats group” is only a partial inventory rule and does not replace the complete precedence table.
Design a small public role interface in defaults/main.yml, avoid defining the same name in many layers, and inspect evidence instead of guessing:
ansible-config dump --only-changed
ansible-inventory -i inventory/production --host web1.example.com
ansible-playbook site.yml -l web1.example.com -vvvWhat are Ansible facts and magic variables?
Facts are information about managed hosts that Ansible gathers automatically at playbook start. They include OS details, network configuration, hardware information, and more. Magic variables are special variables Ansible provides for accessing inventory and runtime information.
Facts enable conditional logic based on the target system—install different packages on Debian vs RedHat, configure memory-appropriate settings, or target specific IP addresses.
# Gathering facts (automatic)
- name: Show OS info
debug:
msg: "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
# Useful facts
ansible_hostname # Short hostname
ansible_fqdn # Fully qualified domain name
ansible_default_ipv4.address # Primary IP
ansible_memtotal_mb # Total memory
ansible_processor_vcpus # CPU count
ansible_os_family # Debian, RedHat, etc.
# Magic variables
inventory_hostname # Name in inventory
groups['webservers'] # List of hosts in group
hostvars['web1'] # Variables for another host
ansible_play_hosts # All hosts in current playHow do you disable fact gathering to speed up playbooks?
Fact gathering adds a connection and discovery work per host. For plays that do not use gathered facts, disabling it can reduce startup work; measure the effect and remember that roles or conditionals may depend on facts indirectly. Fact caching or selected setup subsets may fit repeated runs better than a blanket disable.
Set gather_facts: no at the play level when you don't need facts. You can also gather facts selectively using the setup module with specific subsets if you only need certain information.
- name: Quick playbook
hosts: all
gather_facts: no # Skip if not needed
tasks:
- name: Just copy a file
copy:
src: file.txt
dest: /tmp/Templates and Jinja2 Questions
Templates enable dynamic configuration file generation.
How do you use Jinja2 templates in Ansible?
Templates use Jinja2 syntax to generate configuration files with dynamic content. Variables, loops, conditionals, and filters enable flexible configuration generation based on inventory data and facts.
Templates are stored with a .j2 extension in the templates/ directory of a role, or alongside playbooks. They're processed by the template module, which renders them with current variable values before copying to the target.
{# templates/nginx.conf.j2 #}
worker_processes {{ nginx_worker_processes | default('auto') }};
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
{% for site in nginx_sites %}
server {
listen {{ site.port | default(80) }};
server_name {{ site.domain }};
root {{ site.root }};
{% if site.ssl | default(false) %}
listen 443 ssl;
ssl_certificate {{ site.ssl_cert }};
ssl_certificate_key {{ site.ssl_key }};
{% endif %}
{% for location in site.locations | default([]) %}
location {{ location.path }} {
{{ location.config }}
}
{% endfor %}
}
{% endfor %}
}What are the most useful Jinja2 filters in Ansible?
Filters transform values in templates. Prefer fully qualified collection names when name collisions are plausible, understand native types, and do not apply default so broadly that a missing required configuration becomes silently valid.
Hashing a password in a template is not secret management: plaintext can still appear in variables, process memory or logs, and algorithm parameters must match the target system's current policy. Generate and rotate credentials through an approved secret workflow instead of copying a universal password_hash('sha512') snippet.
{{ variable | default('fallback') }}
{{ list | join(', ') }}
{{ string | lower }}
{{ string | upper }}
{{ path | basename }}
{{ path | dirname }}
{{ dict | to_json }}
{{ dict | to_yaml }}
{{ list | first }}
{{ list | last }}
{{ number | int }}
{{ value | bool }}Ansible Vault Questions
Vault encrypts sensitive data so secrets don't appear in plain text in repositories.
What is Ansible Vault and how do you use it?
Ansible Vault encrypts files or individual variables at rest. Decryption occurs on the control node during a run. Vault does not prevent a play, plugin, callback, debug task, process inspection or destination file from exposing plaintext, so use least privilege, no_log where appropriate, protected temporary/editor files and output review.
Ciphertext may be version-controlled when policy allows, but never store its password or an equivalent retrieval credential beside it. Prefer --vault-id with a prompt, a tightly protected password file, or a client script that obtains the secret from an approved secret manager. Rotate/re-key and separate trust domains so one credential does not unlock every environment.
# Create encrypted file
ansible-vault create secrets.yml
# Encrypt existing file
ansible-vault encrypt secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# View encrypted file
ansible-vault view secrets.yml
# Decrypt file
ansible-vault decrypt secrets.yml
# Encrypt single string
ansible-vault encrypt_string 'mysecret' --name 'db_password'What is a safe way to organize vault files?
A common pattern separates encrypted values from a plain-text variable interface, but file layout is not the security boundary. Use vault IDs and different protected credentials for trust domains, keep production decryption in the authorized execution environment, restrict repository and job access, rotate credentials, and review whether a dedicated runtime secret manager is a better source.
# group_vars/production/vault.yml (encrypted)
vault_db_password: supersecret
vault_api_key: abc123
# group_vars/production/vars.yml (plain, references vault)
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"# Run with vault password
ansible-playbook playbook.yml --vault-id production@prompt
ansible-playbook playbook.yml --vault-id production@/protected/path/vault-clientBest Practices and Patterns Questions
These questions test understanding of Ansible patterns that distinguish experienced practitioners.
What is idempotency and how do you ensure it in Ansible?
An idempotent task converges a resource so another run with the same inputs makes no further change. That property is task- and resource-specific: module implementation, parameters, external API behavior, generated values and surrounding order all matter. A clean second run is useful evidence, not proof of safe recovery from every partial failure.
command and shell report changed by default because their semantics are opaque to Ansible. Use a purpose-built module when it owns the resource correctly, or add an authoritative probe plus creates, removes, changed_when and failed_when. Do not create an unrelated marker file that can diverge from the real database or service state.
# BAD - always reports changed
- name: Add line to file
shell: echo "export PATH=/opt/bin:$PATH" >> /etc/profile
# GOOD - idempotent
- name: Add line to file
lineinfile:
path: /etc/profile
line: 'export PATH=/opt/bin:$PATH'
state: present
# BAD - always runs
- name: Create database
command: createdb myapp
# GOOD - module models the database resource
- name: Ensure application database exists
community.postgresql.postgresql_db:
name: myapp
state: presentHow should you organize an Ansible project directory?
A well-organized directory structure makes projects maintainable and enables multiple environments. The conventional structure separates inventory by environment, roles for reusable code, and playbooks for orchestration.
This structure scales from small projects to enterprise deployments and enables team collaboration with clear ownership of components.
ansible/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ ├── group_vars/
│ │ │ ├── all.yml
│ │ │ └── webservers.yml
│ │ └── host_vars/
│ └── staging/
│ └── ...
├── playbooks/
│ ├── site.yml # Master playbook
│ ├── webservers.yml
│ └── databases.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ └── app/
├── group_vars/ # Shared across inventories
│ └── all.yml
└── requirements.yml # Galaxy dependencies
How do you test Ansible roles with Molecule?
Molecule is a popular separate project for developing and testing Ansible content with scenario drivers and verifiers. It can create disposable containers or VMs, converge a role and run verification, but a container does not faithfully model systemd, kernel, networking or cloud behavior unless the scenario provides those capabilities.
Pin Molecule, drivers, collections and images in CI. Combine syntax/lint checks, Molecule convergence and a second idempotence run with check-mode evidence where supported, integration tests on representative targets, and staged rollout. No single framework proves a role safe for production.
# Initialize molecule for existing role
cd roles/nginx
molecule init scenario -r nginx -d docker
# Run full test sequence
molecule test
# Just apply role (converge)
molecule converge
# Login to test instance
molecule login
# Destroy test environment
molecule destroyScaling and Performance Questions
Large-scale Ansible deployments require specific techniques.
How do you run Ansible against thousands of servers efficiently?
Scale is a capacity and failure-domain problem, not “set forks to 50.” Benchmark the controller or execution environment, connection setup, target capacity, network devices, package repositories and external API quotas. Cache facts where safe, avoid unnecessary gathering, use efficient inventory plugins, pipeline connections where supported, and remove per-host controller bottlenecks.
Control blast radius separately from throughput. serial creates rollout batches; max_fail_percentage, health checks and an explicit abort/rollback-or-forward plan define failure behavior. throttle can protect a fragile task or API. The free strategy lets hosts advance independently and can violate cross-host ordering assumptions, so it is not a universal speed switch.
- name: Roll out application in bounded batches
hosts: app
serial: 10%
max_fail_percentage: 10
tasks:
- name: Deploy reviewed artifact
ansible.builtin.include_role:
name: application
- name: Verify health before the next batch
ansible.builtin.uri:
url: "https://{{ inventory_hostname }}/health"
status_code: 200
delegate_to: localhost
throttle: 5Async tasks detach controller waiting but still consume target resources and require job-result handling. ansible-pull changes scheduling, code distribution, credential and reporting ownership; choose it deliberately rather than solely from host count.
Debugging and Troubleshooting Questions
Debugging skills show practical experience with Ansible in production.
How do you debug a failing or intermittent playbook?
Ansible provides several debugging options from verbose output to step-by-step execution. Knowing these tools helps you diagnose issues efficiently.
Start with the failing host/task, return data and effective variables. Use verbosity carefully because module arguments or responses may contain secrets. --step is interactive; --start-at-task can skip prerequisites and is not a transaction resume. Check mode is a prediction whose accuracy depends on each module, and diff output can expose secret content.
# Increase verbosity
ansible-playbook playbook.yml -vvv
# Step through tasks
ansible-playbook playbook.yml --step
# Start at specific task
ansible-playbook playbook.yml --start-at-task="Configure app"
# Check syntax
ansible-playbook playbook.yml --syntax-check
# Predict supported changes; review output for secrets and unsupported modules
ansible-playbook playbook.yml --check --diff# Debug task
- name: Debug variables
debug:
var: my_variable
- name: Debug message
debug:
msg: "Value is {{ my_variable }}"
# Pause for inspection
- name: Pause for manual check
pause:
prompt: "Check server state, press enter to continue"How do you fix a task that always reports "changed"?
Tasks that always report "changed" make handler notifications and change reporting noisy and can hide drift, but a changed result is not itself proof that target state diverged. Diagnose the module contract and real resource state.
The solution depends on the cause: use changed_when: false for read-only operations, use creates/removes arguments for commands that create artifacts, or replace command/shell with native modules when available.
# Problem: shell always reports changed
- name: Check app status
command: curl --fail --silent http://localhost:8080/health
register: health
# Solution 1: changed_when
- name: Check app status
command: curl --fail --silent http://localhost:8080/health
register: health
changed_when: false
failed_when: health.rc != 0
# Solution 2: Use uri module (idempotent)
- name: Check app status
uri:
url: http://localhost:8080/health
return_content: yes
register: health
# Prefer a module whose state check represents the real resource.
- name: Ensure application database exists
community.postgresql.postgresql_db:
name: myapp
state: presentHow do you handle task failures gracefully?
block/rescue/always resembles try/catch/finally for task failures, but it is not a transaction and does not catch every category such as invalid task definitions or unreachable hosts in the same way. Earlier remote side effects remain unless compensation is explicitly safe and succeeds.
Use it to record evidence, attempt bounded cleanup or choose a safe forward/rollback action. Preserve the original failure context, avoid silently converting corruption into success, and define how unreachable hosts and partial batches are reconciled later.
# Block with error handling
- block:
- name: Try this
command: /might/fail
rescue:
- name: Handle failure
debug:
msg: "Task failed, recovering..."
always:
- name: Always run
debug:
msg: "Cleanup"Quick Reference
Essential commands
| Command | Purpose |
|---|---|
ansible all -m ping | Test connectivity |
ansible-playbook site.yml | Run playbook |
ansible-playbook site.yml -C | Predict supported changes in check mode |
ansible-playbook site.yml -D | Show diff |
ansible-playbook site.yml -l web1 | Limit to host |
ansible-vault encrypt file.yml | Encrypt file |
ansible-galaxy install role | Install role |
ansible-inventory --list | Show inventory |
ansible-doc module_name | Module documentation |
Common patterns
# Register and use a read-only result
- ansible.builtin.command: whoami
register: result
changed_when: false
- ansible.builtin.debug:
var: result.stdout
# Delegate to another host
- name: Reconcile this host in the load balancer
ansible.builtin.include_role:
name: load_balancer_backend
delegate_to: loadbalancer
# Run once (not on every host)
- name: Reconcile shared resource through an idempotent role
ansible.builtin.include_role:
name: shared_resource
run_once: trueWith serial, run_once runs once per batch, not necessarily once for the entire play. For a truly global resource, use a dedicated play/host or another explicit authority and make the operation idempotent under retries.
Frequently Asked Questions
What is the difference between Ansible and Terraform?
Both tools can declare and change infrastructure, but their execution models differ. Terraform builds a dependency graph and reconciles provider-managed resources with recorded state. Ansible executes ordered tasks through modules and derives current state from inventory, facts, registered results and target APIs rather than a Terraform-style state file. Choose by resource lifecycle, provider support, drift, orchestration and team operations; they can complement each other without a rigid create-versus-configure rule.
Why is Ansible agentless and why does it matter?
Ansible normally connects through existing transports such as SSH for POSIX hosts and WinRM or PSRP for Windows instead of installing a persistent Ansible agent. This removes an agent lifecycle but not security work: credentials, bastions, host keys, privilege escalation, temporary files, Python or PowerShell compatibility, logging and control-node reachability still matter. Network devices and execution environments may use different connection plugins.
What is idempotency and why is it important in Ansible?
An idempotent task converges the managed resource so another run with the same inputs causes no further change. Idempotency depends on the module, parameters, external API and surrounding workflow; a module name alone is not a guarantee. command and shell report changed by default, but creates, removes, changed_when and explicit probes can model change. Re-runnability still requires safe retries, ordering and failure recovery.
How does Ansible variable precedence work?
First distinguish configuration settings, command-line options, playbook keywords, variables and direct assignment; these are separate precedence categories. Within variables, role defaults are low and extra vars have the highest variable precedence, with inventory, facts, play, role, block, task, include and set_fact sources ordered by documented rules. Avoid memorizing a version-sensitive level count: use ansible-config, ansible-inventory and the official table to trace the effective source.
What is Ansible Vault and how do you use it?
Ansible Vault encrypts files or variables at rest. It does not protect decrypted values in memory, module arguments, logs, callbacks or target files, so combine it with no_log where appropriate, least privilege, secure editors and secret-handling review. Supply passwords through prompts or vault IDs backed by protected files or secret-manager client scripts, rotate them, and separate trust domains rather than committing a password beside ciphertext.
When should you use roles vs playbooks?
Playbooks orchestrate work across hosts and roles, while roles package a reusable automation unit behind a standard layout and documented variable interface. Use a role when ownership, reuse, supported platforms or testing justify that boundary; use plays to compose the workflow. Line count and whether a task is one-off are weaker criteria than lifecycle and responsibility.
Related Articles
This guide connects to the broader DevOps interview preparation:
Infrastructure as Code:
- Terraform Interview Guide - Complementary IaC tool
DevOps Fundamentals:
- Linux Commands Interview Guide - Linux skills for Ansible
- CI/CD & GitHub Actions Interview Guide - Ansible in pipelines
- Docker Interview Guide - Container configuration
Cloud Platforms:
- AWS Interview Guide - AWS modules and dynamic inventory
- Azure Interview Guide - Azure modules
- GCP Interview Guide - GCP modules
Sources
- ansible-core release and maintenance matrix
- Ansible inventory guide
- Ansible playbooks
- Controlling Ansible precedence
- Variable precedence reference
- Ansible Vault security boundary
- Ansible command module
- Handlers
- Check mode and diff mode
- Controlling playbook execution strategies
- Blocks and error handling
- Molecule documentation
