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:
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
|
||||
Reference in New Issue
Block a user