Ansible Interview Questions — Comprehensive Guide
1. Basic / Fundamental Questions
Q: What is idempotency in Ansible and why does it matter? Idempotency means running the same playbook multiple times produces the same result without unintended side effects. If nginx is already installed, the yum module won’t reinstall it. This makes Ansible safe to run repeatedly — useful for drift correction and CI/CD pipelines.
Q: What is the difference between a Play and a Playbook?
- Play — maps a group of hosts to a set of tasks
- Playbook — a YAML file containing one or more plays
# This entire file = Playbook- name: Play 1 - Configure webservers # ← Play hosts: webservers tasks: - name: Install nginx yum: name: nginx state: present- name: Play 2 - Configure databases # ← Play hosts: dbservers tasks: - name: Install postgresql yum: name: postgresql state: present
Q: What is inventory and what types exist?
# Static inventory — INI format[webservers]web1.example.comweb2.example.com ansible_user=ec2-user ansible_port=2222[dbservers]db1.example.comdb2.example.com[production:children] # Group of groupswebserversdbservers[webservers:vars] # Group variableshttp_port=80
# Static inventory — YAML formatall: children: webservers: hosts: web1.example.com: ansible_user: ec2-user web2.example.com: dbservers: hosts: db1.example.com: db2.example.com:
# Dynamic inventory — script or pluginansible-inventory -i aws_ec2.yml --list # AWS dynamic inventoryansible-inventory -i inventory/ --graph # View inventory tree
2. Variables & Precedence
Q: What is the variable precedence order in Ansible?
From lowest to highest priority (higher overrides lower):
1. Role defaults (roles/x/defaults/main.yml)2. Inventory file vars3. Inventory group_vars/all4. Playbook group_vars/all5. Inventory group_vars/*6. Playbook group_vars/*7. Inventory host_vars/*8. Playbook host_vars/*9. Host facts (gathered)10. Play vars11. Play vars_prompt12. Play vars_files13. Role vars (roles/x/vars/main.yml)14. Block vars15. Task vars16. include_vars17. set_facts / registered vars18. Role params19. Extra vars (-e) ← HIGHEST PRIORITY
# Extra vars always winansible-playbook site.yml -e "env=prod db_password=secret"
Q: What are magic variables? Special variables Ansible populates automatically:
- debug: msg: | Current host: {{ inventory_hostname }} Short hostname: {{ inventory_hostname_short }} All groups: {{ group_names }} All hosts: {{ groups['all'] }} Playbook dir: {{ playbook_dir }} Role path: {{ role_path }} Hostvars of web1: {{ hostvars['web1']['ansible_default_ipv4'] }}
Q: What is set_fact and when do you use it?
- name: Set derived variable set_fact: app_url: "https://{{ ansible_hostname }}:{{ app_port }}/{{ app_path }}" is_production: "{{ env == 'prod' }}" cacheable: true # Persists fact across plays in same run
3. Tasks & Modules
Q: What is the difference between command, shell, and raw modules?
| Module | Use Case | Notes |
|---|---|---|
command | Run commands without shell features | No pipes, redirects, variables |
shell | Run commands with shell features | Supports pipes, &&, |, globs |
raw | Low-level SSH — no Python needed | For bootstrapping, network devices |
# command — safe, no shell interpretation- command: /usr/bin/systemctl restart nginx# shell — needed for pipes/redirects- shell: ps aux | grep nginx | wc -l# raw — no Python required on target- raw: apt-get install -y python3
Prefer built-in modules over
command/shellfor idempotency.
Q: Explain commonly used modules.
# File management- ansible.builtin.copy: src: files/nginx.conf dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644'- ansible.builtin.template: src: templates/app.conf.j2 dest: /etc/app/app.conf- ansible.builtin.file: path: /var/log/myapp state: directory # absent | directory | file | link | touch mode: '0755'# Package management- ansible.builtin.yum: name: - nginx - python3 state: present # present | absent | latest- ansible.builtin.apt: name: nginx state: present update_cache: true# Service management- ansible.builtin.service: name: nginx state: started # started | stopped | restarted | reloaded enabled: true# User management- ansible.builtin.user: name: appuser uid: 1001 groups: wheel shell: /bin/bash state: present# Fetch file from remote- ansible.builtin.fetch: src: /var/log/app.log dest: /local/logs/ flat: true
Q: What is the register keyword? Captures module output into a variable:
- name: Check if file exists ansible.builtin.stat: path: /etc/nginx/nginx.conf register: nginx_conf_stat- name: Show result debug: msg: "File exists: {{ nginx_conf_stat.stat.exists }}"- name: Only run if file missing ansible.builtin.copy: src: nginx.conf dest: /etc/nginx/nginx.conf when: not nginx_conf_stat.stat.exists
4. Conditionals, Loops & Filters
Q: How do conditionals work in Ansible?
# Simple condition- name: Install on RedHat only yum: name: nginx state: present when: ansible_os_family == "RedHat"# Multiple conditions- name: Install on RHEL 8+ yum: name: nginx state: present when: - ansible_os_family == "RedHat" - ansible_distribution_major_version | int >= 8# OR condition- name: Run on web or proxy nodes service: name: nginx state: started when: > inventory_hostname in groups['webservers'] or inventory_hostname in groups['proxies']# Check registered result- name: Restart only if config changed service: name: nginx state: restarted when: config_result.changed# Check if variable is defined- name: Use custom port if defined debug: msg: "Port is {{ custom_port }}" when: custom_port is defined
Q: Explain loops in Ansible.
# Simple loop- name: Create multiple users ansible.builtin.user: name: "{{ item }}" state: present loop: - alice - bob - carol# Loop with dictionaries- name: Create users with attributes ansible.builtin.user: name: "{{ item.name }}" uid: "{{ item.uid }}" groups: "{{ item.groups }}" loop: - { name: alice, uid: 1001, groups: wheel } - { name: bob, uid: 1002, groups: docker }# Loop with index- name: Show item and index debug: msg: "Item {{ ansible_loop.index }}: {{ item }}" loop: "{{ packages }}" loop_control: label: "{{ item }}" # Cleaner output index_var: idx# Loop over dict- name: Set sysctl values sysctl: name: "{{ item.key }}" value: "{{ item.value }}" loop: "{{ sysctl_settings | dict2items }}" vars: sysctl_settings: vm.swappiness: 10 net.core.somaxconn: 65535# until loop — retry until condition met- name: Wait for service to respond uri: url: http://localhost:8080/health status_code: 200 register: result until: result.status == 200 retries: 10 delay: 5
Q: What are Jinja2 filters and give examples?
vars: my_list: [3, 1, 4, 1, 5, 9] my_string: " Hello World " my_dict: {a: 1, b: 2} packages: ["nginx", "python3"]tasks: - debug: msg: # String filters upper: "{{ my_string | upper }}" lower: "{{ my_string | lower }}" trim: "{{ my_string | trim }}" replace: "{{ my_string | replace('World', 'Ansible') }}" default: "{{ undefined_var | default('fallback') }}" # List filters sorted: "{{ my_list | sort }}" unique: "{{ my_list | unique }}" joined: "{{ packages | join(', ') }}" first: "{{ my_list | first }}" last: "{{ my_list | last }}" length: "{{ my_list | length }}" # Type conversion int_val: "{{ '42' | int }}" bool_val: "{{ 'true' | bool }}" list_val: "{{ my_dict | dict2items }}" # Math max_val: "{{ my_list | max }}" min_val: "{{ my_list | min }}" # Conditional ternary: "{{ (env == 'prod') | ternary('production', 'staging') }}" # Path basename: "{{ '/etc/nginx/nginx.conf' | basename }}" dirname: "{{ '/etc/nginx/nginx.conf' | dirname }}"
5. Handlers, Tags & Error Handling
Q: How do handlers work?
tasks: - name: Update nginx config template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: - Reload nginx - Send alert - name: Update SSL cert copy: src: cert.pem dest: /etc/ssl/cert.pem notify: Reload nginx # Same handler — only fires ONCEhandlers: - name: Reload nginx service: name: nginx state: reloaded - name: Send alert uri: url: https://hooks.slack.com/... method: POST body: '{"text": "nginx config updated"}'
Handlers run once at the end of a play, even if notified multiple times.
Q: How do you force handlers to run immediately?
tasks: - name: Update config template: src: app.conf.j2 dest: /etc/app/app.conf notify: Restart app - name: Flush handlers now meta: flush_handlers # Runs handlers immediately here - name: Run health check # Now runs after restart uri: url: http://localhost/health
Q: How do tags work?
tasks: - name: Install packages yum: name: nginx state: present tags: - install - packages - name: Configure nginx template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf tags: - configure - nginx - name: Start nginx service: name: nginx state: started tags: - start - always # 'always' tag runs even with --tags filter
# Run only tagged tasksansible-playbook site.yml --tags installansible-playbook site.yml --tags "install,configure"# Skip tagged tasksansible-playbook site.yml --skip-tags configure# List all tags in a playbookansible-playbook site.yml --list-tags
Q: How do you handle errors in Ansible?
# Ignore errors and continue- name: Try to stop service (may not exist) service: name: myapp state: stopped ignore_errors: true# Custom failure condition- name: Run script command: /usr/local/bin/check-status.sh register: result failed_when: - result.rc != 0 - '"CRITICAL" in result.stdout'# Custom changed condition- name: Run idempotent script command: /usr/local/bin/configure.sh register: result changed_when: '"already configured" not in result.stdout'# Blocks for error handling (try/catch/finally)- block: - name: Try risky operation command: /usr/bin/risky-script.sh - name: Another task in block service: name: myapp state: started rescue: - name: Handle the error debug: msg: "Something went wrong: {{ ansible_failed_result }}" - name: Rollback command: /usr/bin/rollback.sh always: - name: Always run cleanup file: path: /tmp/lockfile state: absent
6. Advanced Topics
Q: What is delegate_to and run_once?
# Run task on a DIFFERENT host- name: Add to load balancer uri: url: http://lb.example.com/api/add method: POST body: '{"host": "{{ inventory_hostname }}"}' delegate_to: localhost # Run on control node- name: Take DB backup before deploy command: pg_dump mydb > /backup/pre-deploy.sql delegate_to: db1.example.com # Run on DB server# Run only once across all hosts- name: Create database schema command: psql -f schema.sql run_once: true # Runs on first host in play only delegate_to: db1.example.com
Q: Difference between include_tasks and import_tasks?
import_tasks | include_tasks | |
|---|---|---|
| Parsed | At playbook load time (static) | At runtime (dynamic) |
| Supports loops | No | Yes |
| Supports conditionals | Limited (on the import) | Full |
| Tags visibility | Tags visible at start | Tags not visible until runtime |
| Use case | Always-needed task files | Conditional or looped includes |
# import — static, parsed upfront- import_tasks: tasks/setup.yml # No loop/conditional on the import# include — dynamic, parsed at runtime- include_tasks: "tasks/{{ ansible_os_family }}.yml" # Dynamic path OK when: setup_needed loop: "{{ environments }}"
Q: What is ansible-pull and when is it used? Reverses the push model — nodes pull their configuration from a Git repo. Used for:
- Large fleets where push is impractical
- Nodes behind firewalls
- Self-provisioning scenarios
ansible-pull -U https://github.com/myorg/ansible-config.git \ -C main \ --inventory localhost, \ local.yml
Q: What is a dynamic inventory plugin and how do you configure one?
# aws_ec2.yml — AWS dynamic inventoryplugin: amazon.aws.aws_ec2regions: - us-east-1 - us-west-2filters: instance-state-name: running tag:Environment: productionkeyed_groups: - key: tags.Role prefix: role - key: placement.region prefix: regionhostnames: - private-ip-addresscompose: ansible_host: private_ip_address
# Test dynamic inventoryansible-inventory -i aws_ec2.yml --graphansible-inventory -i aws_ec2.yml --list
7. Ansible for OpenShift/Kubernetes
Q: How do you manage OCP resources with Ansible?
- name: Manage OpenShift resources hosts: localhost collections: - kubernetes.core - redhat.openshift tasks: - name: Create namespace kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Namespace metadata: name: my-app labels: environment: production - name: Apply manifest from file kubernetes.core.k8s: state: present src: /path/to/deployment.yaml - name: Apply from template kubernetes.core.k8s: state: present definition: "{{ lookup('template', 'deployment.j2') }}" - name: Wait for deployment rollout kubernetes.core.k8s_rollout_status: name: my-deployment namespace: my-app timeout: 300 - name: Get pod info kubernetes.core.k8s_info: kind: Pod namespace: my-app label_selectors: - app=my-app register: pod_info - name: Scale deployment kubernetes.core.k8s_scale: name: my-deployment namespace: my-app replicas: 5
8. Performance & Best Practices
Q: How do you speed up Ansible playbooks?
# ansible.cfg optimizations[defaults]# SSH pipelining — reduces SSH connectionspipelining = True# Parallel executionforks = 20# Fact caching — don't re-gather every runfact_caching = jsonfilefact_caching_connection = /tmp/ansible_factsfact_caching_timeout = 3600[ssh_connection]# Reuse SSH connectionsssh_args = -o ControlMaster=auto -o ControlPersist=60scontrol_path_dir = /tmp/ansible-ssh# Disable fact gathering when not needed- hosts: webservers gather_facts: false # Skip if you don't need facts tasks: ...# Gather only specific facts- hosts: webservers gather_facts: true gather_subset: - network - hardware - "!all" # Only gather what's listed
Q: What are Ansible best practices?
- Use roles for reusable, testable code
- Store secrets in Vault, never plaintext
- Use requirements.yml for collections and roles
- Use tags for selective execution
- Prefix MCs with numbers for ordering (
00-base,99-custom) - Use
handlersinstead of always restarting services - Set
become: trueat play level, not task level - Use
block/rescue/alwaysfor error handling - Test roles with Molecule
- Pin collection versions in
requirements.yml - Use
--check(dry run) and--diffbefore applying changes
# Dry runansible-playbook site.yml --check --diff# Syntax checkansible-playbook site.yml --syntax-check# List hosts that would be affectedansible-playbook site.yml --list-hosts# Step through tasks one at a timeansible-playbook site.yml --step
9. Molecule — Role Testing
# Installpip install molecule molecule-docker# Initialize molecule in a rolecd roles/my-rolemolecule init scenario --driver-name docker
# molecule/default/molecule.ymldriver: name: dockerplatforms: - name: rhel8-instance image: registry.access.redhat.com/ubi8/ubi pre_build_image: true privileged: trueprovisioner: name: ansible playbooks: converge: converge.yml verify: verify.ymlverifier: name: ansible
# molecule/default/verify.yml- name: Verify hosts: all tasks: - name: Check nginx is running service_facts: - name: Assert nginx is active assert: that: - "'nginx' in services" - "services['nginx'].state == 'running'" - name: Check port 80 is listening wait_for: port: 80 timeout: 5
# Run full test cyclemolecule test# Individual stagesmolecule create # Spin up containersmolecule converge # Run the rolemolecule verify # Run testsmolecule destroy # Tear downmolecule login # SSH into instance for debugging
Quick-Fire Round
whenvsfailed_when?whencontrols if a task runs;failed_whendefines what counts as failure.- How to run a task on localhost?
delegate_to: localhostorhosts: localhost. - What is
ansible_facts? Auto-collected system info — OS, IP, memory, CPU. - Difference between
notifyand direct task?notifytriggers handlers at end of play only if task changed; direct task always runs. - What is check mode?
--check— dry run, no changes made, shows what would change. - What is
become? Privilege escalation — equivalent to sudo.become: true+become_user: root. - How to encrypt a single variable?
ansible-vault encrypt_string. - What is
gather_facts: falsegood for? Speeds up plays that don’t need system facts. - What does
any_errors_fatal: truedo? Stops the entire play on any host failure. - What is the
freestrategy? Hosts run tasks as fast as they can without waiting for others (vslineardefault).