
Creating and Running Your First Ansible Playbook
Ansible is a powerful automation tool that has revolutionized the way system administrators and DevOps engineers manage infrastructure. By leveraging YAML-based playbooks, Ansible enables you to automate complex server configurations, application deployments, and system maintenance tasks across hundreds or thousands of servers simultaneously. This guide will walk you through creating your first Ansible playbook from scratch, covering everything from basic concepts to advanced troubleshooting techniques that will help you avoid common pitfalls and implement robust automation solutions.
How Ansible Playbooks Work
Ansible operates on a push-based model where your control machine connects to managed nodes via SSH and executes tasks remotely. Unlike other configuration management tools, Ansible doesn’t require agents on target systems, making it lightweight and easy to deploy.
A playbook is essentially a YAML file containing a series of plays, where each play targets specific hosts and defines tasks to be executed. The Ansible engine reads these playbooks and translates them into Python modules that run on target systems. The idempotent nature ensures that running the same playbook multiple times produces consistent results without unwanted side effects.
Key components of an Ansible playbook include:
- Hosts: Target systems where tasks will be executed
- Tasks: Individual actions like installing packages or copying files
- Modules: Reusable units that perform specific operations
- Variables: Dynamic values that customize playbook behavior
- Handlers: Special tasks triggered by other tasks
Setting Up Your Ansible Environment
Before creating your first playbook, you need to install Ansible and configure your environment properly. The setup process varies depending on your operating system.
For Ubuntu/Debian systems:
sudo apt update
sudo apt install ansible sshpass
ansible --version
For RHEL/CentOS/Fedora:
sudo dnf install ansible sshpass
# or for older systems
sudo yum install epel-release
sudo yum install ansible sshpass
Next, create your project directory structure:
mkdir ~/ansible-project
cd ~/ansible-project
mkdir inventories playbooks roles group_vars host_vars
Create an inventory file that defines your target hosts:
# inventories/production
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11
[databases]
db1.example.com ansible_host=192.168.1.20
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
Test connectivity to your managed nodes:
ansible all -i inventories/production -m ping
Creating Your First Playbook
Let’s create a comprehensive playbook that demonstrates common automation tasks. This example will install and configure a web server with security hardening.
---
- name: Configure Web Server
hosts: webservers
become: yes
gather_facts: yes
vars:
nginx_port: 80
ssl_port: 443
server_name: "{{ inventory_hostname }}"
tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install required packages
package:
name:
- nginx
- ufw
- fail2ban
state: present
- name: Start and enable nginx
systemd:
name: nginx
state: started
enabled: yes
- name: Configure firewall
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "{{ nginx_port }}"
- "{{ ssl_port }}"
- "22"
notify: reload ufw
- name: Enable firewall
ufw:
state: enabled
policy: deny
direction: incoming
- name: Create nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
backup: yes
notify: restart nginx
- name: Ensure nginx is running
service:
name: nginx
state: started
handlers:
- name: restart nginx
systemd:
name: nginx
state: restarted
- name: reload ufw
ufw:
state: reloaded
Create a Jinja2 template for nginx configuration:
# templates/nginx.conf.j2
server {
listen {{ nginx_port }};
server_name {{ server_name }};
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# Logging
access_log /var/log/nginx/{{ server_name }}_access.log;
error_log /var/log/nginx/{{ server_name }}_error.log;
}
Running Your Playbook
Execute your playbook using the ansible-playbook command with various options for different scenarios:
# Basic execution
ansible-playbook -i inventories/production playbooks/webserver.yml
# Dry run to test without making changes
ansible-playbook -i inventories/production playbooks/webserver.yml --check
# Run with increased verbosity for debugging
ansible-playbook -i inventories/production playbooks/webserver.yml -vvv
# Limit execution to specific hosts
ansible-playbook -i inventories/production playbooks/webserver.yml --limit web1.example.com
# Run specific tags only
ansible-playbook -i inventories/production playbooks/webserver.yml --tags "configuration"
For better organization, create an ansible.cfg file in your project root:
[defaults]
inventory = inventories/production
remote_user = ubuntu
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
pipelining = True
Real-World Use Cases and Examples
Ansible playbooks shine in numerous practical scenarios. Here are some common use cases with implementation examples:
Database Deployment and Configuration:
---
- name: Deploy PostgreSQL Database
hosts: databases
become: yes
vars:
postgres_version: "13"
db_name: "production_app"
db_user: "app_user"
tasks:
- name: Install PostgreSQL
package:
name:
- "postgresql-{{ postgres_version }}"
- "postgresql-contrib-{{ postgres_version }}"
- python3-psycopg2
state: present
- name: Configure PostgreSQL
postgresql_db:
name: "{{ db_name }}"
state: present
become_user: postgres
- name: Create database user
postgresql_user:
name: "{{ db_user }}"
password: "{{ vault_db_password }}"
db: "{{ db_name }}"
priv: ALL
become_user: postgres
Application Deployment Pipeline:
---
- name: Deploy Node.js Application
hosts: webservers
become: yes
vars:
app_name: "my-nodejs-app"
app_port: 3000
node_version: "16.x"
tasks:
- name: Install Node.js repository
shell: curl -fsSL https://deb.nodesource.com/setup_{{ node_version }} | sudo -E bash -
- name: Install Node.js and PM2
package:
name:
- nodejs
- npm
state: present
- name: Install PM2 globally
npm:
name: pm2
global: yes
- name: Clone application repository
git:
repo: "https://github.com/company/{{ app_name }}.git"
dest: "/opt/{{ app_name }}"
version: master
notify: restart application
- name: Install dependencies
npm:
path: "/opt/{{ app_name }}"
state: present
- name: Start application with PM2
shell: pm2 start /opt/{{ app_name }}/app.js --name {{ app_name }}
ignore_errors: yes
Comparison with Alternative Tools
Understanding how Ansible compares to other configuration management tools helps you make informed decisions:
Feature | Ansible | Puppet | Chef | SaltStack |
---|---|---|---|---|
Architecture | Agentless (SSH) | Agent-based | Agent-based | Agent-based |
Configuration Language | YAML | Puppet DSL | Ruby DSL | YAML/Jinja2 |
Learning Curve | Low | Medium | High | Medium |
Execution Model | Push | Pull | Pull | Push/Pull |
Performance (1000+ nodes) | Good | Excellent | Excellent | Excellent |
Community Support | Large | Large | Medium | Medium |
Ansible’s agentless architecture provides significant advantages for getting started quickly, but may face performance challenges at massive scale. For organizations managing fewer than 500 nodes, Ansible typically offers the best balance of simplicity and functionality.
Advanced Features and Best Practices
As you become more comfortable with basic playbooks, implementing advanced features will make your automation more robust and maintainable.
Using Ansible Vault for Sensitive Data:
# Create encrypted variable file
ansible-vault create group_vars/production/vault.yml
# Edit encrypted file
ansible-vault edit group_vars/production/vault.yml
# Run playbook with vault password
ansible-playbook playbook.yml --ask-vault-pass
Implementing Conditional Logic:
tasks:
- name: Install Apache on RedHat systems
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Install Apache on Debian systems
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Configure service based on environment
template:
src: "{{ item }}"
dest: /etc/app/config.yml
with_first_found:
- "config_{{ environment }}.yml.j2"
- "config_default.yml.j2"
Error Handling and Recovery:
tasks:
- name: Attempt database connection
postgresql_ping:
db: "{{ database_name }}"
register: db_connection
ignore_errors: yes
- name: Fallback to backup database
set_fact:
database_host: "{{ backup_db_host }}"
when: db_connection.failed
- name: Ensure critical service is running
systemd:
name: "{{ service_name }}"
state: started
retries: 3
delay: 10
until: service_result.state == "started"
register: service_result
Common Issues and Troubleshooting
Even experienced administrators encounter challenges when working with Ansible. Here are the most frequent issues and their solutions:
SSH Connection Problems:
- Host key verification failures – disable with
host_key_checking = False
in ansible.cfg - Permission denied errors – verify SSH key permissions are 600
- Connection timeouts – increase timeout values in ansible.cfg
# Debug SSH connectivity
ansible all -m setup -vvv
ssh -vvv user@target-host
# Test with different connection methods
ansible all -m ping -c ssh
ansible all -m ping -c paramiko
Privilege Escalation Issues:
# Common sudo configurations
- name: Task requiring root privileges
package:
name: nginx
state: present
become: yes
become_method: sudo
become_user: root
# For systems with custom sudo configs
ansible_become_password: "{{ vault_sudo_password }}"
ansible_become_method: su
Performance Optimization:
Large-scale deployments often suffer from performance issues. These configurations significantly improve execution speed:
# ansible.cfg optimizations
[defaults]
forks = 20
host_key_checking = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
Benchmark results show these optimizations can reduce playbook execution time by 40-60% on typical infrastructure deployments.
Module-Specific Troubleshooting:
# Debug failed tasks
- name: Install package with error handling
package:
name: "{{ package_name }}"
state: present
register: install_result
failed_when: false
- name: Display installation details
debug:
var: install_result
when: install_result.failed
# Validate file operations
- name: Copy configuration file
copy:
src: config.conf
dest: /etc/app/config.conf
validate: '/usr/bin/app --test-config %s'
Integration with CI/CD Pipelines
Modern development workflows benefit greatly from Ansible integration. Here’s how to incorporate playbooks into popular CI/CD platforms:
GitLab CI Integration:
# .gitlab-ci.yml
stages:
- test
- deploy
ansible-lint:
stage: test
script:
- ansible-lint playbooks/
deploy-staging:
stage: deploy
script:
- ansible-playbook -i inventories/staging playbooks/deploy.yml
only:
- develop
deploy-production:
stage: deploy
script:
- ansible-playbook -i inventories/production playbooks/deploy.yml
only:
- master
when: manual
Jenkins Pipeline Example:
pipeline {
agent any
stages {
stage('Syntax Check') {
steps {
sh 'ansible-playbook --syntax-check playbooks/deploy.yml'
}
}
stage('Deploy to Staging') {
steps {
ansiblePlaybook(
playbook: 'playbooks/deploy.yml',
inventory: 'inventories/staging',
extras: '--check'
)
}
}
}
}
Security Considerations and Hardening
Security should be a primary concern when implementing Ansible automation. Follow these practices to maintain a secure infrastructure:
- Use Ansible Vault for all sensitive data including passwords, API keys, and certificates
- Implement least-privilege access with dedicated service accounts
- Regularly rotate SSH keys and vault passwords
- Enable logging and monitoring for all playbook executions
- Use dynamic inventories to avoid storing sensitive host information in version control
# Secure playbook structure
group_vars/
all/
vars.yml # Non-sensitive variables
vault.yml # Encrypted sensitive data
production/
vars.yml
vault.yml
# Example vault content
vault_database_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66633262313764623439643565363836616463643263306464326234653433626436363534366663
...
For additional security resources and best practices, consult the official Ansible Vault documentation and the Ansible best practices guide.
This comprehensive introduction to Ansible playbooks provides the foundation for automating your infrastructure management tasks. Start with simple playbooks and gradually incorporate advanced features as your requirements grow. The combination of Ansible’s simplicity and power makes it an invaluable tool for modern system administration and DevOps practices.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.