An Ansible Playbook is a human-readable, declarative configuration file written in YAML (Yet Another Markup Language) that defines automation workflows across managed infrastructure nodes. While Ansible ad-hoc commands are ideal for quick, single-task execution (such as checking system uptime or pinging hosts), Playbooks serve as reusable, version-controlled blueprints for orchestrating multi-step server setups, package installations, file deployments, and service configurations.
A playbook maps target servers (defined in an Inventory) to an ordered sequence of actions called Tasks. Each task invokes an Ansible Module (such as file, yum, systemd, or copy) to enforce a desired end-state on the target machine.
graph TD
subgraph ControlNode ["Jump Host (Ansible Control Node)"]
ThorUser["User: thor"]
InvFile["Inventory File<br/>/home/thor/ansible/inventory"]
PlaybookFile["Playbook File<br/>/home/thor/ansible/playbook.yml"]
AnsibleCLI["Ansible Engine<br/>ansible-playbook"]
end
subgraph StratosDC ["Stratos Datacenter (Managed Node)"]
App1["App Server 1: stapp01<br/>IP: 172.16.238.10<br/>User: tony"]
TargetFile["Target File<br/>/tmp/file.txt"]
end
InvFile -->|"Supply Target Host & Auth"| AnsibleCLI
PlaybookFile -->|"Supply Play & Tasks (file module)"| AnsibleCLI
AnsibleCLI -->|"1. SSH Connection (Port 22)"| App1
AnsibleCLI -->|"2. Execute task state: touch"| TargetFile
App1 -->|"3. Return Task Status ok/changed"| AnsibleCLI
An Ansible playbook consists of one or more Plays in a YAML list. Each play contains specific configuration directives:
- name: ..., hosts: ...):
name: A descriptive label summarizing the purpose of the play.hosts: Specifies the target hosts or group names from the inventory (e.g., stapp01, app_servers, or all).become: yes: Enables privilege escalation (sudo) to execute tasks with root permissions when modifying system files or directories.gather_facts: yes/no: Controls whether Ansible automatically collects remote system information (IP addresses, OS distributions, disk space) before executing tasks.Tasks (tasks:):
A list of individual actions executed sequentially top-to-bottom on each target host. If a task fails on a host, Ansible halts execution for that host while continuing for remaining nodes.
file: Manages files, directories, and symlinks (creating, deleting, changing permissions or ownership).yum / apt: Manages package installation and updates.service / systemd: Manages service states (started, stopped, restarted, enabled).copy: Transfers files from the control node to remote hosts.Variables (vars:):
Key-value pairs used to parameterize playbooks, making them flexible across staging and production environments.
handlers:, notify:):
Special tasks triggered only when a preceding task reports a changed state (e.g., restarting Apache httpd only when its configuration file is updated).---
- name: Example Ansible Playbook Structure
hosts: stapp01
become: yes
vars:
file_path: /tmp/file.txt
tasks:
- name: Ensure target file exists
ansible.builtin.file:
path: ""
state: touch
mode: '0644'
When playbooks fail or behave unexpectedly, DevOps engineers rely on several core diagnostic strategies:
--syntax-check):
Validates YAML syntax and playbook structure without executing any tasks on target hosts:
ansible-playbook -i inventory playbook.yml --syntax-check
--check):
Simulates playbook execution to predict changes without modifying remote target systems:
ansible-playbook -i inventory playbook.yml --check
-v, -vv, -vvv, -vvvv):
Appends detailed execution logs to stdout:
-v: Prints task results.-vv: Prints task arguments and module inputs.-vvv: Prints connection parameters and SSH details.-vvvv: Enables full SSH connection debugging (useful for troubleshooting authentication or key failures).debug Module:
Prints variable values, registered task outputs, or custom messages during execution:
```yaml
ignore_errors: yes: Instructs Ansible to continue executing subsequent tasks even if the current task fails.failed_when: Overrides standard failure criteria based on custom return conditions.block / rescue / always: Groups tasks into exception-handling blocks similar to try/catch blocks in programming languages.| Host Role | Hostname / Alias | IP Address | SSH User | Inventory Directory | Target File |
|---|---|---|---|---|---|
| Control Node | jump_host |
172.16.238.2 |
thor |
/home/thor/ansible |
N/A |
| App Server 1 | stapp01 |
172.16.238.10 |
tony |
Configured in inventory |
/tmp/file.txt |
/home/thor/ansible/inventory/home/thor/ansible/playbook.ymlstapp01)file modulefile.txt under /tmp/ (state: touch)ansible-playbook -i inventory playbook.ymlSSH into the Jump Host as user thor:
ssh thor@jump_host
Navigate to the Ansible project directory:
cd /home/thor/ansible
Inspect the existing inventory file to ensure the target host stapp01 is correctly defined with its connection parameters (ansible_host, ansible_user, ansible_ssh_pass):
cat /home/thor/ansible/inventory
Corrected INI Inventory (/home/thor/ansible/inventory):
[app_servers]
stapp01 ansible_host=stapp01 ansible_user=tony ansible_ssh_pass=******
Test basic connectivity using an ad-hoc Ansible ping:
ansible stapp01 -i inventory -m ping
Expected Output:
stapp01 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Create and open /home/thor/ansible/playbook.yml using vi or nano:
vi /home/thor/ansible/playbook.yml
Add the following play definition to create /tmp/file.txt on stapp01 using the file module:
---
- name: Create an empty file on App Server 1
hosts: stapp01
become: yes
tasks:
- name: Create /tmp/file.txt
ansible.builtin.file:
path: /tmp/file.txt
state: touch
[!NOTE]
state: touchcreates an empty file if it does not exist, or updates its access/modification timestamp if it already exists, ensuring idempotency.
Run the Ansible playbook syntax check to verify YAML formatting and directive structure:
ansible-playbook -i inventory playbook.yml --syntax-check
Expected Output:
playbook: playbook.yml
Run the playbook against the inventory file:
ansible-playbook -i inventory playbook.yml
Expected Terminal Output:
PLAY [Create an empty file on App Server 1] ******************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [stapp01]
TASK [Create /tmp/file.txt] ***********************************************************************
changed: [stapp01]
PLAY RECAP ****************************************************************************************
stapp01 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Execute an ad-hoc command to confirm that /tmp/file.txt exists on stapp01:
ansible stapp01 -i inventory -m shell -a "ls -l /tmp/file.txt"
Expected Output:
stapp01 | CHANGED | rc=0 >>
-rw-r--r-- 1 root root 0 Aug 5 18:30 /tmp/file.txt
Re-run the playbook execution command to confirm idempotency. On the second execution, Ansible should report changed=0:
ansible-playbook -i inventory playbook.yml
Expected Output:
PLAY RECAP ****************************************************************************************
stapp01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
.yml files.ansible.builtin.file instead of file) for clarity and compatibility with newer Ansible versions.file, copy, template) over raw command or shell modules whenever possible to guarantee reproducible state.ansible-playbook --syntax-check before running playbooks in production to catch indentation or missing key errors early.