Understanding and Resolving Ansible group_vars Precedence in Merged Host Group Scenarios


2 views

When working with Ansible 2.2, I encountered unexpected variable behavior where a host belonging to multiple groups inherits variables from all parent groups. Here's the exact scenario:

[webserver]
aegir.dev

[hostmaster]
aegir.dev

The group_vars directory contains:

# group_vars/webserver.yml
my_var:
  - vagrant
# group_vars/hostmaster.yml
my_var:
  - vagrant
  - aegir

The test playbook:

- hosts: webserver
  tasks:
    - debug: var=my_var

- hosts: hostmaster
  tasks:
    - debug: var=my_var

Both play executions show the merged variable content:

"my_var": [
    "vagrant",
    "aegir"
]

Ansible's variable precedence follows these rules when a host belongs to multiple groups:

  1. Variables from all groups the host belongs to are merged
  2. When the same variable exists in multiple groups, the last alphabetical group takes precedence
  3. The merge is recursive for dictionary variables
  4. For lists, items are combined (not replaced)

For the specific case where you need different behavior:

# group_vars/webserver.yml
webserver_my_var:
  - vagrant

# group_vars/hostmaster.yml
hostmaster_my_var:
  - vagrant
  - aegir

Then in your playbook:

- hosts: webserver
  vars:
    my_var: "{{ webserver_my_var }}"
  tasks:
    - debug: var=my_var

- hosts: hostmaster
  vars:
    my_var: "{{ hostmaster_my_var }}"
  tasks:
    - debug: var=my_var

For more complex scenarios, use group priorities:

# ansible.cfg
[defaults]
hash_behaviour = merge

Then control merging behavior explicitly:

# group_vars/webserver.yml
my_var:
  __webserver_config: true
  values:
    - vagrant

# group_vars/hostmaster.yml
my_var:
  __hostmaster_config: true
  values:
    - vagrant
    - aegir

For real-world deployments:

  1. Use distinct variable names for different groups
  2. Leverage inventory group hierarchy (parent/child groups)
  3. Consider using host_vars for host-specific overrides
  4. Document variable precedence expectations in your README

Let's examine a common scenario in Ansible configuration management where variable precedence in group_vars can produce unexpected results. Here's our initial setup:

[webserver]
aegir.dev

[hostmaster]
aegir.dev

We have two group_vars files defining the same variable:

# group_vars/webserver.yml
my_var:
  - vagrant
# group_vars/hostmaster.yml
my_var:
  - vagrant
  - aegir

When running our playbook against both groups, we notice both groups use the hostmaster.yml variables:

PLAY [webserver] ***************************************************************

TASK [debug] *******************************************************************
ok: [aegir.dev] => {
    "my_var": [
        "vagrant",
        "aegir"
    ]
}

PLAY [hostmaster] **************************************************************

TASK [debug] *******************************************************************
ok: [aegir.dev] => {
    "my_var": [
        "vagrant",
        "aegir"
    ]
}

This occurs because Ansible merges variables from all groups a host belongs to, with later groups overriding earlier ones when there are conflicts. In our case:

  1. The host belongs to both webserver and hostmaster groups
  2. Variables from all groups are merged
  3. When the same variable exists in multiple groups, the last one wins

There are several approaches to handle this scenario:

1. Using Group-Specific Prefixes

# group_vars/webserver.yml
webserver_my_var:
  - vagrant

# group_vars/hostmaster.yml
hostmaster_my_var:
  - vagrant
  - aegir

2. Leveraging Group Priority

# ansible.cfg
[defaults]
hash_behaviour = merge

3. Using Host Variables

# host_vars/aegir.dev.yml
my_var:
  - vagrant
  - aegir

For the described use case where hostmaster is a specialized webserver, consider this structure:

# group_vars/webserver.yml
base_config:
  - vagrant

# group_vars/hostmaster.yml
extra_config:
  - aegir

Then in your role or playbook:

vars:
  combined_config: "{{ base_config | default([]) + extra_config | default([]) }}"

Understanding Ansible's variable precedence is crucial when dealing with complex inventory structures. By designing your variable hierarchy carefully and using proper naming conventions, you can avoid unexpected merging behavior while maintaining clean, maintainable configuration code.