// learn Β· linux Β· rhce Β· 12 min

Ansible playbooks & YAML.

A playbook is desired state written in YAML: plays map hosts to tasks, and tasks call modules that converge the system. Watch a play run and read its ok/changed/skipped output, then run it again and watch `changed` drop to zero β€” the idempotency that separates Ansible from a shell script.

Ansible playbooks and YAML animated tutorial. What a play is, the anatomy of a play with name, hosts, become, vars and tasks, YAML indentation rules, running ansible-playbook and reading the ok, changed, skipped, failed and unreachable statuses, the PLAY RECAP, idempotency proven on a second run with changed=0, dry runs with --check and --diff, stepping through tasks with --step and --start-at-task, extra vars with -e, and targeting hosts and tasks with --limit and --tags. RHCE EX294 ready.
// ansible Β· playbooks & yaml

A playbook is desired state in YAML: plays map hosts to tasks, tasks call modules. Watch a play run, read the ok/changed/skipped output, then run it again and watch changed drop to zero β€” that's idempotency.

// before you start
you should know
  • Β·You can reach a fleet (control node + inventory + ping)
  • Β·Comfortable with the Linux shell + dnf/service
  • Β·Know what YAML looks like β€” key: value, lists, 2-space indent
by the end you'll

Read and write a basic playbook, run it, interpret the ok/changed/skipped output and PLAY RECAP, prove idempotency on a second run, and debug a run with --check, --diff, --step and tags.

pace: 12 minutes

ansible β€” step 1 / 6 Β· what & why
πŸ“œ site.yml
---
- name: web servers
hosts: webservers
become: true
vars:
pkg: httpd
tasks:
- name: install httpd
dnf:
name: "{{ pkg }}"
state: present
- name: start httpd
service:
name: httpd
state: started
$ ansible-playbook site.yml
TASK[Gathering Facts]…
TASK[install httpd]…
TASK[start httpd]…

A playbook is desired state, written in YAML

A playbook is an ordered list of PLAYS. A play maps a set of hosts to a list of TASKS, and each task calls a MODULE that does the actual work. It's declarative β€” you describe the end state you want (httpd installed, service started), not the shell steps to get there. Ansible figures out whether reality already matches and only acts when it doesn't.

$ansible-playbook site.yml
$ansible-playbook site.yml --syntax-check
$ansible-navigator run site.yml
// key insight

A playbook describes the desired END STATE, not a sequence of commands β€” so running it twice is safe and the second run changes nothing. That idempotency is the dividing line between Ansible and a bash script: a script blindly re-runs every command, while a playbook checks reality first and only acts on the drift.

// exam-ready Β· playbooks, runs & tags
$ansible-playbook site.yml
$--syntax-check
$--check
$--diff
$--list-tasks
$--list-hosts
$--list-tags
$-v / -vvvv
$--step
$--start-at-task="X"
$--limit HOST / -l
$--tags X / --skip-tags Y
$-e "k=v"
$play keys: name/hosts/become/vars/tasks
$PLAY RECAP: ok / changed / unreachable / failed / skipped
// check yourself
4 quick questions
Q1

What does it mean for a playbook to be "idempotent"?

Q2

Your playbook fails with a YAML parse error. What's the most likely cause?

Q3

What does `ansible-playbook site.yml --check` do?

Q4

On the second run of a working playbook the PLAY RECAP shows `changed=0`. Is that good or bad?

These aren't graded β€” they're just for active recall, which is what actually makes the lesson stick.

🧩
// next: the verbs

Ansible core modules

Tasks are just names; the modules do the actual work. Meet the everyday verbs β€” file, user, dnf, service β€” and the idempotent state= pattern that runs through all of them.

open β†’

Read this lesson as text6 chapters Β· quiz answers

  1. 01A playbook is desired state, written in YAML

    A playbook is an ordered list of PLAYS. A play maps a set of hosts to a list of TASKS, and each task calls a MODULE that does the actual work. It's declarative β€” you describe the end state you want (httpd installed, service started), not the shell steps to get there. Ansible figures out whether reality already matches and only acts when it doesn't.

    • $ ansible-playbook site.yml β€” run the playbook
    • $ ansible-playbook site.yml --syntax-check β€” validate the YAML, don't run
    • $ ansible-navigator run site.yml β€” TUI runner (optional)
  2. 02Anatomy of a play

    Every play has a small, fixed set of keys: name (a label), hosts (which group to target), become (escalate to root), vars (values you reuse), and tasks (the list of work). Each task is itself a name plus a single module call with its args. The YAML rules are strict: 2-space indentation, NEVER tabs, lists start with -, and mappings are key: value.

    • $ ansible-playbook site.yml --list-tasks β€” show the tasks without running
    • $ ansible-playbook site.yml --list-hosts β€” which hosts each play targets
    • $ ansible-playbook site.yml --list-tags β€” all tags defined in the file
  3. 03Run it and read the output

    Run it and Ansible walks the tasks top to bottom, printing a line per task per host with a status: ok (already correct, no change), changed (it acted), skipped (a when condition was false), failed, or unreachable (couldn't SSH in). The very first task is almost always Gathering Facts. At the end, the PLAY RECAP summarises the counts for each host.

    • $ ansible-playbook site.yml β€” ok / changed / skipped per task
    • $ ansible-playbook site.yml -v β€” more detail (up to -vvvv)
    • $ # PLAY RECAP: ok / changed / unreachable / failed / skipped β€” per-host totals
  4. 04Idempotency β€” the whole point

    Now run the exact same playbook a second time. Well-written modules check the current state first, see that httpd is already installed and already started, and report ok instead of changed β€” so the recap shows changed=0. Nothing happened because nothing needed to. That repeatability is what separates a playbook from a shell script. The command and shell modules are NOT idempotent unless you guard them with creates/removes or a when condition.

    • $ ansible-playbook site.yml β€” 2nd run β†’ changed=0
    • $ ansible-playbook site.yml --check β€” dry run, change nothing
    • $ ansible-playbook site.yml --diff β€” show what would change
  5. 05Debug & control the run

    When a play misbehaves, you have a toolbox before you start editing blindly. --syntax-check catches YAML errors; --check with --diff shows what would change without touching anything; -v through -vvvv turns up verbosity; --step pauses for confirmation before each task; --start-at-task jumps straight to a named task; and -e injects extra variables. Here --step is confirming each task one at a time.

    • $ ansible-playbook site.yml --step β€” confirm (y/n) each task
    • $ ansible-playbook site.yml --start-at-task="install httpd" β€” skip ahead to a task
    • $ ansible-playbook site.yml -e "pkg=nginx" β€” override a var at runtime
  6. 06Targeting & tags

    One file can hold several plays, each aimed at a different host group, and Ansible runs them top to bottom. --limit (or -l) narrows a run to a subset of hosts without editing the file. Tag individual tasks, then run only what you want with --tags, or run everything except some with --skip-tags. Here the run is limited to the web tag, so the package task is skipped.

    • $ ansible-playbook site.yml --tags web β€” run only tasks tagged web
    • $ ansible-playbook site.yml --skip-tags packages β€” run everything else
    • $ ansible-playbook site.yml -l servera β€” limit to one host

Quiz β€” answers

What does it mean for a playbook to be "idempotent"?
β†’Running it again makes no changes if the system already matches the desired state β€” the second run reports changed=0.Idempotency means the outcome is the same no matter how many times you apply it. Well-written modules check current state and only act on drift, so a repeat run of a converged system changes nothing.
Your playbook fails with a YAML parse error. What's the most likely cause?
β†’A tab was used for indentation, or the indenting is inconsistent β€” YAML requires spaces (2-space indent), never tabs.YAML is whitespace-sensitive and forbids tabs for indentation. Mixed or tab-based indenting is the classic parse failure. Run --syntax-check to catch it before a real run.
What does ansible-playbook site.yml --check do?
β†’Runs in check (dry-run) mode β€” it reports what WOULD change without making any changes; pair it with --diff to see the detail.--check is dry-run / check mode: modules predict their effect without applying it. Add --diff to see line-level changes (e.g. to a config file) that would have been made.
On the second run of a working playbook the PLAY RECAP shows changed=0. Is that good or bad?
β†’Expected and good β€” it proves idempotency: the system already matches desired state, so nothing needed to change.changed=0 on a converged system is exactly what you want. It demonstrates the playbook is idempotent β€” reality already matches the declared end state, so Ansible correctly does nothing.
// more in systems

keep going β€” these pair well with what you just learned.

see all systems β†’

See also

back to RHCSA / RHCE trackall lessons