Skip to main content
RunBook Academy

AnsibleIV · Inventory FundamentalsInventory inspection

When two groups disagree

Advanced⏱ ~22 minbash

What you'll learn

  • State the inventory variable resolution rule exactly and predict the winner in each case
  • Use ansible_group_priority and explain why it treats a symptom rather than a cause
  • Detect a conflict before a run using read-only inventory inspection
  • Restructure an inventory so the conflict cannot occur, rather than resolving it

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

A host is in several groups. Two of them set max_connections. Ansible does not warn, does not fail, and does not ask. It picks one, applies it, and reports a successful run.

This lesson is the rule it uses. Every claim here was executed against ansible-core 2.21.3 rather than read from documentation, because this is exactly the kind of semantic that is easy to get plausibly wrong.

The rule

Three clauses, in order of decisiveness.

  1. Host beats group. A variable set on the host wins over any group the host belongs to.
  2. Child beats parent. A variable set on a child group wins over the same variable on its parent.
  3. Same-level groups merge alphabetically, so the last group name in alphabetical order wins — unless ansible_group_priority says otherwise, in which case the higher priority wins.

Each clause, executed.

Host beats group

all:
  children:
    grp:
      hosts:
        web01.example.com:
          max_connections: 999
      vars:
        max_connections: 100
Read-only / Safehost variable versus group variable
$ ansible-inventory -i inventory/hosts.yml --host web01.example.com
{
  "max_connections": 999
}

Child beats parent

all:
  children:
    parentgrp:
      vars:
        max_connections: 100
      children:
        childgrp:
          hosts:
            web01.example.com:
          vars:
            max_connections: 400
Read-only / Safenested groups disagreeing
$ ansible-inventory -i inventory/hosts.yml --host web01.example.com
{
  "max_connections": 400
}

Same-level groups merge alphabetically

This is the clause that causes incidents, because the deciding factor is the group’s name.

all:
  children:
    alpha:
      hosts:
        web01.example.com:
      vars:
        max_connections: 100
    zulu:
      hosts:
        web01.example.com:
      vars:
        max_connections: 400
Read-only / Safetwo unrelated groups, same variable
$ ansible-inventory -i inventory/hosts.yml --host web01.example.com
{
  "max_connections": 400
}

Rename zulu to backup and the answer becomes 100, with no other edit. That is the whole problem in one sentence.

ansible_group_priority

The documented override. Set it on a group and that group’s variables win against same-level siblings regardless of name. Higher wins; the default is 1.

all:
  children:
    alpha:
      hosts:
        web01.example.com:
      vars:
        max_connections: 100
        ansible_group_priority: 10
    zulu:
      hosts:
        web01.example.com:
      vars:
        max_connections: 400
Read-only / Safethe same inventory, with a priority on alpha
$ ansible-inventory -i inventory/hosts.yml --host web01.example.com
{
  "max_connections": 100
}

The position this course takes

ansible_group_priority works. Use it when you need it. But reaching for it should feel like a warning rather than a solution, and here is why.

It encodes the decision in the wrong place. The reason alpha wins is now a number in a variable block, not a structure a reviewer can see. Reading the inventory no longer tells you which value a host gets.

It is invisible in a diff that adds a group. Somebody adds a third group setting the same variable. The priority does not mention it, because priorities are per-group rather than per-variable, and the new group joins the alphabetical scramble with whatever number it happens to have.

Relying on alphabetical merge order is not a design, it is a coincidence. It works, and it continues working until someone renames a group, which is an operation nobody expects to change behaviour.

The better move is almost always to make the conflict impossible.

Detecting conflicts before they matter

The resolved output shows the winner, not the argument. --host will tell you max_connections is 400 and will never tell you that something else wanted 100.

So detection is a two-step: find hosts in more than one group that could disagree, then check what each of those groups defines.

Read-only / Safestep 1 - which groups is this host actually in?
ansible -i inventory/hosts.yml web01.example.com \
  -m debug -a 'var=group_names' --connection=local
Read-only / Safestep 2 - who else defines this variable?
grep -rn 'max_connections' inventory/ group_vars/ host_vars/
Read-only / Safestep 3 - catch it in CI, before review
ansible-inventory -i inventory --list \
| python3 -c '
import json, sys
inv = json.load(sys.stdin)
envs = ["production", "staging", "development"]
members = {e: set(inv.get(e, {}).get("hosts", [])) for e in envs}
for a in envs:
  for b in envs:
      if a < b:
          both = members[a] & members[b]
          if both:
              print(f"CONFLICT {a} and {b}: {sorted(both)}")
'

A check like that belongs in the pipeline rather than in a runbook. A host in two environment groups is a repository-level bug — it is wrong in the file, before anything runs — and the right time to catch it is when the file changes.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Groups `alpha` and `zulu` are both direct children of `all`, both contain web01, and both set `max_connections` - alpha to 100, zulu to 400. What does web01 get?

  2. Q2. `ansible_group_priority` cannot make a parent group override its own child.

  3. Q3. Why does this course treat `ansible_group_priority` as a warning sign rather than a solution? Select all that apply.

  4. Q4. A host is in both `patch_now` and `no_patch`. What is the right way to think about this?

Passing score: 75%. Answers are checked in this browser.