contentintech
Learn/devops/Ansible
Intermediate~20 min read

Ansible

Agentless configuration management with Ansible — inventory, playbooks and tasks, modules, roles, handlers, variables and facts, Jinja2 templates, ansible-vault, idempotency, ad-hoc commands, and ansible-galaxy.

PlaybooksInventoryRolesAutomation

Agentless Automation

Ansible is a configuration-management and automation tool that is agentless: it connects to managed hosts over SSH (or WinRM on Windows) and runs modules, then removes them. There is no daemon to install on target machines — the only requirements are SSH access and Python on the target.

Push, not pull

Unlike agent-based tools, Ansible pushes configuration from a control node on demand. This keeps managed hosts clean and makes it easy to run against ephemeral cloud instances.

Inventory

The inventory lists the hosts Ansible manages, organized into groups. It can be written in INI or YAML, or generated dynamically from a cloud provider.

INI format

ini
[web]
web1.example.com
web2.example.com

[db]
db1.example.com ansible_host=10.0.1.5

[prod:children]
web
db

[prod:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3

YAML format

yaml
all:
  children:
    web:
      hosts:
        web1.example.com:
        web2.example.com:
    db:
      hosts:
        db1.example.com:
          ansible_host: 10.0.1.5
      vars:
        ansible_user: deploy

Ad-Hoc Commands

For one-off tasks, run a module directly with ansible instead of writing a playbook.

bash
# Ping all hosts
ansible all -m ping

# Check uptime on the web group
ansible web -m command -a "uptime"

# Install nginx (needs privilege escalation)
ansible web -m ansible.builtin.apt -a "name=nginx state=present" --become

# Gather facts
ansible db1.example.com -m setup

Playbooks and Tasks

A playbook is a YAML file describing a set of plays. Each play maps a group of hosts to a list of tasks, and each task calls a module with arguments. Tasks run top to bottom, in order.

yaml
---
- name: Configure web servers
  hosts: web
  become: true
  vars:
    app_port: 8080

  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Deploy config from template
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        mode: "0644"
      notify: Restart nginx

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

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Run it with ansible-playbook -i inventory.yml site.yml. Add --check for a dry run and --diff to preview file changes.

Idempotency

Idempotency means running a playbook repeatedly produces the same result — modules only change what is not already in the desired state. A second run should report changed=0. Prefer purpose-built modules over command/shell, which are not idempotent unless you add guards like creates.

Handlers

A handler is a task that runs only when notified by another task that reported a change. Handlers run once, at the end of the play, even if notified many times — ideal for restarting a service only when its config actually changed.

Variables and Facts

Variables parameterize plays and can be defined in inventory, playbooks, group_vars/, host_vars/, or the command line. Facts are variables Ansible automatically discovers about a host (OS, IPs, memory) via the setup module.

yaml
tasks:
  - name: Show the OS family (a fact)
    ansible.builtin.debug:
      msg: "Running on {{ ansible_facts['os_family'] }}"

  - name: Only run on Debian-based systems
    ansible.builtin.apt:
      name: htop
      state: present
    when: ansible_facts['os_family'] == "Debian"

  - name: Loop over a list variable
    ansible.builtin.user:
      name: "{{ item }}"
      state: present
    loop: "{{ dev_users }}"

Jinja2 Templates

The template module renders Jinja2 files, substituting variables and facts. Template files end in .j2.

jinja2
# templates/nginx.conf.j2
server {
    listen {{ app_port }};
    server_name {{ ansible_facts['hostname'] }};

    {% for backend in backends %}
    upstream_server {{ backend }};
    {% endfor %}
}

Roles

A role is a standardized directory structure that bundles tasks, handlers, templates, files, and defaults into a reusable unit. Roles keep large playbooks organized and shareable.

text
roles/
  webserver/
    tasks/main.yml       # main task list
    handlers/main.yml    # handlers
    templates/           # .j2 files
    files/               # static files to copy
    defaults/main.yml    # default variables (lowest precedence)
    vars/main.yml        # role variables (higher precedence)
    meta/main.yml        # role metadata / dependencies
yaml
# Using a role in a playbook
- name: Set up web tier
  hosts: web
  become: true
  roles:
    - webserver
    - { role: monitoring, tags: ["obs"] }

ansible-vault

Store secrets encrypted at rest with ansible-vault. Encrypted files or variables are decrypted transparently at runtime when you supply the vault password.

bash
# Encrypt a secrets file
ansible-vault encrypt group_vars/prod/vault.yml

# Edit an encrypted file
ansible-vault edit group_vars/prod/vault.yml

# Run a playbook, prompting for the vault password
ansible-playbook site.yml --ask-vault-pass

# Or point at a password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Best practice

Keep secret values in a dedicated vault.yml and reference them from plain files. This lets you version-control most variables in the clear while encrypting only the sensitive ones.

ansible-galaxy

Ansible Galaxy is the hub for sharing roles and collections. Use it to scaffold roles and install community content.

bash
# Scaffold a new role
ansible-galaxy init roles/webserver

# Install a collection
ansible-galaxy collection install community.general

# Install roles/collections from a requirements file
ansible-galaxy install -r requirements.yml

Practice Exercises

  1. Write a YAML inventory with web and db groups and ping all hosts with an ad-hoc command.
  2. Write a playbook that installs and starts nginx, then run it twice to confirm idempotency (changed=0 the second time).
  3. Add a handler that restarts nginx only when a templated config file changes.
  4. Use a fact in a when condition so a task runs only on Debian-family hosts.
  5. Refactor your playbook into a role using ansible-galaxy init.
  6. Encrypt a secrets file with ansible-vault and run the playbook using a vault password file.

Section navigation