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