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
202 lines
5.9 KiB
Markdown
202 lines
5.9 KiB
Markdown
# 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
|