What is a Playbook? #

If the inventory defines who we manage in the infrastructure, then the Playbook is the component that defines what automation actions we need to perform against them. Playbooks act as the heart of the Ansible automation ecosystem. Written in the declarative YAML markup language, playbooks let you document a series of ordered workflows — from system package installation, configuration editing, to service lifecycle management — to be executed repeatedly with consistent results. This article covers the basic concept of declarative automation, compares ad-hoc commands with playbooks, dissects playbook file anatomy, explains the main components of the play block, and guides you through execution and result report tracing.

The Declarative Concept #

One pillar of Ansible playbook writing philosophy that sets it apart from traditional bash scripts is the Declarative Approach. In imperative programming (like shell scripts), you write how the technical steps should be executed procedurally. You have to check whether a directory already exists, write if-else logic to check package status, and handle errors manually.

In contrast, the declarative approach in Ansible playbooks requires you to focus on defining the desired end state (desired state or what), not the technical procedure to reach it.

Here’s a visualization of how Ansible processes the declarative playbook state from the physical file to runtime execution on target servers:

flowchart TD
    A["YAML Playbook (site.yml)"] -->|"1. Parse & Syntax Validation"| B["Ansible Playbook Executor"]
    B -->|"2. Read Inventory (hosts.ini)"| C["Host Mapping (webservers, dbservers)"]
    C -->|"3. Assemble Variables & Precedence"| D["HostVars Database (Memory)"]
    D -->|"4. Execute Play 1 (Linear)"| E["Managed Node Batch 1"]
    D -->|"5. Execute Play 2 (Linear)"| F["Managed Node Batch 2"]
    E -->|"6. Collect Output & Recap"| G["Result Report in Terminal (PLAY RECAP)"]
    F -->|"6. Collect Output & Recap"| G

The Ansible Engine intelligently detects the target server’s current actual state, compares it with the end state you want in the playbook, and only takes change actions if the actual state doesn’t match the end state. This principle is what delivers the reliability of Idempotency.


Ad-hoc Commands vs Playbooks Comparison #

Ansible provides two execution methods: Ad-hoc Commands (Quick Commands) and Playbooks. You should choose the most efficient method based on your operational task context.

1. Ad-hoc Commands (Single Action Commands) #

Single commands run directly from the command-line terminal without being saved into an automation file. This method is great for quick, one-time maintenance tasks.

  • Use Cases: Checking target server memory capacity, doing a quick reboot, or copying one emergency file to managed nodes.
  • Command Examples:
    # Check disk capacity on servers in the webservers group
    ansible webservers -m command -a "df -h" -u ubuntu
    
    # Install the curl package on all managed nodes in parallel
    ansible all -m apt -a "name=curl state=present" --become
    

2. Playbooks (Structured Runbooks) #

YAML-format text files that store the entire series of complex automation workflows in a structured way.

  • Use Cases: Provisioning a new server from scratch, multi-tier application release deployment, database cluster configuration, or server security hardening.
  • Command Example:
    # Run the entire structured configuration workflow
    ansible-playbook -i inventory/production/hosts.ini playbooks/site.yml
    

The table below summarizes the comparative attributes between Ad-hoc and Playbook:

Comparison AttributeAd-hoc CommandsPlaybooks
Work StructureSingle, linear actionMulti-action, multi-host, hierarchical
PortabilityNot stored, one-time useStored in Git, highly reusable
ReadabilityHard to understand if arguments are longDeclarative, structured, easy to audit
Handlers SupportNot supportedNatively supported (event triggers)
Logic StorageVery limitedVery powerful (loops, conditionals, blocks)

Playbook File Structure Anatomy #

A playbook file consists of one or more Play blocks (Scenarios). A single Play maps a set of target servers (from the inventory) with the series of tasks (Tasks) to be executed by Ansible modules.

Here’s the visual anatomy of an industry-standard playbook file:

# File: playbooks/setup-webserver.yml
---
# The triple dash marks the start of the YAML document

# ==================== PLAY 1: Web Tier Setup ====================
- name: Configure the Nginx Web Server
  hosts: webservers
  become: true
  
  vars:
    http_port: 80
    app_directory: /var/www/my-app

  tasks:
    - name: Install the Nginx package
      apt:
        name: nginx
        state: present

    - name: Copy the virtual host configuration file
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: Trigger Nginx Restart

    - name: Ensure the Nginx service is active and enabled at boot
      systemd:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Trigger Nginx Restart
      systemd:
        name: nginx
        state: restarted

# ==================== PLAY 2: Database Tier Setup ====================
- name: Configure the PostgreSQL Database Server
  hosts: dbservers
  become: true

  tasks:
    - name: Install the PostgreSQL package
      apt:
        name: postgresql
        state: present

Structural Element Explanation: #

  1. - name: (Play Level): The global scenario explanation label. This text output appears in your terminal console to make workflow review easier.
  2. hosts:: Points to the target servers (in the example above, the webservers group in Play 1 and the dbservers group in Play 2).
  3. tasks:: The list of declarative work instructions executed sequentially from top to bottom.
  4. handlers:: Special tasks acting as signal receivers (event listeners). These tasks don’t run unless a task in the tasks: section produces a changed status and sends a notify: signal whose name matches that handler.

Main Components of the Play Block #

When composing a Play block, you can use Ansible’s built-in control parameters to manage the automation flow:

1. hosts (Target Filtering) #

Determines the execution target servers. You can use flexible writing patterns:

hosts: webservers:dbservers   # Group union (webservers OR dbservers)
hosts: webservers:&jakarta     # Group intersection (webservers that are also members of the jakarta group)
hosts: all:!bastion            # All servers in the inventory EXCEPT the bastion server

2. become (Privilege Escalation) #

Controls whether Ansible should run all tasks under this play with administrative (root) privileges:

become: true                  # Enable escalation
become_method: sudo           # Uses the sudo command (Linux default)
become_user: root             # Becomes the root user (default)

3. vars (Play Local Variables) #

Defines local configuration parameters that only apply within the scope of this play block. These variables have higher precedence than inventory variables in group_vars/all.yml.

4. gather_facts (Target Data Collection) #

By default, before running the first task, Ansible runs an implicit task called Gathering Facts to detect detailed target server information (like IP addresses, OS type, remaining RAM, disk capacity).

gather_facts: false

If your playbook doesn’t need any target hardware information variables at all (for example, your playbook only triggers external APIs or performs reboots), you are strongly recommended to set gather_facts: false to significantly cut playbook execution duration (saving SSH handshake data negotiation time).


Complete CLI Execution Guide #

You use the ansible-playbook binary command to run your playbook scenario files. Ansible provides advanced CLI arguments to help with safe execution control.

Here’s the list of execution commands every automation developer must master:

1. Running a Standard Playbook #

ansible-playbook -i inventory/production/hosts.ini playbooks/setup-webserver.yml

2. Dynamically Limiting Targets (Limit) #

If the playbook says hosts: webservers but you only want to test execution against the prod-web-01 server without changing the physical playbook code:

ansible-playbook -i inventory/production/hosts.ini playbooks/setup-webserver.yml --limit "prod-web-01.unisbadri.com"

3. Running a Simulation Test (Dry Run) #

Using the --check option (or dry run) makes Ansible process the playbook flow and compare system status without sending any modification commands to target servers. Very important before applying changes on production servers:

ansible-playbook -i inventory/production/hosts.ini playbooks/setup-webserver.yml --check

4. Showing Code Change Details (Diff Mode) #

The --diff option displays the system configuration file changes visually (similar to git diff) before and after execution. Great when combined with check mode:

ansible-playbook -i inventory/production/hosts.ini playbooks/setup-webserver.yml --check --diff

5. Resuming Execution from a Specific Task #

If your playbook execution fails mid-way at task number 10 due to a network problem, after fixing the issue, you can resume execution directly from the failed task without repeating tasks 1 through 9:

ansible-playbook -i inventory/production/hosts.ini playbooks/setup-webserver.yml --start-at-task "Copy the virtual host configuration file"

Understanding Result Reports and Play Recap #

When a playbook executes, Ansible prints an interactive report to the terminal screen. At the end of execution, Ansible presents a summary report called PLAY RECAP:

PLAY RECAP *****************************************************************************************
prod-web-01.unisbadri.com  : ok=4  changed=1  unreachable=0  failed=0    skipped=0    rescued=0
prod-web-02.unisbadri.com  : ok=3  changed=0  unreachable=0  failed=0    skipped=1    rescued=0
prod-db-01.unisbadri.com   : ok=2  changed=0  unreachable=1  failed=0    skipped=0    rescued=0

Report Status Explanation: #

  • ok: The number of tasks that ran successfully without requiring modification actions because the target server was already in the desired end state (idempotency satisfied).
  • changed: The number of tasks that successfully made system modification changes to match the desired end state.
  • unreachable: Indicates Ansible failed to initiate an SSH connection to the target server (network problems, wrong user, closed port, or rejected private key).
  • failed: The number of tasks that failed to execute (script crashes, wrong module arguments, missing package dependencies). If a host has a failed status, Ansible by default stops all subsequent tasks for that host.
  • skipped: The number of tasks skipped because they didn’t meet the when conditional parameters.

Playbooks as Living Documentation #

In the modern infrastructure engineering era, one of the main added values of playbooks is their function as Executable Documentation.

In conventional server administration, server installation guides are recorded in static text files or internal company wiki pages. Those wikis are often inaccurate because over time, the actual server system configuration keeps changing without anyone updating the wiki notes.

Ansible playbooks solve this problem. Because playbooks are written in declarative YAML with communicative descriptive - name: parameters, playbooks automatically represent the actual blueprint of your infrastructure that anyone can read, while also being runnable at any time to ensure server state compliance.

# File: playbooks/audit-hardening.yml
# This file acts as an audit file and security compliance enforcer
---
- name: Audit and Enforce SSH Security Compliance
  hosts: all
  become: true
  
  tasks:
    - name: Ensure root SSH access is closed
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PermitRootLogin"
        line: "PermitRootLogin no"
        state: present
      notify: Restart SSH Service

    - name: Ensure SSH password authentication is disabled
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PasswordAuthentication"
        line: "PasswordAuthentication no"
        state: present
      notify: Restart SSH Service
      
  handlers:
    - name: Restart SSH Service
      systemd:
        name: sshd
        state: restarted

Sharing this file to the company Git repository lets the security audit team review the SSH security status of all servers just by reading the code above, without having to manually log into hundreds of servers.


Summary #

  • Declarative End State — Ansible playbooks focus on defining the desired end state, letting the engine handle the idempotent workflow details automatically.
  • Work Efficiency — Use ad-hoc commands only for quick one-time diagnostic actions; use playbooks for structured, reusable configuration automation.
  • Central Anatomy — Playbooks consist of Play blocks that map target servers (inventory) with automation instructions (tasks) and follow-up action triggers (handlers).
  • gather_facts Time Savings — Disable target fact gathering (gather_facts: false) if your playbook workflow doesn’t need to read server hardware parameters.
  • Advanced CLI Control — Get into the habit of running check mode simulations (--check --diff) before rolling automation releases to production environments to detect potential conflicts.
  • PLAY RECAP Evaluation — Monitor the final recap output diligently to track target host health status, especially if failed or unreachable indicators are detected.
  • Integrated Documentation — Write descriptive task name parameters (- name:) that explain the task’s functional purpose, not just the terminal command being run.

← Previous: Best Practice Next: Structure →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact