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 system_setup.yml.

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: all tells Ansible to run this play against every host in your inventory — that includes node1, node2, and node3 across the web and database groups.

  • become: true enables privilege escalation. Ansible will use sudo to 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_name is used by the user creation task

  • web_package and web_service manage Apache on web servers

  • db_package and db_service manage 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 ansible.builtin prefix means the module is part of Ansible’s core library. Using fully qualified module names is a best practice that avoids ambiguity when multiple collections are installed.

☑️ Task 4 - Templates

The template files (templates/motd.j2 and templates/index.html.j2) were pre-created for you as part of the lab setup — they were NOT generated by the automation coding assistant. The coding assistant generated the ansible.builtin.template tasks that deploy these templates to the managed nodes, but the Jinja2 template content itself was written in advance and placed in the templates/ directory of your workspace.

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 (web vs database)

  • 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 Next 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.