Ansible Fundamentals

Agentless, push-based config management. Writes YAML, runs over SSH (or WinRM, or network device APIs). Declarative-leaning. The default tool for “I want to reliably make the same changes across 100 servers.”

Why people pick Ansible

  • Agentless — no software to install on managed hosts. Just needs SSH + Python on the target. Huge operational win.
  • Push-based — you run ansible-playbook from your laptop (or a CI runner) and it connects to the targets. No constantly-running server to manage.
  • YAML playbooks — readable, diffable, review-friendly. No “real” programming required for most tasks.
  • Massive module library — Ansible ships with modules for everything: package managers, cloud APIs, network devices, firewalls, Windows, Kubernetes, SaaS APIs.
  • Idempotent by convention — well-written tasks converge to the desired state whether you run them once or a hundred times.

The mental model

Ansible connects to one or more hosts (servers, network devices) and runs tasks on each. Each task uses a module that knows how to reach a particular desired state.

   Your laptop / CI
        │
        │ (SSH/WinRM/API)
        ▼
   ┌───────────┬───────────┬───────────┐
   │  host1    │  host2    │  host3    │
   └───────────┴───────────┴───────────┘
        └──────── tasks run in parallel ────────┘

The core concepts, bottom up

1. Inventory — what hosts exist

A file listing the hosts you can target, organised into groups:

[webservers]
web1.example.com
web2.example.com
 
[databases]
db1.example.com
 
[all:vars]
ansible_user=deploy

YAML inventory is equivalent. Dynamic inventories query cloud APIs (AWS, Azure, etc.) or CMDBs to build the list at runtime.

2. Module — the actual work

A module is a small program Ansible ships to the target and runs. Examples:

  • apt — Debian/Ubuntu packages
  • yum / dnf — RHEL/Fedora packages
  • service / systemd — service state
  • copy / template — files
  • user / group — local users
  • lineinfile / blockinfile — safe file edits
  • command / shell — raw commands (use only when no module fits)
  • uri — HTTP APIs
  • cisco.ios.ios_config — Cisco IOS network config
  • …thousands more

3. Task — one module invocation

- name: nginx is installed
  apt:
    name: nginx
    state: present

The name is a human-readable description. The module (apt) and its parameters (name, state) do the work.

4. Play — a list of tasks targeted at a group

- name: Web server setup
  hosts: webservers
  become: yes            # sudo
  tasks:
    - name: nginx is installed
      apt:
        name: nginx
        state: present
 
    - name: nginx is running
      service:
        name: nginx
        state: started
        enabled: yes

5. Playbook — a file with one or more plays

A playbook is a YAML file. Running it applies every play in order:

ansible-playbook -i inventory.ini site.yml

6. Role — reusable playbook chunks

As playbooks grow, you extract reusable bits into roles. A role has a standard directory layout:

roles/nginx/
├── tasks/main.yml
├── handlers/main.yml
├── templates/nginx.conf.j2
├── defaults/main.yml
├── vars/main.yml
└── meta/main.yml

Plays use roles:

- hosts: webservers
  roles:
    - nginx
    - firewall

Ansible Galaxy is the public registry of community roles.

7. Variables & templates

Variables come from inventory, group_vars, host_vars, defaults, vars files, extra-vars on the command line — there’s a strict precedence order.

Templates use Jinja2 for interpolation:

server {
    listen 80;
    server_name {{ ansible_facts['hostname'] }};
    root /var/www/{{ site_name }};
}

8. Handlers — respond to changes

A handler is a task that runs only if something “notifies” it. Classic use: restart nginx when its config changes.

- name: Deploy nginx config
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx
 
handlers:
  - name: restart nginx
    service:
      name: nginx
      state: restarted

If the template doesn’t change anything (idempotent), the handler doesn’t fire.

A complete minimal example

inventory.ini

[web]
192.168.1.10
192.168.1.11

site.yml

- name: Web servers
  hosts: web
  become: yes
  vars:
    app_version: "1.2.3"
  tasks:
    - name: App user exists
      user:
        name: app
        state: present
 
    - name: App directory exists
      file:
        path: /opt/app
        state: directory
        owner: app
        group: app
 
    - name: App package installed
      apt:
        deb: "https://example.com/app-{{ app_version }}.deb"
        state: present
 
    - name: App service running
      service:
        name: app
        state: started
        enabled: yes

Run:

ansible-playbook -i inventory.ini site.yml

Run again: everything already in desired state, nothing happens (thanks to Idempotence).

Ansible’s superpower: ansible ad-hoc

You don’t always need a playbook. For quick one-offs:

ansible web -i inventory.ini -m service -a "name=nginx state=restarted" --become

“On the web group, run the service module with these args, use sudo.”

Useful for emergency fixes, running shell commands across a fleet, or testing before committing to a playbook.

Ansible for network automation

A core use case. Network modules target devices instead of servers:

- hosts: routers
  gather_facts: no
  connection: network_cli
  tasks:
    - name: Configure loopback
      cisco.ios.ios_config:
        lines:
          - ip address 10.1.1.1 255.255.255.255
        parents: interface Loopback0

Ansible logs into the device via SSH, runs the CLI changes, and idempotently converges config.

Ansible vs alternatives

AnsiblePuppetChefSalt
Agent on targets?NoYesYesYes (can be agentless)
LanguageYAML + JinjaDSLRuby DSLYAML
StylePush, mostly declarativePull, declarativePull, imperative-ishBoth
Learning curveLowMediumHigh (Ruby)Medium

Ansible wins on the “agentless + low barrier to entry” combo, which is why it dominates.

Ansible vs Terraform

They overlap but aren’t the same:

  • Terraform: provisions infrastructure (VMs, networks, DNS zones, cloud resources). Great at creating/modifying/destroying. Keeps state.
  • Ansible: configures what’s inside those systems (users, packages, services, files, device configs). Weaker at provisioning; great at configuration.

Common pattern: Terraform provisions → Ansible configures.

Gotchas to know

  • command and shell modules are not idempotent by default. Use creates: / removes: / changed_when: to make them so.
  • Inventory precedence is subtle; variables from group_vars/all can be overridden by host_vars/specific.
  • become (sudo) isn’t automatic. Set it at play, task, or command level explicitly.
  • Long-running tasks time out; use async + poll for fire-and-forget.
  • Dry run: ansible-playbook ... --check --diff shows what would change without changing it. Always test first.

See also