backend / iac / 02_ansible.md

Ansible

6 interview angles 4 min read source

Ansible

Configuration management. Less central than it was — containers absorbed much of its job — but still standard for VM fleets, on-prem, and anything Terraform provisions but doesn’t configure.

The model

Agentless: Ansible connects over SSH, pushes modules, runs them, removes them. Nothing to install on targets beyond Python.

# playbook.yml
- hosts: webservers
  become: true
  tasks:
    - name: Install nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Deploy config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        validate: nginx -t -c %s      # verify BEFORE replacing
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

The validate parameter is the detail worth knowing: it checks the rendered config before installing it, so a bad template fails the task instead of breaking the service on reload.

Handlers run once at the end, only if notified. That’s how you avoid restarting a service five times in one playbook run.

Terraform vs Ansible

The comparison you’ll be asked for:

Terraform Ansible
Job provision infrastructure configure what exists
Model declarative, with state procedural tasks, idempotent modules
State file yes — tracks reality no
Drift detection plan shows it only if you re-run
Destroys things yes, deliberately rarely
Best at cloud resources, networks, managed services packages, files, services, app deploys

They compose: Terraform creates the VMs and outputs their IPs; Ansible configures them. Using Terraform to install packages via remote-exec, or Ansible to manage cloud resources, is possible and worse at both.

The stronger 2026 answer: if you’re building images or containers, you often need neither for configuration. Packer or a Dockerfile bakes the configuration in, and instances become immutable. Ansible earns its place where you have long-lived mutable servers.

Idempotency

The property that makes re-running safe. Modules declare desired state, not commands:

# Idempotent - describes the end state
- ansible.builtin.package: { name: nginx, state: present }

# NOT idempotent - runs every time, reports "changed" every time
- ansible.builtin.shell: apt-get install -y nginx

shell and command are escape hatches that break idempotency. When you must use them, guard with creates:, removes: or a when: condition, and set changed_when: honestly so your change reporting means something.

A playbook that reports changes on every run is a playbook nobody can use to detect drift.

Structure at scale

inventory/
  production/hosts.yml
  staging/hosts.yml
roles/
  nginx/{tasks,handlers,templates,defaults,vars}/
group_vars/
  webservers.yml
host_vars/
  web-01.yml

Roles are the reuse unit. Inventory separates environments. Variable precedence is deep and a genuine source of confusion — roughly: role defaults lose to group vars, which lose to host vars, which lose to -e extra vars. When a variable “isn’t taking effect”, precedence is the first thing to check.

Secrets

ansible-vault encrypt group_vars/production/secrets.yml
ansible-playbook site.yml --ask-vault-pass

Ansible Vault encrypts files at rest in the repo. It works, and it’s inferior to a real secrets manager: rotation is manual, access is all-or-nothing per file, and there’s no audit trail. Prefer pulling from Vault, AWS Secrets Manager or similar at runtime; use Ansible Vault when you have no other option. See ../../system_design/04_secrets_config/01_secrets_and_configuration.md.

Practical notes

  • --check --diff is your dry run. Not all modules support it faithfully, so it’s indicative rather than authoritative.
  • --limit to target a subset; combine with serial: for rolling deploys across a fleet.
  • Performance: Ansible is slow on large inventories. Increase forks, enable pipelining, and use gather_facts: false where you don’t need facts — fact gathering is often the single biggest cost.
  • Testing: Molecule spins up containers and verifies roles. Rare in practice, valuable when playbooks are load-bearing.

Where it fits in 2026

Use it for Prefer something else
long-lived VMs and on-prem fleets containerised workloads (image build)
network devices, appliances cloud resources (Terraform)
ad-hoc fleet operations ephemeral autoscaled instances (bake the image)
bootstrapping Kubernetes nodes app config in K8s (ConfigMaps, Helm)

Being clear that immutable infrastructure has narrowed Ansible’s scope is a better answer than presenting it as the default.

Interview angle

  • “Terraform or Ansible?” — different jobs. Terraform provisions infrastructure and holds state so it can detect drift and destroy; Ansible configures existing machines and holds no state. They compose: Terraform creates the VMs, Ansible configures them.
  • “What makes a playbook idempotent, and how does it break?” — modules declare desired state, so re-running is a no-op. shell and command break it because they always execute; guard them with creates:/when: and set changed_when: honestly, or your change reporting becomes meaningless.
  • “Why is Ansible less central than it used to be?” — immutable infrastructure. If you bake configuration into an image or container, there’s nothing to configure at runtime. Ansible earns its place with long-lived mutable servers, network devices and on-prem fleets.
  • “A variable isn’t taking effect. Where do you look?” — precedence. Role defaults are weakest, then group vars, then host vars, then extra vars on the command line. It’s the most common source of confusion in a large inventory.
  • “Is Ansible Vault adequate for secrets?” — workable, not good. No rotation, no per-secret access control, no audit trail. Prefer fetching from a real secrets manager at runtime.
  • “Your playbook takes 20 minutes across 200 hosts. Speed it up?” — raise forks, enable pipelining, and disable fact gathering where it isn’t needed; fact gathering is usually the dominant cost.