Ansible Playbook Anatomy
A playbook is a YAML file containing one or more plays. Each play targets a set of hosts and defines a sequence of tasks to run on them. Understanding the relationship between playbooks, plays, tasks, and modules is the conceptual breakthrough that makes everything else in Ansible click into place.
The Hierarchy: Playbook → Play → Task → Module
Think of it as nested containers: a playbook holds plays, plays hold tasks, and each task invokes exactly one module. This hierarchy maps directly onto how you think about automation: "On my web servers (play), install nginx (task using apt module), start nginx (task using service module), and deploy the config file (task using template module)."
A Complete Annotated Playbook
---
# The three dashes mark the start of a YAML document
# A playbook is a list of plays
- name: Configure Web Servers # Play name - shown in output, should be descriptive
hosts: webservers # Which hosts from inventory to target
become: true # Use privilege escalation (sudo) for all tasks
gather_facts: true # Collect system facts before tasks run (default)
vars: # Play-level variables
http_port: 80
document_root: /var/www/html
tasks: # The ordered list of tasks in this play
- name: Ensure Nginx is installed # Task name - shown in output
apt: # Module name
name: nginx # Module argument
state: present # Desired state
update_cache: true # Additional argument
- name: Ensure Nginx is running and enabled
service:
name: nginx
state: started
enabled: true
- name: Create document root directory
file:
path: "{{ document_root }}" # Using a variable with Jinja2 syntax
state: directory
owner: www-data
mode: '0755'
- name: Deploy index.html
copy:
content: "<h1>Server {{ inventory_hostname }} is ready</h1>"
dest: "{{ document_root }}/index.html"
owner: www-data
mode: '0644'
notify: Reload Nginx # Trigger the handler named "Reload Nginx"
handlers: # Tasks that run only when notified
- name: Reload Nginx
service:
name: nginx
state: reloaded
- name: Configure Database Servers # Second play in the same playbook
hosts: databases
become: true
tasks:
- name: Install PostgreSQL
apt:
name: postgresql
state: presentThe Play
A play has these key components:
name— A human-readable description. Required by convention, not technically mandatory, but essential for readable output.hosts— Specifies which inventory hosts this play targets. Can be a group name, a hostname, a comma-separated list, a wildcard pattern, orall.become— Enable privilege escalation for all tasks in this play.gather_facts— Whether to collect system facts before tasks (default true). Set to false to speed up plays that do not need facts.vars— Variables scoped to this play.tasks— The ordered list of tasks.handlers— Tasks triggered by notifications from other tasks.
The Task
Every task has a name and invokes one module with its arguments. Task names appear in playbook output and in Tower/AWX logs — write them as clear, present-tense descriptions of what the task does. "Ensure Nginx is installed" is better than "nginx" or "install stuff".
Tasks run in order, one at a time, on all targeted hosts. If a task fails on a host, that host is removed from the remaining tasks (unless you configure error handling — covered in Module 4).
The Module
A module is the implementation of a specific type of operation. When you write apt: in a task, you are invoking the apt module. The module arguments below it (name, state, update_cache) are the parameters that configure what the module does. Every module has its own set of arguments documented in the Ansible module index.
Special Variables Available in Playbooks
inventory_hostname— The name of the current host as defined in inventory (web01, db01, etc.)ansible_host— The actual IP or hostname used for connectionansible_facts— Dictionary of all gathered facts for the current hosthostvars— Access variables from other hosts:hostvars['web01']['ansible_ip_addresses']groups— Dictionary of all inventory groups and their member hostsplay_hosts— List of hosts active in the current play
Running a Playbook
ansible-playbook -i inventory.ini site.yml
Common useful flags:
--check— Dry run mode: shows what would change without making changes--diff— Shows the diff for file changes--limit web01— Run only against a specific host--tags install— Run only tasks tagged "install"--start-at-task "Task Name"— Start from a specific task-v— Verbose output
Try This: Read a Playbook Out Loud
Take the annotated playbook above and read it aloud as a narrative: "On my web servers, with sudo enabled... first ensure nginx is installed... then ensure nginx is running and enabled on boot..." This exercise reinforces that a well-written playbook is nearly self-documenting and helps you internalise the task sequence pattern you will use throughout your Ansible career.
Summary
A playbook contains one or more plays. Each play targets a set of hosts and contains an ordered list of tasks. Each task invokes one module with specific arguments. The play controls overall settings like privilege escalation, fact gathering, and variables. Handlers are special tasks triggered only when other tasks report a change. Understanding this hierarchy is the conceptual foundation of all Ansible automation.
