ansible · diff
git:20260910.c429a71 to git:20261023.a6d1dc5
153 added, 48 removed. Audit A to A.
---
name: ansible
- description: "Write idempotent Ansible playbooks, inventories, and roles; use check mode and ansible-vault for controlled infrastructure automation."
+ description: "Write idempotent Ansible playbooks, FQCN modules, roles, and inventories; gate prod with check/diff, pin collections, and keep ansible-vault secrets out of Git and chat."
category: devops
risk: safe
source: self
source_type: self
date_added: "2026-08-26"
- tags: ["ansible", "playbooks", "roles", "automation", "vault", "claude"]
+ tags: ["ansible", "playbooks", "roles", "collections", "vault", "check-mode", "idempotency", "inventory"]
tools: ["claude", "cursor", "gemini", "codex"]
---
- # Ansible Configuration Management AI Skill Guide
+ # Ansible Configuration Management AI Skill Guide (Claude)
## Overview & Engine Architecture
- Ansible pushes desired configuration to hosts (SSH, WinRM, or APIs) using **playbooks** and **roles**. Tasks should be idempotent: re-running converges without drift. Agents prefer modules over raw `shell`/`command`, name every task, and keep vault passwords out of repositories and chat transcripts.
+ Ansible pushes **desired state** to hosts (SSH, WinRM, or cloud APIs) via **playbooks** and **roles**. Controllers assemble inventory + vars, then run modules that converge without requiring agents on targets. Production quality means **idempotent modules** (not raw shell), **FQCN** (`ansible.builtin.*`), **pinned collections**, and **vaulted secrets**. Treat `--check --diff` as a pre-flight gate; never invent vault passwords or cloud credentials.
```
- inventory + group_vars/host_vars
- |
- v
- playbook.yml -> roles/ tasks handlers templates
- |
- v
- managed nodes (SSH) / cloud APIs
+ ┌─────────────────────────────────────────────────────────────┐
+ │ Ansible control → managed nodes │
+ │ │
+ │ Inventory (static / dynamic) + group_vars / host_vars │
+ │ ├── Environment split: inventories/{dev,staging,prod} │
+ │ └── Vaulted secrets (never plaintext in Git) │
+ │ │
+ │ Playbook → roles (tasks / handlers / templates / defaults) │
+ │ ├── Modules (FQCN) > command/shell │
+ │ ├── Handlers fire only on changed │
+ │ └── serial / max_fail_percentage for rolling risk │
+ │ │
+ │ Targets: Linux SSH | Windows WinRM | cloud modules │
+ │ Optional: AWX / Execution Environments for fleet scale │
+ └─────────────────────────────────────────────────────────────┘
```
- ## When to use this skill
+ ---
- - Bootstrapping packages, users, and services on VMs
- - Writing reusable roles for app deploy glue
- - Encrypting secrets with ansible-vault
- - Dry-running changes with check mode
+ ## When to use / when not to
- ## Operational directives
+ **Use when**
- 1. Prefer modules (`apt`, `yum`, `copy`, `template`, `service`, `user`) over shell.
- 2. Use `handlers` for restarts; notify only on change.
- 3. Run `--check` (and `--diff` for templates) before production.
- 4. Store secrets in vaulted files; never commit plaintext vault passwords.
- 5. Keep inventories environment-split (`inventories/prod`, `inventories/dev`).
+ - Bootstrapping packages, users, services, and configs on VMs/bare metal.
+ - Writing reusable roles and collections for app deploy glue after `@terraform` provisions hosts.
+ - Encrypting secrets with ansible-vault; dry-running with `--check --diff`.
- ## Playbook sketch
+ **Do not use when**
+ - You need multi-node scheduling, rolling Deployments, or cluster RBAC as the primary model → `@kubernetes` / `@helm`.
+ - Image build / local multi-container graphs are the unit of delivery → `@docker` (Ansible may only provision the host).
+ - Someone asks to commit vault passwords, cloud keys, or plaintext `group_vars` secrets → refuse; use vault + CI secret mounts.
+
+ ---
+
+ ## Operational Capabilities & Agent Directives
+
+ 1. **Prefer modules over shell**: `ansible.builtin.package`, `copy`, `template`, `service`, `user`, `lineinfile`. Use `command`/`shell` only when no module exists—and add `creates`/`removes` or honest `changed_when`.
+ 2. **Always use FQCN** (`ansible.builtin.template`) so collection resolution is explicit and lintable.
+ 3. **Handlers for restarts**: `notify` only on real `changed`; never restart on every run via always-`changed` shell.
+ 4. **Prod gate**: `ansible-playbook -i inventories/prod site.yml --check --diff` (limit one host when diffs are large) before live apply.
+ 5. **Pin collections** in `requirements.yml` with versions; install via `ansible-galaxy collection install -r requirements.yml`.
+ 6. **Split inventories** by environment; never point ad-hoc `--limit` at prod without explicit user confirmation.
+ 7. **Vault secrets only**: encrypt `group_vars/*/vault.yml`; never put vault passwords in the repo, chat, or playbook defaults.
+ 8. **Rolling risk**: set `serial` and `max_fail_percentage` on fleet-wide service flips.
+
+ ---
+
+ ## Production Example: role + inventory + check gate
+
+ `inventories/prod/hosts.yml`:
+
```yaml
+ all:
+ children:
+ web:
+ hosts:
+ web-01.example.com:
+ web-02.example.com:
+ vars:
+ ansible_user: deploy
+ ansible_become: true
+ ```
+
+ `requirements.yml`:
+
+ ```yaml
+ ---
+ collections:
+ - name: community.general
+ version: ">=8.0.0,<10.0.0"
+ - name: ansible.posix
+ version: ">=1.5.0,<2.0.0"
+ ```
+
+ `site.yml`:
+
+ ```yaml
+ ---
- name: Configure web hosts
hosts: web
become: true
+ serial: 1
+ max_fail_percentage: 25
roles:
- role: common
- role: nginx
vars:
nginx_worker_processes: auto
```
- Role task example:
+ `roles/nginx/tasks/main.yml`:
```yaml
+ ---
- name: Ensure nginx is installed
ansible.builtin.package:
name: nginx
state: present
- name: Deploy nginx site config
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/sites-available/app.conf
mode: "0644"
notify: Reload nginx
- name: Enable site
ansible.builtin.file:
src: /etc/nginx/sites-available/app.conf
dest: /etc/nginx/sites-enabled/app.conf
state: link
notify: Reload nginx
+
+ # Last resort when no module exists — make reporting honest
+ - name: Probe nginx config (read-only)
+ ansible.builtin.command: nginx -t
+ register: nginx_test
+ changed_when: false
+ failed_when: nginx_test.rc != 0
```
- ## Commands
+ `roles/nginx/handlers/main.yml`:
+ ```yaml
+ ---
+ - name: Reload nginx
+ ansible.builtin.service:
+ name: nginx
+ state: reloaded
+ ```
+
+ Validation loop:
+
```bash
- ansible-playbook -i inventories/prod site.yml --check --diff
- ansible-galaxy init roles/nginx
- ansible-vault encrypt group_vars/prod/vault.yml
- ansible-vault edit group_vars/prod/vault.yml
+ ansible-galaxy collection install -r requirements.yml
+ ansible-inventory -i inventories/prod --list
+ ansible-playbook -i inventories/prod site.yml --syntax-check
+ ansible-playbook -i inventories/prod site.yml --check --diff --limit web-01.example.com
+ ansible-vault encrypt group_vars/prod/vault.yml # once; password via --vault-password-file (gitignored)
ansible-playbook -i inventories/prod site.yml --ask-vault-pass
```
- ## Failure patterns
+ ---
- | Symptom | Likely cause | Fix |
- | --- | --- | --- |
- | Unreachable | SSH key/bastion | `-vvv`, ProxyJump |
- | Changed every run | Non-idempotent shell | Switch to module |
- | Template wrong | Variable undefined | `ansible-inventory --list`, defaults |
- | Vault decrypt fail | Wrong password/file | Confirm vault id |
+ ## Technical Troubleshooting Matrix
- ## Best practices
+ | Signature | Likely cause | Fix path |
+ | :--- | :--- | :--- |
+ | `UNREACHABLE!` / SSH timeout | Key, bastion, or `ProxyJump` missing | `-vvv`; fix `ansible_ssh_common_args` / inventory vars |
+ | `changed=` on every run | `shell`/`command` without `changed_when`/`creates` | Switch to module or add honest idempotency guards |
+ | Handler restarts every run | Probe task reports `changed` | `changed_when: false` on read-only tasks |
+ | Template wrong / empty | Undefined var or wrong host/group_vars | `ansible-inventory --host H`; add `defaults/main.yml` |
+ | Check mode misses later tasks | Conditionals depend on registered live results | Document check-mode limits; use `check_mode: false` only when justified |
+ | Vault decrypt fail | Wrong password / vault-id | Confirm `--vault-id` and encrypted file set |
+ | Module not found | Collection not installed / unpinned | `ansible-galaxy collection install -r requirements.yml` |
+ | Partial fleet outage after apply | No `serial` / fail budget | Add `serial` + `max_fail_percentage`; tag rollback tasks |
- - Pin collections in `requirements.yml`.
- - Use `serial` / `max_fail_percentage` for rolling host updates.
- - Tag tasks (`--tags`) for partial runs during incidents.
- - Document required privilege (become) per play.
+ ---
+ ## Best Practices
+
+ 1. Name every task; use tags (`--tags`) for incident partial runs.
+ 2. Put secrets in vaulted files; reference with `{{ vault_db_password }}`—never literal credentials in examples.
+ 3. Prefer Execution Environments / AWX for shared controller consistency on large fleets.
+ 4. Pair with `@terraform` (provision) then Ansible (configure); avoid duplicating cloud resource creation in both.
+ 5. Lint with `ansible-lint`; treat `no-changed-when` failures as real bugs.
+ 6. Document required `become` and network access (bastion) in the role README.
+
+ ---
+
## Limitations
- - Network devices and Windows need specialized collections and remoting setup.
- - Extremely large fleets may need AWX/Tower or Execution Environments.
- - Check mode cannot always simulate every module accurately.
+ - Check mode is a simulation: modules without check support no-op; registered-var conditionals may skip falsely.
+ - Windows and network devices need specialized collections and remoting (`ansible.windows`, vendor collections).
+ - Extremely large fleets need AWX/Tower or EE pipelines—raw laptop controllers do not scale.
+ - Stop and ask if vault password location, inventory ownership, or prod `--limit` scope is unclear.
- ## Related skills
+ ---
- - `@terraform` - provision machines Ansible configures
- - `@docker` - when configuration moves into images
- - `@packer` - golden images with Ansible provisioners
+ ## Related Skills
+
+ - `@terraform` — provision machines Ansible configures
+ - `@docker` — when config moves into images; Ansible only hardens the host
+ - `@kubernetes` / `@helm` — cluster-native config instead of SSH fleets
+ - `@vault` — dynamic secrets beyond static ansible-vault files
+ - `@argocd` / `@fluxcd` — GitOps for K8s (complement, not replace, VM Ansible)
+
+ ---
+
+ ## Agent Operational Directive
+
+ > **MANDATORY**: Prefer FQCN modules over shell; make any unavoidable `command`/`shell` idempotent with `creates`/`changed_when`. Gate production with `--check --diff`. Pin collections in `requirements.yml`. Never commit vault passwords or plaintext secrets; never invent credentials. Confirm inventory and `--limit` before touching prod.
+
+ ---
+
+ ## Source anchors (research)
+
+ - [Check mode and diff mode](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html)
+ - [Installing collections (`requirements.yml`)](https://docs.ansible.com/projects/ansible/latest/collections_guide/collections_installing.html)
+ - [ansible-lint `no-changed-when`](https://docs.ansible.com/projects/lint/rules/no-changed-when/)
+ - [ansible.builtin.shell module notes](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/shell_module.html)