Skip to content

Portal | Level: L1: Foundations | Topics: Ansible | Domain: DevOps & Tooling

Ansible Drills

Remember: Ansible module categories for ad-hoc commands: command (raw shell, no pipes), shell (supports pipes and redirects), copy (files to remote), service (start/stop/restart), package (install/remove). Mnemonic: "CSCSP" — Command, Shell, Copy, Service, Package. The -b flag means "become" (run as root via sudo). Without -b, service restarts and package installs fail with permission denied.

Gotcha: command module does NOT support pipes, redirects, or shell builtins. ansible all -m command -a "cat /etc/passwd | grep root" fails. Use -m shell for anything that needs shell features. This is a common interview trip-up.

Drill 1: Ad-Hoc Commands

Difficulty: Easy

Q: Check disk usage on all web servers and restart nginx, using ad-hoc commands.

Answer
# Check disk usage
ansible webservers -m command -a "df -h" -i inventory.yml

# Restart nginx
ansible webservers -m service -a "name=nginx state=restarted" -i inventory.yml -b

# Ping all hosts
ansible all -m ping -i inventory.yml
`-b` = become (sudo). `-m` = module. `-a` = arguments.

Drill 2: Playbook Basics

Difficulty: Easy

Q: Write a playbook that installs nginx, copies a config file, and starts the service.

Answer
---
- name: Configure web servers
  hosts: webservers
  become: true
  tasks:
  - name: Install nginx
    apt:
      name: nginx
      state: present
      update_cache: true

  - name: Copy nginx config
    template:
      src: templates/nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: Restart nginx

  - name: Ensure nginx is running
    service:
      name: nginx
      state: started
      enabled: true

  handlers:
  - name: Restart nginx
    service:
      name: nginx
      state: restarted
Handlers only run if notified AND only once at the end of the play.

Drill 3: Variables and Facts

Difficulty: Easy

Q: Where can variables be defined in Ansible? What's the precedence order?

Answer Sources (low → high precedence): 1. Role defaults (`roles/x/defaults/main.yml`) 2. Inventory vars (`group_vars/`, `host_vars/`) 3. Playbook vars (`vars:` section) 4. Task vars (`vars:` on a task) 5. Extra vars (`-e` on command line) — **highest, always wins**
# Extra vars override everything
ansible-playbook site.yml -e "nginx_port=8080"
# group_vars/webservers.yml
nginx_port: 80
nginx_workers: "{{ ansible_processor_vcpus }}"  # Uses a fact
Facts are auto-gathered system info: `ansible_hostname`, `ansible_os_family`, `ansible_processor_vcpus`, etc.

Drill 4: Roles

Difficulty: Medium

Q: Create a role structure for a PostgreSQL installation. What goes where?

Answer
ansible-galaxy init roles/postgresql
roles/postgresql/
├── defaults/main.yml    # Default variables (lowest precedence)
├── tasks/main.yml       # Main task list
├── handlers/main.yml    # Handlers (restart, reload)
├── templates/            # Jinja2 templates (.j2)
│   └── postgresql.conf.j2
├── files/                # Static files to copy
├── vars/main.yml        # Variables (higher precedence than defaults)
├── meta/main.yml        # Role dependencies, metadata
└── README.md
# tasks/main.yml
---
- name: Install PostgreSQL
  apt:
    name: "postgresql-{{ postgresql_version }}"
    state: present

- name: Configure PostgreSQL
  template:
    src: postgresql.conf.j2
    dest: "/etc/postgresql/{{ postgresql_version }}/main/postgresql.conf"
  notify: Restart PostgreSQL

# defaults/main.yml
postgresql_version: "15"
postgresql_max_connections: 100
Use in a playbook: `roles: [postgresql]` or `include_role: name=postgresql`.

Drill 5: Jinja2 Templates

Difficulty: Medium

Q: Write a Jinja2 template for an nginx upstream config that dynamically lists all hosts in the app group.

Answer
{# templates/upstream.conf.j2 #}
upstream app_servers {
{% for host in groups['app'] %}
    server {{ hostvars[host]['ansible_host'] }}:{{ app_port | default(8080) }};
{% endfor %}
}

server {
    listen 80;
    location / {
        proxy_pass http://app_servers;
    }
}
Key Jinja2 patterns: - `{{ variable }}` — output a value - `{% for %}` / `{% endfor %}` — loops - `{% if %}` / `{% endif %}` — conditionals - `{{ var | default('fallback') }}` — filters

Drill 6: Idempotency

Difficulty: Medium

Q: What does idempotent mean in Ansible? Give an example of a non-idempotent task and fix it.

Answer Idempotent = running the same playbook multiple times produces the same result. No unnecessary changes on re-run. **Bad** (not idempotent):
- name: Add line to config
  shell: echo "max_connections = 200" >> /etc/postgresql/postgresql.conf
  # Runs every time, appends duplicate lines!
**Good** (idempotent):
- name: Set max connections
  lineinfile:
    path: /etc/postgresql/postgresql.conf
    regexp: '^max_connections'
    line: 'max_connections = 200'
  # Only changes if the line doesn't match
Use Ansible modules (`apt`, `copy`, `template`, `lineinfile`, `service`) instead of `shell`/`command` whenever possible — modules are designed to be idempotent.

Drill 7: Vault (Ansible Vault)

Difficulty: Medium

Q: Encrypt a variable file containing database credentials. How do you use it in a playbook?

Answer
# Encrypt a file
ansible-vault encrypt group_vars/production/secrets.yml

# Edit encrypted file
ansible-vault edit group_vars/production/secrets.yml

# Encrypt a single string
ansible-vault encrypt_string 'hunter2' --name 'db_password'
# Outputs:
# db_password: !vault |
#   $ANSIBLE_VAULT;1.1;AES256
#   ...

# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass
# Or with a password file
ansible-playbook site.yml --vault-password-file=~/.vault_pass
# group_vars/production/secrets.yml (encrypted)
db_password: supersecret
api_key: abc123
Best practice: keep secrets in a separate file from non-secret vars so you can encrypt only what's needed.

Drill 8: Conditionals and Loops

Difficulty: Medium

Q: Install different packages based on OS family. Create users from a list.

Answer
# Conditional
- name: Install packages (Debian)
  apt:
    name: "{{ item }}"
    state: present
  loop: [nginx, curl, htop]
  when: ansible_os_family == "Debian"

- name: Install packages (RedHat)
  yum:
    name: "{{ item }}"
    state: present
  loop: [nginx, curl, htop]
  when: ansible_os_family == "RedHat"

# Loop with dict
- name: Create users
  user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    shell: /bin/bash
  loop:
  - { name: alice, groups: "sudo,docker" }
  - { name: bob, groups: "docker" }
  - { name: carol, groups: "sudo" }

Drill 9: Error Handling

Difficulty: Medium

Q: How do you handle failures in Ansible? Make a task that continues on error and another that fails on a specific output.

Answer
# Ignore errors and continue
- name: Check if service exists
  command: systemctl status legacy-app
  register: result
  ignore_errors: true

- name: Stop legacy app if it exists
  service:
    name: legacy-app
    state: stopped
  when: result.rc == 0

# Custom failure condition
- name: Check disk space
  command: df -h /
  register: disk_check
  failed_when: "'100%' in disk_check.stdout"

# Block with rescue (try/catch)
- block:
  - name: Deploy application
    command: /opt/deploy.sh
  rescue:
  - name: Rollback on failure
    command: /opt/rollback.sh
  always:
  - name: Send notification
    slack:
      msg: "Deploy {{ 'succeeded' if not ansible_failed_task else 'failed' }}"

Drill 10: Ansible vs Terraform

Difficulty: Easy

Q: When do you use Ansible vs Terraform? Can they work together?

Answer | Aspect | Terraform | Ansible | |--------|-----------|---------| | Purpose | Provision infrastructure | Configure servers | | State | Stateful (state file) | Stateless | | Style | Declarative only | Declarative + imperative | | Best for | Cloud resources, networking | Package install, config, services | | Idempotency | Built-in | Module-dependent | **Together (common pattern):** 1. Terraform provisions VMs, VPCs, load balancers 2. Terraform outputs IP addresses 3. Ansible configures the VMs (packages, users, configs)
# Terraform creates infra and generates inventory
terraform apply
terraform output -json | ./generate_inventory.py > inventory.yml

# Ansible configures it
ansible-playbook -i inventory.yml site.yml
In a K8s world, Ansible is less common (Helm/operators handle config), but still used for node bootstrapping, bare-metal, and legacy systems.

Wiki Navigation

Prerequisites