DevOps Peach

Linux · Ansible – Configuration Automation · Lesson 3 of 5

YAML and playbook structure

Write structured playbooks and check them before changing hosts.

Practice interactively →

YAML essentials

YAML uses spaces to represent nesting; tabs are not valid indentation. A list begins with -, and a mapping uses key: value. Block scalar | preserves line breaks, while > folds most line breaks into spaces. # starts a comment, and --- marks the start of a document.

Quote values that YAML might misread: yes, no, on, and off can become Booleans, 08:30 may be parsed in surprising ways, and a value starting with { or * needs quotes. When in doubt, quote strings.

flowchart LR
  PB["site.yml<br/>playbook"] --> PL["Play<br/>hosts: web"]
  PL --> T1["Task<br/>apt: nginx"]
  T1 --> T2["Task<br/>template: nginx.conf"]
  T2 -- "notify" --> H["Handler<br/>restart nginx"]
  PB -. "--check --diff" .-> DRY["Dry run<br/>preview changes"]
  %% step PB: A playbook is a YAML list of plays.
  %% step PL: Each play maps a group of hosts to the tasks they should run.
  %% step T1: Tasks run in order, each calling one module.
  %% step T2: A task that changes a config file can notify a handler.
  %% step H: Handlers run once at the end, only if something notified them.
  %% step DRY: --syntax-check, --check and --diff let you review before changing hosts.

Playbook structure

---
- name: Configure web hosts
  hosts: web
  become: true
  vars:
    site_dir: /var/www/demo
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Deploy config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: Reload nginx

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

A play selects hosts; tasks is an ordered list. Put become: true at play or task level when an operation needs root. FQCNs such as ansible.builtin.apt make a module's origin explicit. Give every task a name, since it appears in the output.

Handlers run only when a task that notifies them reports changed, and only once at the end of the play. That is how a service is reloaded after its config really changes, not on every run. A playbook may contain several plays, for example one for db and one for web.

Running safely

Command Goal
ansible-playbook --syntax-check site.yml Parse without running tasks
ansible-playbook -i inventory.ini site.yml --check --diff Predict changes and show file differences
ansible-playbook -i inventory.ini site.yml --limit web1 Run on one host first
ansible-playbook -i inventory.ini site.yml --tags nginx Run only tagged tasks
ansible-playbook -i inventory.ini site.yml Execute for real

Check mode cannot guarantee zero side effects for every module and does not replace review. -v to -vvvv add detail when a task fails, and ansible-lint flags common style and safety problems.

Reading the recap

The final PLAY RECAP counts ok, changed, failed, unreachable, and skipped per host. A rerun should tend toward changed=0 after desired state is reached; a task that reports changed every time usually needs a better module or guard.

Practice interactively →