Skip to main content

Playbooks

Playbook Structure

Basic Playbook

---
- name: Example playbook
hosts: all
become: yes
gather_facts: yes
vars:
package_name: nginx
service_name: nginx

tasks:
- name: Install package
package:
name: '{{ package_name }}'
state: present

- name: Start and enable service
service:
name: '{{ service_name }}'
state: started
enabled: yes

- name: Configure firewall
ufw:
rule: allow
port: 80
proto: tcp

Multiple Plays

---
- name: Configure web servers
hosts: webservers
become: yes
tasks:
- name: Install nginx
package:
name: nginx
state: present

- name: Start nginx
service:
name: nginx
state: started

- name: Configure database servers
hosts: databases
become: yes
tasks:
- name: Install mysql
package:
name: mysql-server
state: present

- name: Start mysql
service:
name: mysql
state: started

Play Keywords

Common Play Keywords

---
- name: Example play with all keywords
hosts: webservers
remote_user: ansible
become: yes
become_user: root
become_method: sudo
gather_facts: yes
connection: ssh
timeout: 300
serial: 2
max_fail_percentage: 10
any_errors_fatal: false
ignore_errors: false
ignore_unreachable: false

vars:
variable1: value1
variable2: value2

vars_files:
- vars/main.yml
- vars/secrets.yml

vars_prompt:
- name: username
prompt: 'Enter username'
private: no

- name: password
prompt: 'Enter password'
private: yes

pre_tasks:
- name: Update package cache
apt:
update_cache: yes
when: ansible_os_family == "Debian"

roles:
- common
- webserver

tasks:
- name: Main tasks here
debug:
msg: 'This is a main task'

post_tasks:
- name: Final cleanup
debug:
msg: 'Cleanup completed'

handlers:
- name: restart nginx
service:
name: nginx
state: restarted

Execution Control

---
- name: Controlled execution
hosts: webservers
serial: 1 # Execute on one host at a time
max_fail_percentage: 25 # Stop if more than 25% fail
any_errors_fatal: true # Stop all hosts if one fails

tasks:
- name: Critical task
command: /bin/critical-command
ignore_errors: no
ignore_unreachable: no

Task Structure

Basic Task

- name: Install package
package:
name: nginx
state: present
become: yes
notify: restart nginx
when: ansible_os_family == "Debian"
tags: installation
register: install_result
failed_when: install_result.rc != 0
changed_when: false

Task Keywords

- name: Complex task example
shell: |
echo "Starting complex operation"
/path/to/script.sh
args:
chdir: /tmp
creates: /tmp/output.txt
become: yes
become_user: app
delegate_to: localhost
delegate_facts: yes
run_once: true
local_action: command echo "local command"
environment:
PATH: /usr/local/bin:{{ ansible_env.PATH }}
CUSTOM_VAR: custom_value
async: 3600
poll: 0
retries: 3
delay: 10
timeout: 300
until: result.stdout.find("success") != -1
ignore_errors: yes
ignore_unreachable: yes
no_log: true
diff: false
check_mode: false
always: true
rescue: true
block: true

Variables in Playbooks

Variable Definition

---
- name: Variable examples
hosts: all
vars:
# Simple variables
package_name: nginx
service_port: 80

# List variables
packages:
- nginx
- mysql-server
- php

# Dictionary variables
user_accounts:
admin:
name: admin
uid: 1000
groups: [sudo, admin]
app:
name: app
uid: 1001
groups: [app]

# Computed variables
service_name: '{{ package_name }}'
config_file: '/etc/{{ package_name }}/{{ package_name }}.conf'

tasks:
- name: Use simple variable
debug:
msg: 'Installing {{ package_name }}'

- name: Use list variable
package:
name: '{{ packages }}'
state: present

- name: Use dictionary variable
user:
name: '{{ item.value.name }}'
uid: '{{ item.value.uid }}'
groups: '{{ item.value.groups }}'
loop: '{{ user_accounts | dict2items }}'

Variable Files

---
- name: Using variable files
hosts: all
vars_files:
- vars/common.yml
- vars/{{ ansible_os_family }}.yml
- vars/{{ environment }}.yml

tasks:
- name: Use variables from files
debug:
msg: 'Database host: {{ db_host }}'

Variable Prompt

---
- name: Interactive variables
hosts: all
vars_prompt:
- name: username
prompt: 'Enter username'
private: no
default: admin

- name: password
prompt: 'Enter password'
private: yes
encrypt: sha512_crypt
confirm: yes
salt_size: 7

- name: environment
prompt: 'Select environment'
private: no
default: staging
when: environment is not defined

tasks:
- name: Create user
user:
name: '{{ username }}'
password: '{{ password }}'

Includes and Imports

Include Tasks

---
- name: Main playbook
hosts: all

tasks:
- name: Include common tasks
include_tasks: tasks/common.yml

- name: Include OS-specific tasks
include_tasks: 'tasks/{{ ansible_os_family }}.yml'

- name: Include conditional tasks
include_tasks: tasks/ssl.yml
when: ssl_enabled | default(false)

- name: Include tasks with variables
include_tasks: tasks/database.yml
vars:
db_name: myapp
db_user: appuser

Import Tasks

---
- name: Main playbook
hosts: all

tasks:
- name: Import common tasks
import_tasks: tasks/common.yml

- name: Import OS-specific tasks
import_tasks: 'tasks/{{ ansible_os_family }}.yml'
when: ansible_os_family == "Debian"

Include Playbooks

---
- name: Master playbook
import_playbook: playbooks/common.yml

- name: Web servers
import_playbook: playbooks/webservers.yml

- name: Database servers
import_playbook: playbooks/databases.yml

Handlers

Basic Handlers

---
- name: Web server setup
hosts: webservers

tasks:
- name: Install nginx
package:
name: nginx
state: present
notify: restart nginx

- name: Update nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- restart nginx
- reload nginx

handlers:
- name: restart nginx
service:
name: nginx
state: restarted

- name: reload nginx
service:
name: nginx
state: reloaded

Handler Execution Control

---
- name: Handler control
hosts: all

tasks:
- name: Update config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: restart app

- name: Force handler execution
meta: flush_handlers

- name: Task that depends on handler
debug:
msg: 'App has been restarted'

handlers:
- name: restart app
service:
name: app
state: restarted
listen: 'restart services'

Error Handling

Error Control

---
- name: Error handling
hosts: all

tasks:
- name: Task that might fail
command: /bin/false
ignore_errors: yes
register: result

- name: Custom failure condition
shell: echo "custom command"
register: custom_result
failed_when: custom_result.rc > 2

- name: Custom changed condition
shell: echo "unchanged"
register: unchanged_result
changed_when: false

- name: Retry on failure
uri:
url: http://example.com/api
method: GET
register: api_result
retries: 3
delay: 5
until: api_result.status == 200

Block Error Handling

---
- name: Block error handling
hosts: all

tasks:
- name: Block with error handling
block:
- name: Risky task
command: /bin/might-fail

- name: Another risky task
command: /bin/also-might-fail

rescue:
- name: Handle errors
debug:
msg: "Something went wrong, but we're handling it"

always:
- name: Always execute
debug:
msg: 'This always runs'

Playbook Organization

Directory Structure

playbooks/
├── site.yml
├── webservers.yml
├── databases.yml
├── common.yml
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── databases.yml
├── host_vars/
│ ├── web1.example.com.yml
│ └── db1.example.com.yml
├── roles/
│ ├── common/
│ ├── webserver/
│ └── database/
├── tasks/
│ ├── common.yml
│ ├── ssl.yml
│ └── firewall.yml
├── templates/
│ ├── nginx.conf.j2
│ └── mysql.cnf.j2
├── files/
│ ├── ssl-cert.pem
│ └── authorized_keys
└── vars/
├── main.yml
└── secrets.yml

Main Site Playbook

# site.yml
---
- import_playbook: common.yml
- import_playbook: webservers.yml
- import_playbook: databases.yml
- import_playbook: monitoring.yml

Role-based Playbook

# webservers.yml
---
- name: Configure web servers
hosts: webservers
become: yes

roles:
- common
- firewall
- webserver
- ssl
- monitoring

Advanced Playbook Features

Delegation

---
- name: Delegation examples
hosts: webservers

tasks:
- name: Run on localhost
command: echo "Running on control machine"
delegate_to: localhost

- name: Run on different host
command: echo "Running on database server"
delegate_to: db1.example.com

- name: Run once on first host
command: echo "Run once"
run_once: true
delegate_to: localhost

Serial Execution

---
- name: Rolling updates
hosts: webservers
serial: 2 # Update 2 hosts at a time

tasks:
- name: Stop service
service:
name: nginx
state: stopped

- name: Update application
copy:
src: app.jar
dest: /opt/app/app.jar

- name: Start service
service:
name: nginx
state: started

Strategy

---
- name: Strategy examples
hosts: all
strategy: free # Don't wait for all hosts to complete each task

tasks:
- name: Task 1
debug:
msg: 'Task 1'

- name: Task 2
debug:
msg: 'Task 2'
# Other strategies: linear (default), free, debug

Testing Playbooks

Syntax Check

# Check syntax
ansible-playbook --syntax-check playbook.yml

# Dry run
ansible-playbook --check playbook.yml

# Show differences
ansible-playbook --check --diff playbook.yml

# Step through
ansible-playbook --step playbook.yml

Playbook Testing

---
- name: Test playbook
hosts: all

tasks:
- name: Test connectivity
ping:

- name: Test service status
service:
name: nginx
state: started
check_mode: yes
register: service_status

- name: Assert service is running
assert:
that:
- service_status.state == "started"
fail_msg: 'Service is not running'
success_msg: 'Service is running correctly'

Best Practices

Naming and Documentation

---
- name: Clear and descriptive play name
hosts: webservers

tasks:
- name: Install web server package
package:
name: nginx
state: present
tags: [installation, packages]

- name: Configure web server
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: restart nginx
tags: [configuration]

Idempotency

- name: Ensure idempotent operations
hosts: all

tasks:
- name: Create directory (idempotent)
file:
path: /opt/app
state: directory
mode: '0755'

- name: Install package (idempotent)
package:
name: nginx
state: present

- name: Configure service (idempotent)
service:
name: nginx
state: started
enabled: yes

Security

- name: Security best practices
hosts: all

tasks:
- name: Use vault for sensitive data
user:
name: admin
password: "{{ admin_password | password_hash('sha512') }}"
no_log: true

- name: Secure file permissions
file:
path: /etc/ssl/private/server.key
mode: '0600'
owner: root
group: root

Performance

- name: Performance optimization
hosts: all
gather_facts: false # Skip if facts not needed

tasks:
- name: Batch operations
package:
name: '{{ packages }}'
state: present
vars:
packages:
- nginx
- mysql-server
- php

- name: Use appropriate modules
file:
path: /tmp/test
state: touch
# Better than: shell: touch /tmp/test