Skip to main content
RunBook Academy

AnsibleXLV · Debugging and TroubleshootingDebugging and Troubleshooting

Proving which hosts you are about to touch

Intermediate⏱ ~22 minansible-inventoryansible-playbook

What you'll learn

  • Resolve why a host is or is not a member of a group from inventory output alone
  • Compare the host list a pattern produces against the list you intended
  • Diagnose an inventory that differs between the controller and a colleague machine
  • Use --list-hosts as the last gate before any change run

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.

You have already met the inventory tools as a pre-flight discipline — print what hosts: all means before you rely on it. This lesson uses the same tools for a different job: diagnosis, when the host list is not what you expected and you need to find out why.

Two questions come up over and over, and neither is answerable by reading the inventory files:

  • Why is this host in this group?
  • Why is this host not in the group I expected?

The first is more common in incidents. A change reached a host it should not have, and someone needs to explain how.

The four views, and what each is for

Read-only / Safethe structural view — executed on ansible-core 2.21.3
$ ansible-inventory -i inv.ini --graph
@all:
|--@ungrouped:
|--@local:
|  |--lh
|--@far:
|  |--web01
|  |--web02
CommandAnswers
--graphWhat is the group tree, and which hosts are in each group
--graph --varsThe same, plus the variables visible at each level
--listThe complete parsed inventory as JSON, including _meta.hostvars
--host <name>Every variable that resolves for one host, from inventory sources
--yamlThe same content as --list, in a form a human can diff

For debugging, --graph --vars and --host do most of the work, and --yaml is the one to use when you want to compare two machines.

Why is this host in this group?

Membership has exactly four possible sources, and the diagnosis is a matter of eliminating them in order.

1. It is listed explicitly. Grep the inventory tree for the hostname. This is the case people check, and it is the least common cause of a surprise.

2. It is a member through a children relationship. A host in web_prod is also in webservers if webservers has web_prod as a child. --graph shows this as nesting; a flat mental model of the inventory does not.

3. A pattern or range expanded to it. An entry like web[01:50].example.com in an INI inventory produces fifty hosts, and the hostname you are looking for is in there without appearing literally.

4. A dynamic source or constructed plugin created it. A cloud inventory plugin invents groups from tags; the constructed plugin creates groups from Jinja expressions over facts and variables. Neither appears in any file that mentions the group name next to the host name.

Read-only / Safethe elimination, in order
h=app-047.example.com

# 1. Listed literally anywhere?
grep -rn "$h" inventory/ || echo 'not literal'

# 2. Which groups does the resolved inventory place it in?
ansible-inventory -i inventory/prod --host "$h" | grep -A 20 group_names \
|| ansible -i inventory/prod "$h" -m debug -a 'var=group_names'

# 3. Which group definitions expand to ranges?
grep -rnE '\[[0-9]+:[0-9]+\]' inventory/

# 4. Which inventory sources are in play at all?
ansible-inventory -i inventory/prod --list | head -5
ansible-config dump --only-changed | grep -i inventory

Why is this host not in the group?

The mirror-image question, and it fails silently in a way the first one does not: a pattern that matches nothing produces a warning and an empty run, and an empty run looks like a fast successful run.

The three usual causes:

A name mismatch. The inventory knows app-047 and you typed app047, or the inventory knows the FQDN and the group definition uses the short name. Inventory hostnames are strings; nothing normalises them.

Group membership defined in the wrong direction. children goes from parent to child. Adding webservers as a child of web_prod when you meant the reverse produces a tree that looks plausible in a diff and places hosts in the wrong place.

Source order. When two inventory sources define the same group, the result depends on how they merge, and a host defined in a file that is not being read at all is the commonest cause of “it is right there in the inventory”.

Read-only / Safeproving which sources are actually being read
$ ansible-playbook -i inv.ini one.yml -vvv 2>&1 | grep -i 'inventory source'
Parsed /home/ops/estate/inv.ini inventory source with ini plugin

That single line resolves more inventory arguments than any amount of reading files. If the file you have been editing is not in that list, nothing else you check matters.

The pattern is the blast radius

--list-hosts is the gate. It resolves the same pattern the run will use, through the same inventory, and prints the result without connecting to anything.

Read-only / Safethe last check before a change run
$ ansible-playbook -i inventory/prod patch.yml --limit @retarget.txt --list-hosts
playbook: patch.yml

play #1 (appservers): Patch and configure    TAGS: []
  pattern: ['appservers']
  hosts (80):
    app-047.example.com
    app-112.example.com
    ...

Illustrative output

Read the count first. hosts (80) against a limit file you know contains 80 lines is a pass. hosts (77) means three names in your file do not exist in the inventory, and Ansible will proceed anyway.

Diagnosing “it works on my machine”

When two people get different host lists from the same repository, the difference is in one of four places, and --yaml finds it in a minute.

Read-only / Safediffing two controllers' view of the same inventory
# on each machine, from the same commit
ansible-inventory -i inventory/prod --list --yaml > inv-$(hostname -s).yaml

# then compare
diff -u inv-controller.yaml inv-laptop.yaml | head -40

# and check the four usual suspects
ansible --version | grep -E 'core|config file'
ansible-config dump --only-changed | grep -iE 'inventory|vault|collections'
git -C . rev-parse --short HEAD
ls -l inventory/

The four suspects, in order of how often they are the answer: a different ansible.cfg in effect, a different working directory changing which relative inventory path resolves, a different commit checked out, and an environment variable (ANSIBLE_INVENTORY) set in one shell and not the other.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A change reached app-047, which nobody expected to be in the appservers group. grep finds the hostname nowhere in the inventory tree. What is the most efficient next step?

  2. Q2. Two engineers run the same playbook from the same commit and get different host counts. Which are plausible causes? Select all that apply.

  3. Q3. A --limit pattern that matches fewer hosts than intended is easy to notice, because Ansible refuses to run when part of the limit does not resolve.

  4. Q4. A variable has an unexpected value on one host and does not appear in ansible-inventory --host output for it. What does that tell you?

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