ansible-playbook-design
This skill should be used when creating new Ansible playbooks, designing playbook structure, implementing state-based playbooks with present/absent patterns, organizing plays and tasks, or structuring playbook variables. Covers play organization, variable scoping, and state-based design patterns.
$ Installer
git clone https://github.com/basher83/lunar-claude /tmp/lunar-claude && cp -r /tmp/lunar-claude/plugins/infrastructure/ansible-workflows/skills/ansible-playbook-design ~/.claude/skills/lunar-claude// tip: Run this command in your terminal to install the skill
name: ansible-playbook-design description: > This skill should be used when creating new Ansible playbooks, designing playbook structure, implementing state-based playbooks with present/absent patterns, organizing plays and tasks, or structuring playbook variables. Covers play organization, variable scoping, and state-based design patterns.
Ansible Playbook Design
Patterns for designing well-structured, maintainable Ansible playbooks.
State-Based Playbook Pattern
Design playbooks to handle both creation and removal via a state variable.
Core Pattern
---
- name: Manage admin user account
hosts: all
become: true
vars:
admin_state: present # or absent
tasks:
- name: Create admin user
ansible.builtin.user:
name: "{{ admin_name }}"
groups: "{{ admin_groups }}"
state: "{{ admin_state }}"
- name: Configure SSH key
ansible.posix.authorized_key:
user: "{{ admin_name }}"
key: "{{ admin_ssh_key }}"
state: "{{ admin_state }}"
when: admin_state == 'present'
Usage
# Create user (default)
uv run ansible-playbook playbooks/manage-admin.yml \
-e "admin_name=alice" \
-e "admin_ssh_key='ssh-ed25519 AAAA...'"
# Remove user
uv run ansible-playbook playbooks/manage-admin.yml \
-e "admin_name=alice" \
-e "admin_state=absent"
Benefits
- Single source of truth
- Consistent interface
- Less code duplication
- Follows community role conventions
Play Structure
Recommended Play Sections
Order sections consistently across all playbooks:
---
- name: Descriptive play name
hosts: target_group
become: true
gather_facts: true
vars:
# Play-level variables
app_version: "2.0.0"
vars_files:
# External variable files
- vars/secrets.yml
pre_tasks:
# Tasks that must run before roles
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
roles:
# Role includes
- role: common
- role: app_deploy
vars:
deploy_version: "{{ app_version }}"
tasks:
# Play-specific tasks
- name: Verify deployment
ansible.builtin.uri:
url: http://localhost:8080/health
post_tasks:
# Cleanup or finalization
- name: Send deployment notification
ansible.builtin.debug:
msg: "Deployment complete"
handlers:
# Event-triggered tasks
- name: restart app
ansible.builtin.systemd:
name: myapp
state: restarted
Variable Organization
Variable Precedence (Key Levels)
From lowest to highest precedence:
- Role defaults (
roles/x/defaults/main.yml) - Inventory group_vars (
group_vars/all.yml) - Inventory host_vars (
host_vars/hostname.yml) - Play vars (
vars:in playbook) - Task vars (
vars:on task) - Extra vars (
-eon command line) - highest
Organizing Variables
ansible/
âââ group_vars/
â âââ all.yml # Variables for ALL hosts
â âââ proxmox.yml # Proxmox cluster hosts
â âââ docker_hosts.yml # Docker host group
âââ host_vars/
â âââ node01.yml # Host-specific overrides
â âââ node02.yml
âââ playbooks/
âââ deploy.yml # Uses vars: for playbook-specific
Variable Naming by Scope
# group_vars/all.yml - Global defaults
default_timezone: "UTC"
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
# group_vars/proxmox.yml - Group-specific
proxmox_api_host: "192.168.1.10"
proxmox_cluster_name: "production"
# host_vars/node01.yml - Host-specific overrides
proxmox_node_id: 1
ceph_osd_devices:
- /dev/sdb
- /dev/sdc
Task Organization with Includes
When to Split Tasks
Split playbook tasks into separate files when:
- Tasks exceed 50 lines
- Logical groupings emerge (networking, storage, users)
- Conditional sections can be skipped entirely
Include Patterns
# playbooks/setup-cluster.yml
---
- name: Setup Proxmox cluster
hosts: proxmox
become: true
tasks:
- name: Configure networking
ansible.builtin.include_tasks: tasks/networking.yml
- name: Setup storage
ansible.builtin.include_tasks: tasks/storage.yml
when: setup_storage | default(true)
- name: Initialize cluster
ansible.builtin.include_tasks: tasks/cluster-init.yml
when: inventory_hostname == groups['proxmox'][0]
import_tasks vs include_tasks
| Feature | import_tasks | include_tasks |
|---|---|---|
| When evaluated | Parse time (static) | Runtime (dynamic) |
| Supports loops | No | Yes |
| Supports conditionals on import | Limited | Full |
| Use case | Ordered execution | Conditional/looped |
# Static import - always loaded, order matters
- ansible.builtin.import_tasks: users.yml
- ansible.builtin.import_tasks: permissions.yml
# Dynamic include - conditional, looped
- ansible.builtin.include_tasks: "setup-{{ ansible_os_family }}.yml"
- ansible.builtin.include_tasks: deploy-app.yml
loop: "{{ applications }}"
Multi-Play Playbooks
Use multiple plays for different host groups or privilege levels:
---
# Play 1: Gather facts from all nodes
- name: Gather cluster information
hosts: proxmox
gather_facts: true
tasks:
- name: Set cluster facts
ansible.builtin.set_fact:
cluster_node_count: "{{ groups['proxmox'] | length }}"
# Play 2: Initialize primary node
- name: Initialize cluster on primary
hosts: proxmox[0]
become: true
tasks:
- name: Create cluster
ansible.builtin.command: pvecm create {{ cluster_name }}
when: not cluster_exists
# Play 3: Join secondary nodes
- name: Join cluster on secondary nodes
hosts: proxmox[1:]
become: true
serial: 1 # One node at a time
tasks:
- name: Join cluster
ansible.builtin.command: pvecm add {{ primary_node }}
when: not node_in_cluster
Handler Best Practices
Define Handlers at Play Level
---
- name: Configure web server
hosts: webservers
become: true
tasks:
- name: Update nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: reload nginx
- name: Update SSL certificates
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/nginx/ssl/
loop:
- cert.pem
- key.pem
notify: reload nginx
handlers:
- name: reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
Handler Execution Order
Handlers run:
- At the end of each play
- In the order they are defined (not notified)
- Only once, even if notified multiple times
Force immediate handler execution:
- name: Update critical config
ansible.builtin.template:
src: config.j2
dest: /etc/app/config.yml
notify: restart app
- name: Flush handlers now
ansible.builtin.meta: flush_handlers
- name: Verify app is running
ansible.builtin.uri:
url: http://localhost:8080/health
Playbook Validation
Pre-flight Checks
Add validation at the start of playbooks:
---
- name: Deploy application
hosts: app_servers
become: true
tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- app_version is defined
- app_version | regex_search('^\d+\.\d+\.\d+$')
- deploy_env in ['staging', 'production']
fail_msg: "Invalid configuration. Check app_version and deploy_env."
- name: Check disk space
ansible.builtin.assert:
that: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
fail_msg: "Insufficient disk space. Need at least 1GB free."
Template Patterns
Playbook Template Structure
---
# playbooks/template-playbook.yml
# Description: [What this playbook does]
# Usage: uv run ansible-playbook playbooks/template-playbook.yml -e "var=value"
# Requirements: [Any prerequisites]
- name: [Descriptive play name]
hosts: [target_group]
become: [true/false]
gather_facts: [true/false]
vars:
# Configurable variables with defaults
resource_state: present
tasks:
- name: Validate inputs
ansible.builtin.assert:
that:
- required_var is defined
fail_msg: "required_var must be defined"
# Main tasks...
- name: Verify completion
ansible.builtin.debug:
msg: "Playbook completed successfully"
Additional Resources
For detailed playbook patterns and techniques, consult:
references/playbook-role-patterns.md- Comprehensive playbook organization patterns, play structure, import strategies
Related Skills
- ansible-role-design - When to use roles vs playbooks
- ansible-fundamentals - Core module selection and naming
- ansible-error-handling - Block/rescue patterns in playbooks
Repository
