The Inventory — Automation's Address Book
In one sentence
Before asking "what to do", Ansible asks "who to do it to". The inventory is the answer.
Why this was needed
With three servers, you can just open three terminals. With seven, you start picking the wrong window. A record of growing a home lab from 3 nodes to 7 shows that the procedure for adding a node is not hard in itself, but mistakes begin the moment a person has to remember "what was done on which node". In fact, the two newly added nodes still carried leftovers from an old cluster (an old kubelet version, old certificates), and when you clean those up by hand, you end up missing one machine.
The inventory flips this problem into "make the list a file". A server that is not written in the file is not an automation target, and every server that is written in it gets treated the same way. This simple rule structurally eliminates the situation where "only one machine is configured differently".
How it works
An inventory holds three things.
| Element | What it is | Example |
|---|---|---|
| Host | A single target to connect to | web1 |
| Group | A collection of hosts | [web], [db] |
| Variable | A value attached to a host or group | ansible_port=2222, app_port=8080 |
The key point is that groups form a hierarchy. Under [prod:children], if you put web and db, then prod contains every host of both groups. And every host automatically belongs to the all group. Variables flow from top to bottom, but the narrower scope wins — a group's value takes precedence over the value in all, and a host's value takes precedence over its group's value.
There are two formats, INI and YAML. INI is short and easy to write by hand, while YAML is better at holding nested structures and complex variables. Whichever you use, dumping it with ansible-inventory --list produces the same JSON in the end. When you suspect the inventory, don't stare at the file. Look at the actual parsed result with this command. You can tell in 3 seconds that a single typo left a whole group empty.
What you see in the field
First, "why did only one machine fail". Some people temporarily delete a host from the inventory instead of using --limit to narrow the targets. Then that file gets committed as it is, and days later only that server is missing from patching. Narrow the targets with a run option, not by editing the inventory.
Second, connection details are variables too. ansible_host, ansible_port, and ansible_user look like special variables, but they are just variables. So environment differences such as a jump host or a non-standard port can be absorbed in the inventory without changing any code. In this lab environment too, sshd is running at 127.0.0.1:2222, so we handle that difference with ansible_port.
Third, dynamic inventory. In the cloud, the server list changes every day, so a script builds the list instead of a file. The concept is still the same — it only produces JSON that contains groups and variables. Once you understand static inventory properly, dynamic inventory is the same thing with a different output format.
Where to put variables
If you write lots of variables inside the inventory file, it quickly becomes hard to read. So in practice you move the variables into separate directories. If you place group_vars/ and host_vars/ next to the inventory, group_vars/web.yml applies to the whole web group and host_vars/web1.yml applies to that one host only. The file name is the scope, so you never wander around wondering where to edit.
As seen above, the precedence is that the narrower one wins. The actual order is longer than that, though, and summarizing only the parts you run into most often gives this:
-eon the command line (the strongest)- Values written directly on a task or block
host_vars/group_vars/(a child group beats its parent group)group_vars/all- A role's defaults (
defaults/, the weakest)
It is by design that a role's defaults are the weakest. A role is the place to write "if this value is absent, do this", and whoever uses the role must always be able to override it. By contrast, vars/ inside a role is much stronger and hard to override, so use it only for things that really must not change.
Here is one more practical rule. Never keep secrets in plain text in the inventory. Once they are committed to the repository, you cannot undo it, and even if you delete them, they remain in the history. Use encrypted variable files or fetch them from an external secret store, and either way it is better to keep non-secret values in separate files from secrets. If they are mixed in one file, you have to decrypt it every time just to change one ordinary setting, and before long nobody uses encryption at all.
What you will do in the next lab
In /root/ans/inventory/hosts.ini, create the web and db groups and a parent group prod, and express the same structure in YAML too. Then use an ad-hoc command to connect to all three hosts at once, and narrow the targets with --limit. Finally, dump the inventory as JSON and build a per-group host report.