Ansible remains the go-to tool for configuration management and automation. Its agentless architecture and simple YAML syntax make it accessible, but interviews dig deeper—into idempotency, variable precedence, and how Ansible fits into modern infrastructure alongside Terraform and Kubernetes.
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 provides several critical benefits: consistency across all servers (no more "works on my machine"), version control for tracking changes and enabling rollbacks, documentation through code, automation eliminating manual intervention, and drift correction by re-running playbooks to fix unauthorized manual changes.
What is Ansible and how does it work?
Ansible is an agentless automation tool that uses SSH to connect to target machines and execute tasks defined in YAML playbooks. It follows a push-based model where a control node sends configurations to managed nodes, requiring no software installation on the targets.
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 advantages include: nothing to install on target servers, no listening ports or daemons creating attack surface, no agent updates to manage, works immediately on any SSH-accessible system, and a simpler security model. The trade-offs are that push-based execution requires connectivity from the control node to all targets, and there's no continuous enforcement like pull-based agents provide.
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.
Ansible is agentless and uses a push model with YAML syntax. Puppet and Chef are agent-based with pull models—Puppet uses its own DSL while Chef uses Ruby. Salt can work either way (agent or agentless) and uses YAML. Ansible often wins for its low barrier to entry, no agents to maintain, and large module library.
| 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 is declarative infrastructure provisioning—it creates cloud resources like VMs, VPCs, and databases. Ansible is procedural configuration management—it configures servers by running tasks in order. Terraform tracks state in a state file, while Ansible is stateless. They complement each other perfectly: Terraform provisions infrastructure, Ansible configures it.
| Aspect | Terraform | Ansible |
|---|---|---|
| Purpose | Infrastructure provisioning | Configuration management |
| Model | Declarative | Procedural |
| State | Tracks state file | Stateless |
| Creates | Cloud resources (VMs, VPCs, DBs) | Configures existing servers |
| 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 queries external sources (cloud providers, CMDBs, container orchestrators) for the current list of hosts rather than maintaining a static file. This is essential for cloud environments where servers are created and destroyed automatically.
With dynamic inventory, as instances come and go through auto-scaling, the inventory updates automatically. You tag instances with their role (web, api, worker) and use tag-based groups in your playbooks. No manual inventory maintenance required.
# 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: public_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: yes # Run as root
vars:
http_port: 80
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart nginx
service:
name: nginx
state: restartedWhat does "become" mean in Ansible and when do you use it?
The become directive enables privilege escalation, allowing tasks to run as a different user (typically root). This is necessary for administrative tasks like installing packages, managing services, or modifying system files.
You can set become: yes at the play level to apply to all tasks, or at individual task level for granular control. By default, become uses sudo, but you can configure other methods like su or doas.
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 specialized modules for different package managers, plus a generic package module that auto-detects the OS. Using the right module ensures idempotent package installation.
The apt module works for Debian/Ubuntu, yum for RHEL/CentOS, and package provides a cross-platform option. Each supports installing, removing, and updating packages with various options.
# Debian/Ubuntu
- name: Install packages
apt:
name:
- nginx
- postgresql
- python3
state: present
update_cache: yes
# RHEL/CentOS
- name: Install packages
yum:
name: nginx
state: present
# Generic (detects OS)
- name: Install package
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?
Command and shell modules should be avoided when idempotent alternatives exist. They run arbitrary commands and always report "changed" unless you configure changed_when. Native modules like apt, service, and file are idempotent by design.
Use command/shell only when no native module exists for your task, or for one-off commands. Always add creates or removes arguments to make them idempotent, and use changed_when: false for read-only operations.
# Avoid when possible - not idempotent
- name: Run script
command: /opt/scripts/setup.sh
args:
creates: /opt/app/.installed # Only run if file doesn't exist
# Shell for pipes and redirects
- name: Check disk space
shell: df -h | grep /dev/sda1
register: disk_result
changed_when: false # Never report changedConditionals 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 tasks that run at the end of a play, only if they've been notified by other tasks. They're typically used for service restarts—you want to restart nginx only once, even if multiple config files changed.
The key behavior: handlers run once at play end, regardless of how many times they're notified. They run in the order defined in the handlers section, not the order notified. This ensures efficient service restarts without unnecessary repetition.
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?
By default, handlers run at the end of the play. Sometimes you need a service restarted before continuing—for example, to verify health before deploying more changes. The meta: flush_handlers task forces all pending handlers to execute.
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 are appropriate for simple, one-off tasks or for orchestrating multiple roles. Roles are better when configuration might be reused across projects, you want to share via Galaxy, or tasks are complex enough to benefit from organized structure.
A good rule: if you're copying tasks between playbooks, extract them into a role. Roles also enable testing with Molecule, version control of configuration units, and clear interfaces through documented variables.
How do you use Ansible Galaxy for role management?
Ansible Galaxy is the public repository of community-contributed roles. You can install roles directly or define dependencies in a requirements file for reproducible environments.
The requirements.yml file pins role versions and can include roles from Galaxy, Git repositories, or local paths. This ensures consistent deployments across teams and environments.
# 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: ">=5.0.0"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: yes
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?
Ansible has 22 levels of variable precedence, from role defaults (lowest) to extra vars (highest). Understanding this hierarchy prevents confusion when the same variable is defined in multiple places.
The key rules to remember: extra vars (-e) always win, role defaults always lose, and more specific definitions (host_vars) override less specific ones (group_vars). For predictable behavior, use role defaults for overridable values and avoid setting the same variable in multiple places.
Lowest priority:
1. Role defaults (roles/x/defaults/main.yml)
2. Inventory file or script group vars
3. Inventory group_vars/all
4. Playbook group_vars/all
5. Inventory group_vars/*
6. Playbook group_vars/*
7. Inventory file or script host vars
8. Inventory host_vars/*
9. Playbook host_vars/*
10. Host facts / cached set_facts
11. Play vars
12. Play vars_prompt
13. Play vars_files
14. Role vars (roles/x/vars/main.yml)
15. Block vars
16. Task vars
17. include_vars
18. set_facts / registered vars
19. Role params
20. include params
21. Extra vars (-e) -- ALWAYS WIN
Highest priority
What 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 overhead—Ansible SSHes to each host and runs a discovery script. For simple tasks that don't need system information, disabling fact gathering significantly speeds up execution.
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 variable values in templates. Ansible provides many built-in filters for common operations like providing defaults, joining lists, hashing passwords, and converting between formats.
Knowing these filters helps you write cleaner templates and avoid complex logic in playbooks.
{{ variable | default('fallback') }}
{{ list | join(', ') }}
{{ string | lower }}
{{ string | upper }}
{{ path | basename }}
{{ path | dirname }}
{{ dict | to_json }}
{{ dict | to_yaml }}
{{ password | password_hash('sha512') }}
{{ 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 sensitive data like passwords, API keys, and certificates. You can encrypt entire files or individual variables. Decryption happens at runtime when you provide the vault password.
This enables storing secrets in version control safely—the encrypted content is unreadable without the password, but you maintain the benefits of Git history and collaboration.
# 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 the best practice for organizing vault files?
A common pattern is separating vault variables into their own file, then referencing them from a plain-text variables file. This keeps your structure clear—you can see what variables exist without decrypting, and only the values are encrypted.
Use different vault files and passwords for different environments (production, staging) to limit blast radius if a password is compromised.
# 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 --ask-vault-pass
ansible-playbook playbook.yml --vault-password-file ~/.vault_passBest 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?
Idempotency means running a playbook multiple times produces the same result as running it once. If nginx is already installed, the apt module reports "ok" not "changed". This property enables safe re-runs, drift correction, and reliable automation.
Most Ansible modules are idempotent by design. The exceptions are command and shell, which always report "changed" unless you configure them otherwise. When you must use these modules, add creates, removes, or changed_when to make them idempotent.
# 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 - check first
- name: Create database
command: createdb myapp
args:
creates: /var/lib/postgresql/data/myapp # Skip if existsHow 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 the standard testing framework for Ansible roles. It creates isolated environments (Docker containers, VMs), applies your role, and runs verification tests. This catches bugs before they reach production.
Molecule integrates with CI/CD pipelines to test every change to your roles automatically. It supports multiple test instances for testing across different operating systems.
# 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?
Running against large inventories requires parallelism, async tasks, and potentially switching to pull mode. Understanding these options shows experience with production-scale deployments.
The default fork count of 5 is too low for large deployments. Async tasks enable parallel execution without waiting for each host to complete. For very large scales, ansible-pull inverts the model—hosts pull and apply their own configuration.
# 1. Increase parallelism in ansible.cfg
[defaults]
forks = 50 # Default is 5
# 2. Use async for long-running tasks
- name: Update packages (async)
apt:
upgrade: dist
async: 3600 # 1 hour timeout
poll: 0 # Don't wait
register: apt_update
- name: Check update status
async_status:
jid: "{{ apt_update.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 60
# 3. Use free strategy (don't wait for slowest host)
- hosts: all
strategy: free
tasks: ...# 4. Pull mode - each host pulls and runs its own config
ansible-pull -U https://github.com/org/ansible-config.gitDebugging 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 verbosity flags to see what's happening. Use --step for interactive execution, --start-at-task to resume from a specific point, and --check for dry runs. The debug module helps inspect variables during execution.
# 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
# Dry run
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" break idempotency and make it impossible to detect actual changes. This commonly happens with command/shell modules or poorly configured tasks.
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
shell: curl -s http://localhost:8080/health
register: health
# Solution 1: changed_when
- name: Check app status
shell: curl -s http://localhost:8080/health
register: health
changed_when: false # Never report changed
# Solution 2: Use uri module (idempotent)
- name: Check app status
uri:
url: http://localhost:8080/health
return_content: yes
register: health
# Problem: command always runs
- name: Initialize database
command: /opt/app/init-db.sh
# Solution: creates argument
- name: Initialize database
command: /opt/app/init-db.sh
args:
creates: /opt/app/.db_initializedHow do you handle task failures gracefully?
Block/rescue/always provides try-catch-finally semantics in Ansible. This enables error handling, cleanup tasks, and graceful degradation when tasks fail.
Use this pattern when you need to recover from errors, run cleanup regardless of success or failure, or implement complex error handling logic.
# 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 | Dry run (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 result
- command: whoami
register: result
- debug:
var: result.stdout
# Delegate to another host
- name: Add to load balancer
command: add-backend {{ inventory_hostname }}
delegate_to: loadbalancer
# Run once (not on every host)
- name: Create shared resource
command: create-resource
run_once: trueRelated 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
