docs: add ansible howto collection
Four reference files covering: - ansible.md — core commands, ansible.cfg, key settings - ansible-inventory.md — static/dynamic inventory, directory layout - ansible-variables.md — 22-level precedence, scoping, merge behavior - ansible-roles.md — structure, defaults vs vars, dependencies
This commit is contained in:
4
TASKS.md
4
TASKS.md
@@ -3,8 +3,8 @@
|
|||||||
## Current
|
## Current
|
||||||
|
|
||||||
- [x] Scaffold project structure
|
- [x] Scaffold project structure
|
||||||
- [ ] Create topic template
|
- [x] Create topic template
|
||||||
- [ ] Write first topic
|
- [x] Write first topic (ansible — 4 files)
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -3,7 +3,7 @@
|
|||||||
## Topics to Write
|
## Topics to Write
|
||||||
|
|
||||||
- [ ] git — common workflows, rebase, stash, bisect
|
- [ ] git — common workflows, rebase, stash, bisect
|
||||||
- [ ] ansible — playbook patterns, inventory, vault
|
- [x] ansible — playbook patterns, inventory, vault, variables, roles
|
||||||
- [ ] podman — build, run, compose, volumes
|
- [ ] podman — build, run, compose, volumes
|
||||||
- [ ] jq — filters, select, map, slurp
|
- [ ] jq — filters, select, map, slurp
|
||||||
- [ ] curl — headers, auth, methods, output
|
- [ ] curl — headers, auth, methods, output
|
||||||
|
|||||||
214
topics/ansible-inventory.md
Normal file
214
topics/ansible-inventory.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Ansible Inventory
|
||||||
|
|
||||||
|
> Define which hosts to manage, how to group them, and where to find them.
|
||||||
|
|
||||||
|
## Inventory Formats
|
||||||
|
|
||||||
|
### INI Format
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# inventory/hosts.ini
|
||||||
|
|
||||||
|
[webservers]
|
||||||
|
web1.example.com
|
||||||
|
web2.example.com ansible_port=2222
|
||||||
|
|
||||||
|
[dbservers]
|
||||||
|
db1.example.com ansible_user=postgres
|
||||||
|
db[1:3].example.com # db1, db2, db3
|
||||||
|
|
||||||
|
[loadbalancers]
|
||||||
|
lb1.example.com
|
||||||
|
|
||||||
|
# Group of groups
|
||||||
|
[production:children]
|
||||||
|
webservers
|
||||||
|
dbservers
|
||||||
|
loadbalancers
|
||||||
|
|
||||||
|
# Group variables
|
||||||
|
[webservers:vars]
|
||||||
|
http_port=8080
|
||||||
|
app_env=production
|
||||||
|
|
||||||
|
[all:vars]
|
||||||
|
ansible_python_interpreter=/usr/bin/python3
|
||||||
|
```
|
||||||
|
|
||||||
|
### YAML Format (preferred)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory/hosts.yml
|
||||||
|
all:
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
children:
|
||||||
|
production:
|
||||||
|
children:
|
||||||
|
webservers:
|
||||||
|
hosts:
|
||||||
|
web1.example.com:
|
||||||
|
web2.example.com:
|
||||||
|
ansible_port: 2222
|
||||||
|
vars:
|
||||||
|
http_port: 8080
|
||||||
|
dbservers:
|
||||||
|
hosts:
|
||||||
|
db1.example.com:
|
||||||
|
ansible_user: postgres
|
||||||
|
loadbalancers:
|
||||||
|
hosts:
|
||||||
|
lb1.example.com:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
inventory/
|
||||||
|
├── hosts.yml # Host definitions
|
||||||
|
├── group_vars/
|
||||||
|
│ ├── all.yml # Vars for every host
|
||||||
|
│ ├── all/ # Dir form (merged, alphabetical)
|
||||||
|
│ │ ├── common.yml
|
||||||
|
│ │ └── vault.yml # Encrypted secrets
|
||||||
|
│ ├── webservers.yml # Vars for webservers group
|
||||||
|
│ └── production.yml # Vars for production parent group
|
||||||
|
└── host_vars/
|
||||||
|
├── web1.example.com.yml
|
||||||
|
└── db1.example.com/ # Dir form
|
||||||
|
├── main.yml
|
||||||
|
└── vault.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Environments
|
||||||
|
|
||||||
|
```
|
||||||
|
inventories/
|
||||||
|
├── staging/
|
||||||
|
│ ├── hosts.yml
|
||||||
|
│ ├── group_vars/
|
||||||
|
│ └── host_vars/
|
||||||
|
└── production/
|
||||||
|
├── hosts.yml
|
||||||
|
├── group_vars/
|
||||||
|
└── host_vars/
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook site.yml -i inventories/staging/
|
||||||
|
ansible-playbook site.yml -i inventories/production/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic Inventories
|
||||||
|
|
||||||
|
### Script-Based (legacy)
|
||||||
|
|
||||||
|
A script that outputs JSON when called with `--list` or `--host <name>`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# inventory/dynamic.py — must be executable (chmod +x)
|
||||||
|
import json, sys
|
||||||
|
|
||||||
|
def get_inventory():
|
||||||
|
return {
|
||||||
|
"webservers": {
|
||||||
|
"hosts": ["web1.example.com", "web2.example.com"],
|
||||||
|
"vars": {"http_port": 8080}
|
||||||
|
},
|
||||||
|
"dbservers": {
|
||||||
|
"hosts": ["db1.example.com"]
|
||||||
|
},
|
||||||
|
"_meta": {
|
||||||
|
"hostvars": {
|
||||||
|
"web1.example.com": {"ansible_port": 22},
|
||||||
|
"db1.example.com": {"ansible_user": "postgres"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if "--list" in sys.argv:
|
||||||
|
print(json.dumps(get_inventory(), indent=2))
|
||||||
|
elif "--host" in sys.argv:
|
||||||
|
host = sys.argv[sys.argv.index("--host") + 1]
|
||||||
|
hostvars = get_inventory().get("_meta", {}).get("hostvars", {})
|
||||||
|
print(json.dumps(hostvars.get(host, {})))
|
||||||
|
```
|
||||||
|
|
||||||
|
The `_meta.hostvars` key avoids per-host `--host` calls (efficiency).
|
||||||
|
|
||||||
|
### Plugin-Based (modern, preferred)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory/aws_ec2.yml — filename must end with the plugin suffix
|
||||||
|
plugin: amazon.aws.aws_ec2
|
||||||
|
regions:
|
||||||
|
- eu-west-1
|
||||||
|
keyed_groups:
|
||||||
|
- key: tags.Environment
|
||||||
|
prefix: env
|
||||||
|
- key: instance_type
|
||||||
|
prefix: type
|
||||||
|
filters:
|
||||||
|
tag:Managed: ansible
|
||||||
|
compose:
|
||||||
|
ansible_host: private_ip_address
|
||||||
|
```
|
||||||
|
|
||||||
|
Common inventory plugins:
|
||||||
|
|
||||||
|
| Plugin | Source | Suffix |
|
||||||
|
|-------------------------------|------------|-----------------|
|
||||||
|
| `amazon.aws.aws_ec2` | AWS EC2 | `aws_ec2.yml` |
|
||||||
|
| `azure.azcollection.azure_rm`| Azure | `azure_rm.yml` |
|
||||||
|
| `google.cloud.gcp_compute` | GCP | `gcp.yml` |
|
||||||
|
| `community.general.proxmox` | Proxmox | `proxmox.yml` |
|
||||||
|
| `community.docker.docker_containers` | Docker | `docker.yml` |
|
||||||
|
| `ansible.builtin.constructed` | Derived | `constructed.yml`|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify dynamic inventory output
|
||||||
|
ansible-inventory -i inventory/aws_ec2.yml --graph
|
||||||
|
ansible-inventory -i inventory/aws_ec2.yml --list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constructed Inventory (compose groups from existing data)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory/constructed.yml
|
||||||
|
plugin: ansible.builtin.constructed
|
||||||
|
strict: false
|
||||||
|
groups:
|
||||||
|
is_debian: ansible_os_family == "Debian"
|
||||||
|
is_large: ansible_memtotal_mb > 8192
|
||||||
|
keyed_groups:
|
||||||
|
- key: ansible_distribution | lower
|
||||||
|
prefix: os
|
||||||
|
```
|
||||||
|
|
||||||
|
## Special Variables
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
|-----------------------|------------------------------------|
|
||||||
|
| `ansible_host` | IP/hostname to connect to |
|
||||||
|
| `ansible_port` | SSH port (default: 22) |
|
||||||
|
| `ansible_user` | SSH user |
|
||||||
|
| `ansible_ssh_private_key_file` | SSH key path |
|
||||||
|
| `ansible_become` | Enable privilege escalation |
|
||||||
|
| `ansible_connection` | Connection type (`ssh`, `local`) |
|
||||||
|
| `ansible_python_interpreter` | Python path on target |
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Script inventories must be executable (`chmod +x`) and have a shebang
|
||||||
|
- Plugin inventory files must end with the correct suffix or be listed in `enable_plugins`
|
||||||
|
- `group_vars/` directory must sit next to the inventory file, or at playbook level
|
||||||
|
- YAML inventory: hosts need trailing colon even with no vars (`web1.example.com:`)
|
||||||
|
- Groups named `all` and `ungrouped` are implicit — don't redefine them
|
||||||
|
- Directory form `group_vars/all/` merges files alphabetically — name carefully
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `ansible` — core commands and ansible.cfg
|
||||||
|
- `ansible-variables` — variable precedence across inventory layers
|
||||||
|
- `ansible-roles` — role structure and organization
|
||||||
211
topics/ansible-roles.md
Normal file
211
topics/ansible-roles.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Ansible Roles
|
||||||
|
|
||||||
|
> Reusable, self-contained units of automation with a standard directory structure.
|
||||||
|
|
||||||
|
## Role Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
roles/
|
||||||
|
└── nginx/
|
||||||
|
├── defaults/
|
||||||
|
│ └── main.yml # Low-precedence defaults (user overrides these)
|
||||||
|
├── vars/
|
||||||
|
│ └── main.yml # High-precedence vars (internal to role)
|
||||||
|
├── tasks/
|
||||||
|
│ └── main.yml # Entry point for tasks
|
||||||
|
├── handlers/
|
||||||
|
│ └── main.yml # Handlers triggered by notify
|
||||||
|
├── templates/
|
||||||
|
│ └── nginx.conf.j2 # Jinja2 templates
|
||||||
|
├── files/
|
||||||
|
│ └── index.html # Static files for copy module
|
||||||
|
├── meta/
|
||||||
|
│ └── main.yml # Dependencies, galaxy metadata
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
All directories are optional — include only what the role needs.
|
||||||
|
|
||||||
|
### What Goes Where
|
||||||
|
|
||||||
|
| Directory | Purpose | Precedence |
|
||||||
|
|-------------|--------------------------------|-------------|
|
||||||
|
| `defaults/` | Default variable values | **Lowest** — meant to be overridden |
|
||||||
|
| `vars/` | Internal role variables | **High** — not easily overridden |
|
||||||
|
| `tasks/` | Main task list | Entry point: `tasks/main.yml` |
|
||||||
|
| `handlers/` | Restart/reload triggers | Run once at end of play |
|
||||||
|
| `templates/` | Jinja2 files (`.j2`) | Referenced as `template: nginx.conf.j2` |
|
||||||
|
| `files/` | Static files | Referenced as `copy: src=index.html` |
|
||||||
|
| `meta/` | Role deps, platform info | Processed before role tasks run |
|
||||||
|
|
||||||
|
## Creating a Role
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scaffold with ansible-galaxy
|
||||||
|
ansible-galaxy role init roles/nginx
|
||||||
|
|
||||||
|
# Minimal manual structure
|
||||||
|
mkdir -p roles/nginx/{tasks,defaults,templates,handlers,meta}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Roles
|
||||||
|
|
||||||
|
### In a Playbook
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# site.yml
|
||||||
|
- hosts: webservers
|
||||||
|
roles:
|
||||||
|
- nginx # simple
|
||||||
|
- role: nginx # with params
|
||||||
|
vars:
|
||||||
|
nginx_listen_port: 8080
|
||||||
|
- role: nginx # conditional
|
||||||
|
when: install_nginx | bool
|
||||||
|
- role: nginx # tagged
|
||||||
|
tags: [web, nginx]
|
||||||
|
```
|
||||||
|
|
||||||
|
### With `include_role` / `import_role`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tasks:
|
||||||
|
# Static import — processed at parse time
|
||||||
|
- import_role:
|
||||||
|
name: nginx
|
||||||
|
|
||||||
|
# Dynamic include — processed at runtime (supports loops/conditionals)
|
||||||
|
- include_role:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- nginx
|
||||||
|
- certbot
|
||||||
|
```
|
||||||
|
|
||||||
|
| | `import_role` | `include_role` |
|
||||||
|
|---|---|---|
|
||||||
|
| When parsed | Playbook load | Runtime |
|
||||||
|
| Tags/when | Applied to all tasks inside | Applied to include itself |
|
||||||
|
| Loops | Not supported | Supported |
|
||||||
|
| Handlers | Visible globally | Scoped to include |
|
||||||
|
|
||||||
|
## Role Dependencies
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/meta/main.yml
|
||||||
|
dependencies:
|
||||||
|
- role: common
|
||||||
|
- role: firewall
|
||||||
|
vars:
|
||||||
|
firewall_allowed_ports:
|
||||||
|
- 80
|
||||||
|
- 443
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies run **before** the role's own tasks. Duplicate dependencies are skipped unless `allow_duplicates: true` is set.
|
||||||
|
|
||||||
|
## Example Role: nginx
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/defaults/main.yml
|
||||||
|
nginx_listen_port: 80
|
||||||
|
nginx_worker_processes: auto
|
||||||
|
nginx_server_name: "_"
|
||||||
|
nginx_root: /var/www/html
|
||||||
|
nginx_access_log: /var/log/nginx/access.log
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/tasks/main.yml
|
||||||
|
- name: Install nginx
|
||||||
|
apt:
|
||||||
|
name: nginx
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: Deploy configuration
|
||||||
|
template:
|
||||||
|
src: nginx.conf.j2
|
||||||
|
dest: /etc/nginx/nginx.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload nginx
|
||||||
|
|
||||||
|
- name: Enable and start nginx
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/handlers/main.yml
|
||||||
|
- name: Reload nginx
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: reloaded
|
||||||
|
```
|
||||||
|
|
||||||
|
## Splitting Tasks
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/tasks/main.yml
|
||||||
|
- import_tasks: install.yml
|
||||||
|
- import_tasks: configure.yml
|
||||||
|
- import_tasks: service.yml
|
||||||
|
|
||||||
|
# Conditional platform tasks
|
||||||
|
- import_tasks: debian.yml
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
- import_tasks: redhat.yml
|
||||||
|
when: ansible_os_family == "RedHat"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Layout with Roles
|
||||||
|
|
||||||
|
```
|
||||||
|
ansible-project/
|
||||||
|
├── ansible.cfg
|
||||||
|
├── site.yml # Master playbook
|
||||||
|
├── webservers.yml # Play for web tier
|
||||||
|
├── dbservers.yml # Play for db tier
|
||||||
|
├── inventory/
|
||||||
|
│ ├── hosts.yml
|
||||||
|
│ ├── group_vars/
|
||||||
|
│ └── host_vars/
|
||||||
|
├── roles/
|
||||||
|
│ ├── common/ # Shared baseline
|
||||||
|
│ ├── nginx/
|
||||||
|
│ ├── postgresql/
|
||||||
|
│ └── requirements.yml # Galaxy dependencies
|
||||||
|
└── collections/
|
||||||
|
└── requirements.yml # Collection dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/requirements.yml
|
||||||
|
- src: geerlingguy.docker
|
||||||
|
version: "6.1.0"
|
||||||
|
- src: geerlingguy.certbot
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-galaxy install -r roles/requirements.yml -p roles/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `defaults/` is for users to override; `vars/` is for role internals — mixing them up breaks precedence
|
||||||
|
- Handlers run at end of play, not after each task — use `meta: flush_handlers` if needed mid-play
|
||||||
|
- Role names must be valid Python identifiers (no hyphens) when used as variable namespaces
|
||||||
|
- `import_role` makes role tasks visible to `--list-tasks`; `include_role` does not
|
||||||
|
- Dependencies declared in `meta/` run every time the role is referenced unless deduplicated
|
||||||
|
- Template paths are relative to `templates/` — use `nginx.conf.j2`, not `templates/nginx.conf.j2`
|
||||||
|
- File paths in `copy` are relative to `files/` — use `index.html`, not `files/index.html`
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `ansible` — core commands and ansible.cfg
|
||||||
|
- `ansible-variables` — how defaults/ vs vars/ fits into precedence
|
||||||
|
- `ansible-inventory` — inventory structure for role-consuming projects
|
||||||
201
topics/ansible-variables.md
Normal file
201
topics/ansible-variables.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Ansible Variables
|
||||||
|
|
||||||
|
> Variable precedence, inheritance, and scoping — the part everyone gets wrong.
|
||||||
|
|
||||||
|
## Precedence (lowest to highest)
|
||||||
|
|
||||||
|
Ansible merges variables from many sources. **Higher number wins.**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. command line values (for constants, not variables)
|
||||||
|
2. role defaults (roles/x/defaults/main.yml)
|
||||||
|
3. inventory file or script group vars
|
||||||
|
4. inventory group_vars/all
|
||||||
|
5. playbook group_vars/all
|
||||||
|
6. inventory group_vars/*
|
||||||
|
7. playbook group_vars/*
|
||||||
|
8. inventory file or script host vars
|
||||||
|
9. inventory host_vars/*
|
||||||
|
10. playbook host_vars/*
|
||||||
|
11. host facts / cached set_facts
|
||||||
|
12. play vars (vars: in play)
|
||||||
|
13. play vars_prompt
|
||||||
|
14. play vars_files
|
||||||
|
15. role vars (roles/x/vars/main.yml)
|
||||||
|
16. block vars (vars: on block)
|
||||||
|
17. task vars (vars: on task)
|
||||||
|
18. include_vars
|
||||||
|
19. set_facts / registered vars
|
||||||
|
20. role params (role: {role: x, param: val})
|
||||||
|
21. include params
|
||||||
|
22. extra vars (-e / --extra-vars) *** always wins ***
|
||||||
|
```
|
||||||
|
|
||||||
|
### Practical Summary
|
||||||
|
|
||||||
|
| Source | Precedence | Use for |
|
||||||
|
|-------------------------|:----------:|--------------------------------|
|
||||||
|
| Role defaults | Lowest | Sane defaults, meant to be overridden |
|
||||||
|
| `group_vars/all` | Low | Organization-wide defaults |
|
||||||
|
| `group_vars/<group>` | Medium | Environment/group-specific |
|
||||||
|
| `host_vars/<host>` | Medium+ | Host-specific overrides |
|
||||||
|
| Play/block/task `vars:` | High | Playbook-scoped values |
|
||||||
|
| Role vars | High | Role internals, not for override |
|
||||||
|
| `set_fact` | High | Runtime-computed values |
|
||||||
|
| Extra vars (`-e`) | Highest | CLI overrides, CI/CD pipelines |
|
||||||
|
|
||||||
|
## Group Variable Inheritance
|
||||||
|
|
||||||
|
When a host belongs to multiple groups, merge order is:
|
||||||
|
|
||||||
|
1. **`all` group** (lowest)
|
||||||
|
2. **Parent groups** (alphabetical within same level)
|
||||||
|
3. **Child groups** (override parents)
|
||||||
|
4. **Host vars** (override all groups)
|
||||||
|
|
||||||
|
### Controlling Merge Order
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory/hosts.yml
|
||||||
|
all:
|
||||||
|
children:
|
||||||
|
env_staging:
|
||||||
|
vars:
|
||||||
|
app_env: staging
|
||||||
|
log_level: debug
|
||||||
|
env_production:
|
||||||
|
vars:
|
||||||
|
app_env: production
|
||||||
|
log_level: warn
|
||||||
|
webservers:
|
||||||
|
hosts:
|
||||||
|
web1.example.com: # member of webservers AND env_production
|
||||||
|
vars:
|
||||||
|
http_port: 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `ansible_group_priority` to force ordering (default: 1):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# group_vars/env_production.yml
|
||||||
|
ansible_group_priority: 10 # higher = wins over other groups
|
||||||
|
app_env: production
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable Scoping
|
||||||
|
|
||||||
|
| Scope | Defined in | Visible to |
|
||||||
|
|----------|-----------------------------------|---------------------------|
|
||||||
|
| Global | `extra_vars`, config, env | Everything |
|
||||||
|
| Play | `vars:`, `vars_files:`, `include_vars` | Current play |
|
||||||
|
| Host | Inventory, facts, `set_fact` | That host across plays |
|
||||||
|
| Role | `defaults/`, `vars/` | Role + dependents |
|
||||||
|
| Task | `register`, task-level `vars:` | Subsequent tasks (same host) |
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Layered Defaults
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# group_vars/all.yml — base defaults
|
||||||
|
ntp_server: ntp.pool.org
|
||||||
|
log_level: info
|
||||||
|
app_port: 8080
|
||||||
|
|
||||||
|
# group_vars/production.yml — override for prod
|
||||||
|
log_level: warn
|
||||||
|
|
||||||
|
# host_vars/web1.example.com.yml — host-specific
|
||||||
|
app_port: 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
Result for `web1` in `production`: `ntp_server=ntp.pool.org`, `log_level=warn`, `app_port=9090`.
|
||||||
|
|
||||||
|
### Role Defaults vs Role Vars
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/nginx/defaults/main.yml — LOW precedence, user overrides these
|
||||||
|
nginx_worker_processes: auto
|
||||||
|
nginx_listen_port: 80
|
||||||
|
|
||||||
|
# roles/nginx/vars/main.yml — HIGH precedence, internal to role
|
||||||
|
nginx_conf_path: /etc/nginx/nginx.conf # don't let users change this
|
||||||
|
nginx_user: www-data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule of thumb:** put everything in `defaults/` unless the role breaks if the value changes.
|
||||||
|
|
||||||
|
### Merging Dictionaries and Lists
|
||||||
|
|
||||||
|
By default, Ansible **replaces** (does not merge) dicts and lists. To merge:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# ansible.cfg
|
||||||
|
[defaults]
|
||||||
|
hash_behaviour = merge # CAUTION: global, can cause surprises
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer explicit merging with `combine` filter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Merge defaults with overrides
|
||||||
|
set_fact:
|
||||||
|
final_config: "{{ default_config | combine(override_config, recursive=True) }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registered Variables
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Check service status
|
||||||
|
command: systemctl is-active nginx
|
||||||
|
register: nginx_status
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Restart if stopped
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
state: started
|
||||||
|
when: nginx_status.rc != 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vault-Encrypted Variables
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# group_vars/all/vault.yml (encrypted)
|
||||||
|
vault_db_password: "s3cret"
|
||||||
|
|
||||||
|
# group_vars/all/main.yml (references vault)
|
||||||
|
db_password: "{{ vault_db_password }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefix vault variables with `vault_` so it's clear where values originate.
|
||||||
|
|
||||||
|
## Debugging Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show all vars for a host
|
||||||
|
ansible -m debug -a "var=hostvars[inventory_hostname]" web1.example.com
|
||||||
|
|
||||||
|
# Show specific variable
|
||||||
|
ansible -m debug -a "var=http_port" web1.example.com
|
||||||
|
|
||||||
|
# In a playbook
|
||||||
|
- debug: var=ansible_facts
|
||||||
|
- debug: msg="{{ http_port }} on {{ inventory_hostname }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `extra_vars` always win — they override even role `vars/`. Don't use `-e` for defaults
|
||||||
|
- `set_fact` persists for the host across plays in the same run (not across runs unless cached)
|
||||||
|
- `hash_behaviour = merge` is global and rarely what you want — prefer `combine` filter
|
||||||
|
- Variable names are flat — `foo.bar` is dict access, not a separate variable
|
||||||
|
- Jinja2 variables in `when:` don't need `{{ }}` — just `when: my_var == "x"`
|
||||||
|
- Undefined variables fail by default. Use `{{ my_var | default('fallback') }}`
|
||||||
|
- `group_vars/` at playbook level AND inventory level both load — watch for conflicts
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `ansible` — core commands and ansible.cfg
|
||||||
|
- `ansible-inventory` — inventory structure, dynamic inventories
|
||||||
|
- `ansible-roles` — role structure and defaults vs vars
|
||||||
127
topics/ansible.md
Normal file
127
topics/ansible.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Ansible
|
||||||
|
|
||||||
|
> Agentless automation — push-based configuration management over SSH.
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
| Concept | Description |
|
||||||
|
|-------------|--------------------------------------------------|
|
||||||
|
| Inventory | Hosts and groups to manage |
|
||||||
|
| Playbook | YAML file defining tasks to run on hosts |
|
||||||
|
| Role | Reusable unit of tasks, vars, templates, handlers|
|
||||||
|
| Task | Single action (install pkg, copy file, etc.) |
|
||||||
|
| Module | Built-in unit of work (`apt`, `copy`, `template`)|
|
||||||
|
| Handler | Task triggered by `notify`, runs once at end |
|
||||||
|
| Facts | Auto-gathered host info (`ansible_os_family`) |
|
||||||
|
| Vault | Encrypted secrets storage |
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run ad-hoc command on all hosts
|
||||||
|
ansible all -m ping
|
||||||
|
ansible all -m shell -a "uptime"
|
||||||
|
|
||||||
|
# Run playbook
|
||||||
|
ansible-playbook site.yml
|
||||||
|
ansible-playbook site.yml -l webservers # limit to group
|
||||||
|
ansible-playbook site.yml --tags deploy # run tagged tasks only
|
||||||
|
ansible-playbook site.yml --skip-tags debug # skip tagged tasks
|
||||||
|
ansible-playbook site.yml -e "version=2.1" # extra vars (highest precedence)
|
||||||
|
|
||||||
|
# Dry run / check mode
|
||||||
|
ansible-playbook site.yml --check --diff
|
||||||
|
|
||||||
|
# List hosts that would be affected
|
||||||
|
ansible-playbook site.yml --list-hosts
|
||||||
|
ansible-playbook site.yml --list-tasks
|
||||||
|
ansible-playbook site.yml --list-tags
|
||||||
|
|
||||||
|
# Vault
|
||||||
|
ansible-vault create secrets.yml
|
||||||
|
ansible-vault edit secrets.yml
|
||||||
|
ansible-vault encrypt existing.yml
|
||||||
|
ansible-vault decrypt existing.yml
|
||||||
|
ansible-playbook site.yml --ask-vault-pass
|
||||||
|
ansible-playbook site.yml --vault-password-file ~/.vault_pass
|
||||||
|
|
||||||
|
# Galaxy (roles/collections)
|
||||||
|
ansible-galaxy role install geerlingguy.docker
|
||||||
|
ansible-galaxy role init my_role
|
||||||
|
ansible-galaxy collection install community.general
|
||||||
|
|
||||||
|
# Debug / info
|
||||||
|
ansible --version
|
||||||
|
ansible-config dump --only-changed
|
||||||
|
ansible-inventory --graph
|
||||||
|
ansible-inventory --host <hostname>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ansible.cfg
|
||||||
|
|
||||||
|
Config is loaded from the **first file found** in this order:
|
||||||
|
|
||||||
|
1. `$ANSIBLE_CONFIG` (env variable)
|
||||||
|
2. `./ansible.cfg` (current directory)
|
||||||
|
3. `~/.ansible.cfg` (home directory)
|
||||||
|
4. `/etc/ansible/ansible.cfg` (system-wide)
|
||||||
|
|
||||||
|
### Practical ansible.cfg
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[defaults]
|
||||||
|
inventory = ./inventory/
|
||||||
|
roles_path = ./roles/
|
||||||
|
collections_path = ./collections/
|
||||||
|
remote_user = deploy
|
||||||
|
private_key_file = ~/.ssh/id_ed25519
|
||||||
|
host_key_checking = False
|
||||||
|
retry_files_enabled = False
|
||||||
|
stdout_callback = yaml
|
||||||
|
callback_enabled = timer, profile_tasks
|
||||||
|
forks = 20
|
||||||
|
timeout = 30
|
||||||
|
gathering = smart
|
||||||
|
fact_caching = jsonfile
|
||||||
|
fact_caching_connection = /tmp/ansible_facts
|
||||||
|
fact_caching_timeout = 3600
|
||||||
|
vault_password_file = ~/.vault_pass
|
||||||
|
interpreter_python = auto_silent
|
||||||
|
log_path = ./ansible.log
|
||||||
|
|
||||||
|
[privilege_escalation]
|
||||||
|
become = True
|
||||||
|
become_method = sudo
|
||||||
|
become_user = root
|
||||||
|
become_ask_pass = False
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
pipelining = True
|
||||||
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
|
||||||
|
control_path_dir = /tmp/.ansible-cp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Settings Explained
|
||||||
|
|
||||||
|
| Setting | Effect |
|
||||||
|
|------------------------|-------------------------------------------------|
|
||||||
|
| `forks` | Parallel host connections (default: 5) |
|
||||||
|
| `pipelining` | Reduces SSH operations, major speed gain |
|
||||||
|
| `gathering = smart` | Cache facts, skip re-gathering |
|
||||||
|
| `stdout_callback=yaml` | Human-readable output instead of JSON |
|
||||||
|
| `fact_caching` | Persist facts between runs |
|
||||||
|
| `retry_files_enabled` | Disable `.retry` file clutter |
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Config in current dir (`./ansible.cfg`) is ignored if the directory is world-writable
|
||||||
|
- `become: true` in `ansible.cfg` applies globally — prefer setting it per play
|
||||||
|
- `host_key_checking = False` is fine for labs, not for production
|
||||||
|
- `forks` above ~50 can exhaust file descriptors on the control node
|
||||||
|
- `pipelining` requires `requiretty` disabled in sudoers on targets
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `ansible-inventory` — inventory structure and dynamic inventories
|
||||||
|
- `ansible-variables` — variable precedence and inheritance
|
||||||
|
- `ansible-roles` — role structure and best practices
|
||||||
Reference in New Issue
Block a user