Day-03 | Write Your First Ansible Playbook | For Absolute Beginners
This chapter introduces the core concepts of Ansible Playbooks, beginning with a foundational guide to YAML syntax. It covers the structural hierarchy of playbooks—including plays, tasks, and modules—and provides a step-by-step walkthrough for writing, executing, and debugging your first playbook to deploy a static web application on an AWS EC2 instance.
1. The Problem with Unstructured Data (Why YAML?)
When building automation scripts or applications (such as a Python script), you often need to feed external data into the system. This data could be a list of students, server configurations, or IP addresses.
Historically, developers used raw text files, but text files present significant parsing challenges due to a lack of standardization.
Real-Life Analogy: The Text File Formatting Chaos
Imagine asking three different administrators to write a list of students in a text file:
- Admin A writes each name on a new line.
- Admin B writes all names on a single line, separated by commas.
- Admin C writes names separated by spaces and leaves blank lines between them.
Because a standard text file has no strict syntax or semantic rules, writing a parser for your application that handles all these variations without throwing errors is incredibly difficult.
To solve this, the industry adopted standardized, structured data serialization formats. The two most prominent are JSON (JavaScript Object Notation) and YAML (YAML Ain't Markup Language).
Why Ansible Uses YAML Over JSON
While JSON is highly structured and widely supported, it relies heavily on curly braces {}, square brackets [], and strict quotation marks, making it difficult for humans to read and write manually.
YAML solves this by using indentation (spaces) instead of brackets, making it exceptionally clean, highly readable, and easy to learn.
2. YAML Syntax Fundamentals
To write Ansible Playbooks, you only need to master four basic concepts in YAML. Every YAML file should begin with three hyphens (---) to signal the start of the document.
I. Scalars (Strings, Numbers, and Booleans)
Scalars are basic key-value pairs. The syntax is always key: value (note the mandatory space after the colon).
---
name: "Abishek" # String (can be single-quoted, double-quoted, or unquoted)
age: 29 # Integer
is_working: true # Boolean (true/false)
II. Lists (Sequences)
Lists are
collections of values. Each item in a list starts with a hyphen followed by a space (- ), indented under the parent key.
---
tasks:
- "YouTube content creation"
- "Write Ansible code"
- "Exercise"
III. Dictionaries (Maps / Key-Value Pairs)
Dictionaries are groups of key-value pairs nested under a parent key. They do not use hyphens.
---
address:
street: "123 DevOps Lane"
city: "Bengaluru"
state: "Karnataka"
country: "India"
IV. List of Dictionaries
This structure combines lists and dictionaries. It is the most common structure in Ansible Playbooks, where each item in a list is itself a dictionary of configurations.
---
- name: "Abishek"
role: "Instructor"
- name: "John"
role: "Student"
Note: Always use spaces for indentation in YAML. Never use the
Tabkey, as it will break the parser and cause syntax errors. You can validate your files using online tools like YAML Lint.
3. Ansible Playbook Architecture
An Ansible Playbook is a YAML file that contains one or more Plays. A Play is a collaborative configuration mapped to a specific set of managed hosts.
The structural hierarchy flows downward from the Playbook level to individual execution modules:
Real-World Playbook Structure Example
Below is a conceptual representation of a multi-play playbook designed to configure both a web server and a database server:
---
# Play 1: Configure Web Servers
- name: "Configure Web Server"
hosts: webservers
become: true
tasks:
- name: "Install Apache"
ansible.builtin.apt:
name: apache2
state: present
# Play 2: Configure Database Servers
- name: "Configure Database Server"
hosts: dbservers
become: true
tasks:
- name: "Install PostgreSQL"
ansible.builtin.apt:
name: postgresql
state: present
4. Anatomy of a Play
Every Play within a playbook contains specific directives that define where, how, and what actions will execute.
hosts: Specifies the target machines from your inventory file (e.g.,all,webservers, or specific IP addresses) where this play should run.become: When set totrue, this instructs Ansible to escalate privileges (typically usingsudo) to execute tasks as therootuser. This is required for administrative tasks like installing packages or modifying system configuration files.remote_user: Defines the SSH user account used to log into the managed nodes (e.g.,ubuntu,ec2-user).tasks: A list of sequential actions that Ansible executes on the target hosts.
5. Understanding Tasks and Modules
While Playbooks, Plays, and Tasks organize the execution flow, Modules are the actual engines that perform the work.
Ansible modules are discrete, reusable scripts that execute specific system actions (e.g., installing a package, copying a file, restarting a service).
Finding and Using Modules
You do not need to memorize module parameters. Ansible provides a comprehensive Collection Index in its official documentation.
To find a module:
- Navigate to the Ansible Documentation.
- Go to the Collection Index -> Ansible Built-in Namespace.
- Locate the module matching your target operating system or task (e.g.,
aptfor Debian/Ubuntu package management,copyfor transferring files).
6. Step-by-Step Evolution: Writing the First Playbook
Let's build a playbook that installs an Apache web server on an Ubuntu EC2 instance and deploys a custom static HTML page.
Step 1: Create the Static HTML File
Create a file named index.html in your local working directory:
<!DOCTYPE html>
<html>
<head>
<title>My First Ansible Deployment</title>
</head>
<body>
<h1>I'm learning Ansible with Abishek Veeramalla!</h1>
</body>
</html>
Step 2: The Naive vs. Production-Ready Playbook Evolution
The Naive/Incorrect Approach
In this initial draft, the developer makes two critical mistakes: setting the package state to absent (which uninstalls it) and forgetting privilege escalation (become: true).
# NAIVE PLAYBOOK (DO NOT USE)
---
- name: Deploy Web Server
hosts: all
tasks:
- name: Install Apache
ansible.builtin.apt:
name: apache2
state: absent # Error: This uninstalls the package!
- name: Copy Web Page
ansible.builtin.copy:
src: index.html
dest: /var/www/html/index.html
# Error: This will fail due to Permission Denied in /var/www/html/
The Production-Ready Approach
This corrected, decoupled version uses become: true for root access, sets the state to present to ensure installation, and explicitly configures file permissions, owners, and groups.
# PRODUCTION-READY PLAYBOOK (first-playbook.yaml)
---
- name: "Deploy Static Web Application"
hosts: all
become: true
tasks:
- name: "Ensure Apache Web Server is Installed"
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
- name: "Deploy Custom index.html to Apache Document Root"
ansible.builtin.copy:
src: index.html
dest: /var/www/html/index.html
owner: root
group: root
mode: '0644'
7. Execution and Runtime Lifecycle
To execute your playbook, use the ansible-playbook command-line tool, pointing to your inventory file and the playbook YAML file.
Execution Command
ansible-playbook -i inventory.ini first-playbook.yaml
The Ansible Execution Flow
When you run the command, Ansible executes the tasks sequentially across all target hosts:
Understanding the Execution Output
During execution, Ansible outputs the status of each task using a color-coded system:
ok(Green): The task was evaluated, but no changes were necessary because the system was already in the desired state (Idempotency).changed(Yellow): The task successfully modified the target system to match the desired state (e.g., installed a new package or updated a file).failed(Red): The task encountered an error (e.g., network timeout, missing package, or permission denied) and halted execution.
8. Real-World Debugging & AWS Configuration
If your playbook runs successfully but you cannot access the web server in your browser, follow this systematic debugging workflow.
Step 1: Verify the Apache Service Status on the Remote Host
SSH into your managed EC2 node and verify that the Apache process is active and running:
# Check if the process is running
sudo ps -ef | grep apache2
# Check the systemd service status
sudo systemctl status apache2
Step 2: Verify the File Deployment
Ensure that the index.html file was copied to the correct directory with the correct permissions:
ls -la /var/www/html/index.html
cat /var/www/html/index.html
Step 3: Configure AWS Security Groups (The Common Pitfall)
By default, AWS blocks external traffic to EC2 instances. If Apache is running but the page times out in your browser, you must allow inbound HTTP traffic:
- Open the AWS EC2 Console.
- Select your managed instance and click on the Security tab.
- Click on the active Security Group.
- Select Edit inbound rules.
- Add a rule: HTTP (Port 80) with source Anywhere-IPv4 (
0.0.0.0/0). - Save the rules and access your instance's public IP in a browser:
http://<EC2_PUBLIC_IP>.
9. Tabular Synthesis of Ansible Modules & YAML Structures
Ansible Modules Used in This Lesson
YAML Structural Elements
10. Summary and Key Takeaways
- YAML is the Standard: Ansible uses YAML because it is highly human-readable, standardized, and avoids the complex syntax of JSON or the inconsistency of raw text files.
- Indentation is Critical: YAML relies strictly on spaces for nesting. Never use tabs, and maintain consistent indentation (typically 2 spaces).
- Playbooks are Play Lists: An Ansible Playbook is a collection of one or more plays. Each play maps a specific group of hosts to a series of tasks.
- Modules Do the Work: Tasks are logical wrappers; the actual system changes are executed by Ansible modules (such as
aptorcopy). - Privilege Escalation: Use
become: trueat the play or task level to run commands with administrative (sudo) privileges. - Idempotency in Action: If you run a playbook multiple times, Ansible only makes changes if the target system deviates from the defined state. If the package is already installed, Ansible marks the task as
okand skips it. - The "Gathering Facts" Step: Every playbook run begins with an automatic discovery step where Ansible gathers system variables (IPs, OS distribution, disk space) from the target hosts.
- Network Security Matters: Successful playbook execution is only half the battle; real-world deployments often require configuring cloud firewalls (like AWS Security Groups) to allow traffic on service ports like Port 80.