1.2 - Understand Your Playbook
Before running your playbook, take a moment to understand what each section does. Open system_setup.yml in the VS Code tab and follow along as we walk through each component.
1. Playbook Anatomy
☑️ Task 1 - The play header
|
Switch to the VS Code tab and open |
Look at the top of your playbook:
---
- name: Web and Database Infrastructure Setup
hosts: all
become: true
-
name:is a human-readable label. It appears in the output when you run the playbook, making it easier to follow what is happening. -
hosts: alltells Ansible to run this play against every host in your inventory — that includesnode1,node2, andnode3across thewebanddatabasegroups. -
become: trueenables privilege escalation. Ansible will usesudoto run tasks as root, which is required for installing packages, creating users, and modifying system files.
☑️ Task 2 - Variables
Find the vars: section in your playbook:
vars:
user_name: padawan
web_package: httpd
web_service: httpd
db_package: mariadb-server
db_service: mariadb
Variables let you define values once and reference them throughout the playbook using {{ variable_name }} syntax. If you need to change a package name or service name later, you update it in one place instead of editing every task.
In this playbook:
-
user_nameis used by the user creation task -
web_packageandweb_servicemanage Apache on web servers -
db_packageanddb_servicemanage MariaDB on database servers
This pattern — defining all configurable values at the top — is a best practice that makes playbooks easier to maintain and reuse across different environments.
☑️ Task 3 - Tasks and conditionals
The tasks: section is the core of the playbook — each task is an action Ansible performs on the managed hosts. Tasks run in order, top to bottom.
Look at the Apache installation task:
- name: Install Apache on web servers
ansible.builtin.dnf:
name: "{{ web_package }}"
state: present
when: inventory_hostname in groups['web']
Each task has:
-
name:— a description that appears in the output -
A module (
ansible.builtin.dnf) — the tool Ansible uses to perform the action -
Parameters (
name,state) — configuration for that module -
when:— a conditional that controls whether the task runs
The when: inventory_hostname in groups['web'] conditional means this task only runs on hosts that belong to the web group in your inventory (node1 and node2).
Now look at the MariaDB installation task:
- name: Install MariaDB on database servers
ansible.builtin.dnf:
name: "{{ db_package }}"
state: present
when: inventory_hostname in groups['database']
This uses the same pattern but targets the database group (node3). By using different conditionals, a single playbook that runs against hosts: all can apply different configurations to different server groups.
Tasks without a when: conditional — like user creation and MOTD deployment — run on all hosts.
|
The |
☑️ Task 4 - Templates
|
The template files ( |
Find the template tasks in your playbook. There are two:
- name: Deploy status page to web servers
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
mode: "0644"
when: inventory_hostname in groups['web']
- name: Deploy MOTD template
ansible.builtin.template:
src: templates/motd.j2
dest: /etc/motd
mode: "0644"
The ansible.builtin.template module takes a Jinja2 template file and renders it with real values before copying it to each host. Open templates/motd.j2 in VS Code to see the template:
Welcome to {{ ansible_hostname }}.
Role: {% if inventory_hostname in groups['web'] %}Web Server{% elif inventory_hostname in groups['database'] %}Database Server{% endif %}
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
Architecture: {{ ansible_architecture }}
The {{ }} placeholders are replaced with Ansible facts — information Ansible automatically collects from each host during the "Gathering Facts" step. Because each host has different facts, the same template produces different output on each node:
-
On
node1: "Welcome to node1. Role: Web Server …" -
On
node3: "Welcome to node3. Role: Database Server …"
The index.html.j2 template works the same way — it produces a status page with each web server’s hostname, IP address, and OS information. You will see the result when you run the playbook and use curl to view the page.
☑️ Task 5 - Handlers
Look at the handlers: section at the bottom of your playbook:
handlers:
- name: Restart Apache
ansible.builtin.service:
name: "{{ web_service }}"
state: restarted
Handlers are special tasks that only run when notified by another task. A task notifies a handler using the notify: keyword — for example, when the status page template is deployed, the task notifies the "Restart Apache" handler to restart the web service.
Handlers have two important properties:
-
They only run when notified — if no task triggers them, they are skipped entirely
-
They run once at the end of the play, even if multiple tasks notify the same handler
This makes handlers ideal for service restarts: you do not want to restart Apache after every single configuration change. Instead, all changes are applied first, and the service restarts once at the end.
2. Learning Outcomes
By completing this module, you now understand:
-
How a play header defines the target hosts and privilege escalation
-
How
vars:lets you define reusable values in one place -
How tasks run in order and use modules with parameters
-
How
when:conditionals target specific inventory groups (webvsdatabase) -
How Jinja2 templates render host-specific content using Ansible facts
-
How handlers run only when notified, and only once at the end of the play
3. Embracing the Next Challenge
✅ Next Challenge
Once you have reviewed each section, press the
button to proceed to the next challenge where you will run this playbook and see it in action.
🐛 Encountered an issue?
If you have encountered an issue or noticed something not quite right, please open an issue on the Introduction to automation coding assistant repository.