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.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.
- Β·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
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
[Gathering Facts]β¦[install httpd]β¦[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.ymlA 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.
$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 / skippedWhat does it mean for a playbook to be "idempotent"?
Your playbook fails with a YAML parse error. What's the most likely cause?
What does `ansible-playbook site.yml --check` do?
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.
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.
Read this lesson as text6 chapters Β· quiz answers
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)
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 arekey: 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
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
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
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
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
webtag, 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 --checkdo? - β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.
keep going β these pair well with what you just learned.
Ansible control node & inventory
Ansible is agentless β one control node pushes work to a fleet over SSH. Install ansible-core, write an inventory with groups, ping the fleet, and fire ad-hoc commands. RHCE-ready.
Ansible core modules
file, user, copy, dnf, service β the modules that do the real work. Declare the desired state and let Ansible converge to it, idempotently. RHCE-ready.
Ansible variables & facts
Which value wins when the same variable is set in five places? Variable precedence, gathered facts, and ansible_facts β visualized as a ladder. RHCE-ready.