ansible · git:20260517.05cd147 · 2026-05-17 · sha256 0bc9c6ecd128bc2a
ansible git:20260517.05cd147A
Immutable. This exact content never changes and is served at /api/v1/blob/0bc9c6ecd128bc2a.
---
name: ansible
description: >-
Ansible automation conventions, patterns, and toolchain: playbook design, roles,
inventory, vault, collections, execution environments, Event-Driven Ansible,
testing, and performance tuning. Invoke whenever task involves any interaction
with Ansible — writing playbooks, creating roles, managing inventory, reviewing
automation code, debugging runs, upgrading ansible-core, or working with AAP.
---
# Ansible
Idempotency is the highest Ansible virtue. Every task must describe desired state, not a sequence of commands.
## References
Extended examples, patterns, and rationale for the rules below live in `${CLAUDE_SKILL_DIR}/references/`.
- **playbook-patterns** — [`${CLAUDE_SKILL_DIR}/references/playbook-patterns.md`]: Play execution order, static vs
dynamic reuse comparison table, batched execution with `serial`, verification flags, standard directory layouts
- **role-structure** — [`${CLAUDE_SKILL_DIR}/references/role-structure.md`]: Extended directory tree, three ways to use
roles (play-level, import_role, include_role) with examples, platform-specific task splitting, argument_specs example,
dependency mechanics, deduplication rules
- **inventory-management** — [`${CLAUDE_SKILL_DIR}/references/inventory-management.md`]: YAML/INI examples, group
hierarchy, environment separation, AWS/Azure/GCP/NetBox/Terraform plugins, multi-cloud chaining, caching
- **vault-and-security** — [`${CLAUDE_SKILL_DIR}/references/vault-and-security.md`]: File vs variable encryption,
password sources, ansible-sign, GPG verification, hardening roles, compliance scanning, security smells
- **variables-and-templating** — [`${CLAUDE_SKILL_DIR}/references/variables-and-templating.md`]: Full 22-level
precedence list, magic variables, YAML quoting gotcha, registered variables
- **error-handling** — [`${CLAUDE_SKILL_DIR}/references/error-handling.md`]: Block execution flow, rescue variables,
failed_when/changed_when, any_errors_fatal
- **handlers-and-delegation** — [`${CLAUDE_SKILL_DIR}/references/handlers-and-delegation.md`]: Handler execution order,
flushing, delegate_to, delegate_facts, fire-and-forget async
- **testing-and-performance** — [`${CLAUDE_SKILL_DIR}/references/testing-and-performance.md`]: Molecule lifecycle,
driver comparison, CI matrix testing, strategy plugins, SSH pipelining, fact caching, serial batching
- **execution-environments-and-collections** —
[`${CLAUDE_SKILL_DIR}/references/execution-environments-and-collections.md`]: EE vs local installs, version 3 schema,
FQCN migration, collection certification, Galaxy publishing, automation mesh, AAP 2.5/2.6 platform architecture
- **event-driven-ansible** — [`${CLAUDE_SKILL_DIR}/references/event-driven-ansible.md`]: Rulebook structure, event
sources, conditions, actions, Event Streams, decision environments, Kafka vs webhooks, performance tuning,
troubleshooting
- **porting-guide** — [`${CLAUDE_SKILL_DIR}/references/porting-guide.md`]: ansible-core 2.17/2.18 breaking changes,
Python version requirements, removed modules, AAP 2.5/2.6 deprecations, upgrade strategy
## Playbook Design
### Naming and Clarity
- **Always name plays, tasks, and blocks.** Unnamed tasks produce opaque output that makes debugging impossible.
- **Always specify `state:` explicitly.** Different modules have different defaults. `state: present` / `state: absent`
makes intent visible.
- **Always use FQCN** (Fully Qualified Collection Names): `ansible.builtin.copy`, not `copy`. Prevents ambiguity when
multiple collections are installed.
### Idempotency
- Prefer declarative modules (`ansible.builtin.template`, `ansible.builtin.service`, `ansible.builtin.user`) over
imperative ones (`ansible.builtin.command`, `ansible.builtin.shell`)
- When `command`/`shell` is unavoidable, add `creates:`, `removes:`, or `changed_when:` to make it idempotent
- Move complex logic into custom modules or filter plugins — Ansible is a desired state engine, not a scripting language
- Test idempotency: run twice, second run must report zero changes
### Static vs Dynamic Reuse
- `import_tasks` / `import_role` -- static, parsed at load time. Tags propagate to all imported tasks. Cannot loop. Use
when structure is fixed.
- `include_tasks` / `include_role` -- dynamic, evaluated at runtime. Tags apply only to the include statement. Can loop
and use `when`. Use when inclusion is conditional.
Default to `import_*` for predictability.
### Project Layout
```
inventories/
production/
hosts
group_vars/
host_vars/
staging/
hosts
group_vars/
host_vars/
site.yml # imports tier playbooks
webservers.yml
dbservers.yml
roles/
common/
webserver/
database/
```
`site.yml` imports tier playbooks. Each tier playbook maps host groups to roles.
## Roles
A role manages one service or component — not an entire stack. Keep provisioning separate from configuration and
application deployment. Roles are not programming constructs: avoid deep inheritance hierarchies, tight coupling, or
hard dependencies on external variables.
### Structure
```
roles/my_role/
tasks/main.yml # entry point
handlers/main.yml # auto-imported into play scope
templates/*.j2 # Jinja2 templates
files/ # static files
defaults/main.yml # low-precedence (user-configurable)
vars/main.yml # high-precedence (internal constants)
meta/main.yml # dependencies
meta/argument_specs.yml # argument validation (2.11+)
```
### defaults/ vs vars/
- `defaults/` -- easily overridden. Use for knobs users should change (ports, paths, feature flags).
- `vars/` -- hard to override. Use for internal constants the role needs to function.
### Naming
- Role names: lowercase, hyphens: `nginx-proxy`, `ssl-certs`
- Prefix all role variables with the role name: `nginx_port`, `nginx_worker_count`
- Prefix handler names with role name: `nginx : Restart nginx`
### Argument Validation
Define expected parameters in `meta/argument_specs.yml`. Validation runs before role tasks execute.
### Dependencies
Defined in `meta/main.yml`. Run before the role. Deduplicated per play unless parameters differ or
`allow_duplicates: true` is set.
## Inventory
### Format
Prefer YAML over INI. INI `:vars` sections treat all values as strings, causing type confusion.
### Grouping Strategy
Group along three dimensions:
- **What** (function): `webservers`, `dbservers`, `monitoring`
- **Where** (location): `dc1`, `dc2`, `us_east`
- **When** (environment): `production`, `staging`, `development`
### Environment Separation
Split large inventories by function or region — a single static file with 5,000+ hosts takes 15-30 seconds to load. Keep
production and staging in separate inventory files or directories. Never mix environments in a single inventory --
developers using a mixed inventory need access to all vault passwords.
### Dynamic Inventory
Use inventory plugins (not scripts) for cloud providers:
- **AWS:** `amazon.aws.aws_ec2` -- groups from tags, instance types, regions
- **Azure:** `azure.azcollection.azure_rm` -- conditional groups, keyed groups
- **GCP:** `google.cloud.gcp_compute` -- zones, machine types, labels
- **NetBox:** `netbox.netbox.nb_inventory` -- single source of truth for hybrid environments, automatic group updates
from tags/custom fields
- **Terraform:** `cloud.terraform.terraform_state` -- parse state files as inventory
Mix static and dynamic sources in the same inventory directory.
### Constructed Inventory
Build groups dynamically from host metadata using Jinja2 logic. Chain multiple cloud inventories into a single
constructed inventory for cross-cloud targeting. Successor to Smart Inventories in AAP.
## Variables and Precedence
### The 22-Level Precedence Rule
Role `defaults/` is lowest. Extra vars (`-e`) always win. Most common layers:
- **Overridable defaults** → Role `defaults/main.yml`
- **Environment-wide values** → `group_vars/all.yml`
- **Group-specific values** → `group_vars/<group>.yml`
- **Host-specific values** → `host_vars/<host>.yml`
- **Force a value in a role** → Role `vars/main.yml`
- **Override everything at runtime** → `--extra-vars`
Define each variable in ONE place.
### YAML Quoting
Values starting with `{{ }}` must be quoted:
```yaml
app_path: "{{ base_path }}/app" # correct
app_path: {{ base_path }}/app # YAML parse error
```
### Common Gotchas
- **Boolean coercion:** YAML treats `yes`, `no`, `true`, `false`, `on`, `off` as booleans. Quote strings that match:
`version: "yes"`, not `version: yes`
- **Octal numbers:** Leading zeros create octals in YAML 1.1. `mode: 0644` becomes `420` (decimal). Use `mode: "0644"`
for file permissions.
- **Dictionary merge:** `combine()` does shallow merge. Nested dicts are replaced, not merged. Use
`combine(recursive=true)` for deep merge.
- **Variable scope in loops:** `set_fact` in a loop overwrites on each iteration. Use `set_fact` with
`{{ result | default([]) + [item] }}` to accumulate.
## Jinja2 Templating
All templating runs on the control node before task execution.
### Key Patterns
- **Filters:** `{{ value | default('fallback') }}`, `{{ list | unique }}`, `{{ dict1 | combine(dict2) }}`
- **Tests:** `when: result is defined`, `when: path is file`
- **Template files (.j2):** support loops, conditionals, macros -- full Jinja2
### Templates in Tasks vs Files
- Playbooks: only variable substitution and filters. No loops or conditionals in task arguments.
- Template files (.j2): full Jinja2 including `{% for %}`, `{% if %}`, `{% macro %}`.
## Vault and Security
### The vars/vault Pattern
```yaml
# group_vars/production/vars.yml (plaintext, searchable)
db_password: "{{ vault_db_password }}"
# group_vars/production/vault.yml (encrypted)
vault_db_password: "actual_secret"
```
Variable names remain greppable. Values stay encrypted.
### Password Automation
- Never type vault passwords manually for every run
- Use a password file (`--vault-password-file vault_pass.txt`) for local dev
- Use a password script (`.vault_pass.sh`) that fetches from a secrets manager for team environments
- In CI/CD, pass vault passwords via `ANSIBLE_VAULT_PASSWORD_FILE` environment variable pointing to a pipeline secret
### External Secret Managers
For enterprise or compliance-heavy environments, shift from static vault files to runtime secret fetching via lookup
plugins:
- HashiCorp Vault, AWS Secrets Manager, Azure Key Vault
- Eliminates manual vault file rotation
- Secrets never touch disk -- fetched at playbook runtime
### Content Signing
Use `ansible-sign` with GPG to sign project content. Creates checksum manifests (SHA256) of protected files with
detached GPG signatures. AAP automation controller verifies signatures on project sync -- tampered projects fail to
update and no jobs launch. Automate signing in CI via `ANSIBLE_SIGN_GPG_PASSPHRASE` environment variable.
### Security Hardening
- Use community CIS benchmark roles (e.g., `ansible-lockdown`) for automated compliance. Customize via
`defaults/main.yml`, select levels via tags.
- Integrate OpenSCAP for compliance scanning and report generation.
- Watch for IaC security smells: root SSH login, command injection, plaintext secrets, unvalidated paths, outdated
dependencies.
## Error Handling
### block/rescue/always
```yaml
block:
- name: Deploy new version
# ... tasks that might fail
rescue:
- name: Rollback
# ... recovery tasks
always:
- name: Send notification
# ... runs regardless
```
- `rescue` runs only when a `block` task fails
- `always` runs regardless of block/rescue outcome
- Rescue variables: `ansible_failed_task`, `ansible_failed_result`
- Hosts that fail in `block` but succeed in `rescue` are reported as **"rescued"**, not "failed" -- account for this in
reporting
### Result Aggregation Pattern
For multi-host runs, capture per-host status in `block`/`rescue`, then aggregate in `always` using
`ansible_play_hosts_all` with `delegate_to: localhost` and `run_once: true`. This produces a single summary of all
successes and failures across the fleet.
### Error Control
- `failed_when:` -- custom failure conditions
- `changed_when:` -- control when a task reports "changed"
- `ignore_errors: true` -- continue on failure (use sparingly)
- `any_errors_fatal: true` -- stop entire play on any host failure
### Retry
```yaml
- name: Wait for service
ansible.builtin.uri:
url: http://localhost:8080/health
register: result
until: result.status == 200
retries: 30
delay: 10
```
## Handlers
- Run once per play after all tasks complete (or on `meta: flush_handlers`)
- Execute in **definition order**, not notification order
- Multiple notifications to the same handler result in single execution
- Use `listen:` topics to group related handlers
- Never use variables in handler **names** -- use them in handler **parameters**
- Handlers from roles have global scope; prefix with `role_name : handler_name`
## Delegation and Async
### Delegation
Execute a task on a different host: `delegate_to: lb.example.com`. Use for load balancer operations, centralized
notifications, cross-host coordination.
`local_action:` is shorthand for `delegate_to: 127.0.0.1`.
When multiple hosts delegate to the same target, use `throttle: 1` or `run_once: true` to prevent race conditions.
`become` applies to the delegated host, not the original target -- verify escalation permissions.
### Async
- `async: N, poll: M` (M > 0) -- extended timeout, still blocks
- `async: N, poll: 0` -- fire-and-forget, check later with `async_status`
- Do not use `poll: 0` with tasks requiring exclusive locks (package managers)
## Event-Driven Ansible (EDA)
EDA is the "Automation Decisions" component of AAP -- a decision engine that listens to event sources and triggers
automated responses via rulebooks. Rulebooks are the event-driven equivalent of playbooks: YAML files with sources,
conditions, and actions.
Key concepts:
- **Rulebooks** define event sources, conditions (`when`), and actions (`run_job_template`, `run_workflow_template`,
`set_fact`, `debug`)
- **Event Streams** (AAP 2.5+) route a single webhook endpoint to multiple rulebook activations with credential
integration -- use for production
- **Decision environments** are container images for running rulebooks (analogous to execution environments for
playbooks)
- Supported controller sources: `alertmanager`, `aws_sqs_queue`, `azure_service_bus`, `kafka`, `pg_listener`, `webhook`
- Use **Kafka** for high-volume mission-critical streams; **webhooks** for simple integrations; **Event Streams** for
production webhook scenarios
See [`${CLAUDE_SKILL_DIR}/references/event-driven-ansible.md`] for rulebook structure, event filters, scaling, and
troubleshooting.
## Execution Environments and AAP Platform
Container images bundling Ansible Core, Runner, collections, and all dependencies. Replace traditional virtual
environments for consistent automation execution.
- **ansible-builder:** Creates custom EEs from definition files (version 3 schema). Specify base image, Galaxy
collections, Python packages, and system dependencies.
- **ansible-navigator:** Interactive TUI for playbook development. Drill into task outputs, inspect variables, replay
artifacts for collaborative debugging. Tightly integrated with EEs for dev-prod parity.
- **Automation mesh:** Overlay network distributing workloads across execution nodes via peer-to-peer connections using
Receptor. Scale execution capacity independently from the control plane.
Use EEs when: enterprise scale, complex dependencies, team consistency needed. Use local installs for: simple setups,
ad-hoc tasks, beginners.
### AAP 2.5/2.6 Awareness
AAP 2.5 introduced a unified UI, Platform Gateway (single auth entry point), and containerized installer (Podman on
RHEL). AAP 2.6 adds an automation dashboard (ROI tracking), self-service automation portal, and Ansible Lightspeed
intelligent assistant.
RPM-based installer is deprecated as of AAP 2.5 -- containerized and operator-based deployments are the future.
See [`${CLAUDE_SKILL_DIR}/references/porting-guide.md`] for AAP platform changes and upgrade guidance.
## Collections
- Install with `ansible-galaxy collection install community.general`
- Pin versions in `requirements.yml` using open ranges:
```yaml
collections:
- name: community.general
version: ">=7.0.0,<8.0.0"
```
- Always use FQCN in playbooks: `community.general.ufw`, not `ufw`
- Install collection dependencies before playbook execution in CI:
`ansible-galaxy collection install -r requirements.yml`
- Vendor collections for air-gapped environments:
`ansible-galaxy collection download -r requirements.yml -p ./collections/`
- Scope collection installs per project -- avoid global installs that create version conflicts across projects
### Collection Quality
- `ansible-test sanity --docker default` for coding standards; `ansible-lint --profile production` for certification
- `galaxy-importer` in CI to replicate automation hub import checks
- Semantic versioning (minimum 1.0.0), `requires_ansible` in `meta/runtime.yml`
- FQCN migration: use `plugin_routing` in `meta/runtime.yml` for backward-compatible redirects
## Testing
### Pipeline
Integrate `ansible-lint` in CI and pre-commit hooks. For enterprise environments, add policy-as-code tools (Steampunk
Spotter, Checkov) as gates before automation reaches production.
1. **ansible-lint** -- static analysis in CI and pre-commit hooks
2. **--syntax-check** -- parse without executing
3. **--check --diff** -- dry run against staging
4. **Molecule** -- role-level testing with idempotency verification
5. **Matrix testing** -- multiple OS versions and Ansible versions in CI
6. **Staging environment** -- full run before production
### Molecule
Standard role testing framework. Drivers: Docker (fast, local dev), Podman (rootless, enterprise), Vagrant (full VM),
delegated (default in Molecule 6). Run `molecule test` for the full lifecycle. Use multiple scenarios for different
conditions (default, HA cluster, upgrade).
## Performance
- Increase `forks` (default 5) for parallel host execution -- start at 2-4x CPU cores, monitor control node memory
- Enable SSH pipelining: `pipelining = True` with ControlPersist
- **Mitogen strategy plugin:** replaces SSH-based execution with RPC protocol, 1.5x-7x faster. Use `mitogen_linear` or
`mitogen_free`. Most impactful for playbooks with many small tasks.
- Cache facts: `gathering = smart` with `fact_caching = jsonfile` (or Redis)
- Disable `gather_facts` when not needed; use `gather_subset` to limit scope
- Use `synchronize` over `copy` for large file transfers
- Install packages as a list, not in a loop
- Use `serial` for staged batching in rolling deployments
- Profile before optimizing: `callbacks_enabled = timer, profile_tasks`
### Large Inventories
Enable inventory caching for dynamic sources (30+ seconds to under 1 second). Use constructed inventory plugin over
large static groups. Flatten group hierarchies (3-4 groups per host, not 6-7). Split inventories by function/region.
## Porting and Compatibility
ansible-core 2.17+ requires Python 3.7+ on managed hosts. RHEL 8 environments must stay on ansible-core 2.16 (system
Python 3.6 bindings are incompatible). Key removals: `yum` module (redirected to `dnf`), `include` module (use
`include_tasks`/`import_tasks`), `smart` connection option (select explicit plugin).
ansible-core 2.18 removes old-style vars plugins (`get_host_vars`/`get_group_vars`) and deprecates plural
`COLLECTIONS_PATHS`. Windows Server 2012/2012 R2 support is removed.
See [`${CLAUDE_SKILL_DIR}/references/porting-guide.md`] for the full list of breaking changes, deprecations, and upgrade
strategy.
## Application
When **writing** Ansible automation: apply all conventions silently. If an existing codebase contradicts a convention,
follow the codebase and flag the divergence.
When **reviewing** Ansible code: cite the specific violation and show the fix inline. Example:
`copy: -> ansible.builtin.copy:`
## Integration
The **coding** skill governs workflow; this skill governs Ansible-specific conventions. Both are active simultaneously.
## Non-Negotiable Defaults
- Every task must be idempotent -- run twice, zero changes on second run
- Always use FQCN -- no short module names
- Always name plays, tasks, and blocks
- Never store secrets in plaintext -- use Vault
- Never commit vault password files to version control
- Use `no_log: true` on tasks handling secrets
- Use `become: true` at task level, not play level
- Use SSH key authentication, not password authentication
- Sign project content with `ansible-sign` in regulated environments
**Idempotency is the highest Ansible virtue. Describe desired state, never command sequences.**