When provisioning complex server environments, we often need to execute multiple Ansible playbooks in a specific sequence. While a shell script with sequential ansible-playbook
commands works, it lacks the native Ansible benefits of error handling, variable sharing, and dependency management.
Ansible provides several built-in approaches for playbook orchestration:
# Method 1: Importing playbooks
- name: First playbook tasks
import_playbook: playbook1.yml
- name: Second playbook with different user
import_playbook: playbook2.yml
vars:
ansible_user: deploy_user
ansible_become: yes
For more complex scenarios with mixed privilege requirements:
# master_playbook.yml
- hosts: all
tasks:
- name: Include first phase as root
include_tasks: setup_phase1.yml
vars:
ansible_become: yes
- hosts: all
tasks:
- name: Include second phase as app_user
include_tasks: setup_phase2.yml
vars:
ansible_user: app_user
Add robustness with blocks and error handling:
- block:
- import_playbook: db_setup.yml
- import_playbook: app_deploy.yml
rescue:
- debug:
msg: "Playbook sequence failed, rolling back"
- import_playbook: rollback.yml
For selective execution within the sequence:
# Execute only security-related playbooks
ansible-playbook master_playbook.yml --tags "security"
When dealing with numerous playbooks:
- Use
strategy: free
for independent tasks - Leverage
serial
for rolling updates - Consider asynchronous tasks where appropriate
When provisioning complex server environments, we often need to chain multiple Ansible playbooks that have specific execution orders and privilege requirements. While a shell script approach works, it lacks native Ansible features like error handling, variable sharing, and proper privilege escalation management.
Ansible offers two primary mechanisms for combining playbooks:
# Static inclusion (pre-Ansible 2.4 style)
- include: playbook1.yml
- include: playbook2.yml
# Dynamic imports (modern approach)
- import_playbook: playbook1.yml
- import_playbook: playbook2.yml
Here's a robust implementation that handles ordering and privilege separation:
---
# master_playbook.yml
- name: Phase 1 - Root-level configuration
hosts: all
become: yes
vars:
phase: initial_setup
tasks: []
- import_playbook: hardware_config.yml
- import_playbook: os_baseline.yml
- name: Phase 2 - Application deployment
hosts: app_servers
become: no
become_user: "{{ deployment_user }}"
vars:
phase: app_deployment
tasks: []
- import_playbook: db_setup.yml
- import_playbook: webapp_deploy.yml
For more complex scenarios, consider these patterns:
# Conditional execution
- name: Check if DB needs provisioning
hosts: db_servers
tasks:
- stat:
path: /var/lib/mysql
register: mysql_installed
- import_playbook: mysql_install.yml
when: not mysql_installed.stat.exists
# Error handling with blocks
- block:
- import_playbook: critical_setup.yml
rescue:
- debug:
msg: "Critical setup failed, aborting!"
- meta: end_play
- Use
serial
directive for rolling updates - Leverage
strategy: free
when order doesn't matter - Consider asynchronous execution for long-running tasks
- Cache facts between playbooks with
fact_caching