Red Hat Certified Engineer (RHCE) #2 Inventory: static, dynamic, group/host_vars
In #1 we pinned down RHCE (EX294) as a hands-on Ansible automation exam. Before you write a playbook, there’s one question to answer first: which hosts is Ansible actually operating on? The file that answers it is the inventory. The inventory is the list of managed nodes that the control node will manage; it declares which hosts go into which groups and how to connect to each host.
If you can’t get the inventory right, every playbook after it will fail to find its targets. So this post covers it all at once — the two formats of static inventory, groups and groups of groups, the group_vars and host_vars directories that split out variables, and the commands that verify your inventory.
What is an inventory #
An inventory is the list of target hosts Ansible will perform work on. The simplest form is a text file with one hostname or IP address per line. From there you group hosts together, assign variables per group, and add connection details — and that builds up into an inventory you’d use in practice.
Inventories come in two broad kinds.
- static inventory. An inventory where a person has written the hosts into a file directly. It suits fixed environments that don’t change, and the inventories you write on the RHCE exam are almost all of this kind.
- dynamic inventory. An inventory where a script or plugin queries an external system (a cloud API, a CMDB, and so on) and generates the host list at run time. It suits environments where hosts change frequently.
The exam centers on static inventory, so we’ll lock that down first.
static inventory: INI format #
The most traditional inventory format is the INI format. You declare a group name in square brackets and list the hosts one per line beneath it.
# inventory
# hosts listed without a group automatically belong to the ungrouped group
bastion.example.com
[web]
web1.example.com
web2.example.com
[db]
db1.example.com
[lb]
lb1.example.comA host written at the top of the file without being placed in any bracketed group falls into an implicit group called ungrouped. And every host in the inventory automatically belongs to a group called all as well. In other words, all and ungrouped are groups that always exist even though you never created them.
Host range notation #
If hostnames run in a regular sequence of numbers or letters, you can shorten them with range notation. Connect the start and end with a colon inside square brackets.
[web]
web[1:3].example.com
[db]
db[a:c].example.comweb[1:3].example.com means web1.example.com, web2.example.com, and web3.example.com. Letter ranges like [a:c] work too, not just numbers. This is the notation that keeps an inventory short and clear when there are many hosts, so it’s worth getting familiar with.
Groups of groups: children #
You can group groups again to make a parent group. In the INI format you list the child group names under a [groupname:children] header.
[web]
web[1:3].example.com
[db]
db1.example.com
# a parent group combining web and db
[production:children]
web
dbWith this, targeting the production group selects every host in both web and db together. It’s a useful structure for layering hosts by environment (production, staging) or by role.
Inline variables in the INI format #
In the INI format you can also attach variables right after the host line.
[web]
web1.example.com ansible_host=192.168.10.11 ansible_user=devops
web2.example.com ansible_host=192.168.10.12 ansible_user=devopsThat said, attaching a long string of variables to the host line hurts readability. The recommended approach is to split variables out into group_vars and host_vars, which we’ll cover later.
static inventory: YAML format #
You can write the same inventory in YAML format too. The structure is explicit, so the group hierarchy and the variables are visible at a glance.
# inventory.yml
all:
children:
web:
hosts:
web1.example.com:
web2.example.com:
web3.example.com:
db:
hosts:
db1.example.com:
production:
children:
web:
db:In the YAML format you declare groups with children under all, and put hosts under each group’s hosts. A group of groups is expressed by placing another children inside a group. The INI [production:children] corresponds here to a children under production. In the YAML format you can also assign variables directly under a group’s vars or beneath a host. Whether you use INI or YAML, the behavior is identical. On the exam you can use whichever format you’re comfortable with, but you need to be able to read both formats to interpret an inventory that’s been handed to you.
Connection variables: how to connect to a host #
Often a hostname alone isn’t enough to connect. The connection address may differ, the login account may differ, or the port may be non-standard. The special variables you use here are connection variables.
| Variable | Meaning |
|---|---|
ansible_host | The actual address or IP to connect to (may differ from the inventory name) |
ansible_user | The SSH login account |
ansible_port | The SSH port (specify when it isn’t the default 22) |
ansible_connection | The connection method (ssh by default, local for local) |
ansible_become | Whether to escalate privileges (true/false) |
When the name written in the inventory differs from the real connection details, you reconcile them with these variables. Connection variables are also cleaner to manage when organized into group_vars and host_vars rather than scattered across host lines.
Splitting variables with group_vars and host_vars #
You can write variables directly into the inventory file, but as the number of variables grows the file gets complicated. Ansible provides a convention: if you place certain directories next to the inventory, it reads the variables automatically. These are group_vars and host_vars.
group_vars/<groupname>.yml. Variables that apply to every host in that group.host_vars/<hostname>.yml. Variables that apply to one specific host only.
The directory structure looks like this.
project/
├── ansible.cfg
├── inventory
├── group_vars/
│ ├── all.yml
│ ├── web.yml
│ └── db.yml
├── host_vars/
│ ├── web1.example.com.yml
│ └── db1.example.com.yml
└── site.ymlgroup_vars/all.yml holds variables that apply to every host in common. group_vars/web.yml applies only to the web group, and host_vars/web1.example.com.yml applies only to the single host web1.example.com. The filename has to match the group name or hostname exactly for it to be read automatically.
group_vars example #
Put variables for the entire web group in group_vars/web.yml.
# group_vars/web.yml
http_port: 80
max_clients: 200
app_user: webadmin
firewall_service: httpPut variables common to all hosts in group_vars/all.yml.
# group_vars/all.yml
ansible_user: devops
ansible_port: 22
ntp_server: time.example.comhost_vars example #
Values needed for one specific host go in host_vars. For example, if only web1 has a different connection address and port, write it like this.
# host_vars/web1.example.com.yml
ansible_host: 192.168.10.11
ansible_port: 2222
server_id: 1Split this way, the inventory file holds only the host and group structure, and each file takes responsibility for its variables. When the exam gives you a task like “assign this variable to this group only,” it’s almost always solved with group_vars.
Verifying the inventory #
After writing an inventory, you must verify that Ansible parsed it as intended. It’s common for the structure in your head to differ from the actual parse result. The commands you use to verify are as follows.
ansible-inventory –list #
Prints the entire structure of the inventory and the variables applied to each host as JSON.
ansible-inventory -i inventory --listThis command merges in group_vars and host_vars and shows the final set of variables each host ends up with, so it’s the best way to confirm that variables are attached to the hosts you intended.
ansible-inventory –graph #
Prints the group-and-host hierarchy as a tree.
ansible-inventory -i inventory --graphA sample of the output is as follows.
@all:
|--@ungrouped:
|--@production:
| |--@web:
| | |--web1.example.com
| | |--web2.example.com
| | |--web3.example.com
| |--@db:
| | |--db1.example.comYou can see at a glance whether the groups of groups (children) are nested as intended. Adding --vars after --graph shows each node’s variables alongside it.
ansible all –list-hosts #
Quickly confirms which hosts a given pattern expands to.
ansible web -i inventory --list-hosts
ansible production -i inventory --list-hosts
ansible all -i inventory --list-hostsIt shows which hosts actually get selected when you specify the web group or a parent group like production. It’s a safeguard for checking the targets are right before you run a playbook.
The concept of dynamic inventory #
In environments where hosts change often, an inventory is hard to maintain by hand. Somewhere like the cloud, where instances are created and deleted constantly, you need a dynamic inventory that fetches the current host list at run time.
Dynamic inventory is implemented in two ways.
- inventory script. An executable that, when run, prints the host list in a set JSON format. It can be written in any language as long as the format matches.
- inventory plugin. The current recommended approach. You point at an external source with a YAML configuration file, and the plugin fetches the hosts. For example, the
amazon.aws.aws_ec2plugin turns AWS EC2 instances into an inventory.
An example of a plugin-style configuration file is as follows.
# aws_ec2.yml (inventory plugin)
plugin: amazon.aws.aws_ec2
regions:
- ap-northeast-2
keyed_groups:
- key: tags.Role
prefix: rolePointing at this file with -i aws_ec2.yml makes Ansible query the AWS API at run time to build the host list and automatically create groups from the EC2 Role tag value. Tasks that ask you to write a dynamic inventory yourself are a small part of the RHCE exam, but it’s worth keeping the difference between static and dynamic — and the concept — straight.
Recurring exam task patterns #
The inventory-related tasks that show up repeatedly on the RHCE exam are standardized.
- Defining specific groups. Grouping given hosts under specified group names, and combining several groups again into a parent group (children).
- Splitting variables with group_vars. Putting variables that apply to a specific group only in
group_vars/<group>.ymlwithout dirtying the inventory file. - Handling exceptions with host_vars. Using
host_vars/<host>.ymlwhen just one host needs a different value. - Setting connection variables. Reconciling connection details with
ansible_host,ansible_user, andansible_port.
Here’s a combined example that satisfies all of the above at once. It pairs an INI inventory with group_vars.
# inventory
[web]
web1.example.com
web2.example.com
[db]
db1.example.com
[datacenter:children]
web
db# group_vars/web.yml
http_port: 80
firewall_service: http
app_root: /var/www/app# group_vars/all.yml
ansible_user: devops
timezone: Asia/SeoulChecking this setup with ansible-inventory -i inventory --graph --vars, you can see web and db nested under datacenter and the group_vars variables correctly applied to each host.
Exam points #
- Every host in the inventory automatically belongs to the
allgroup, and a host with no group belongs to theungroupedgroup. - A group of groups is expressed as
[group:children]in the INI format and as achildrenunder the group in the YAML format. - The range notation
web[1:3]expands toweb1〜web3. Both numbers and letters work. - Rather than writing variables directly into the inventory, split them into
group_vars/<group>.ymlandhost_vars/<host>.yml. The filename has to match the group name or hostname exactly for it to be read automatically. - Connection is controlled with
ansible_host,ansible_user, andansible_port. - After writing, always verify with
ansible-inventory --list,ansible-inventory --graph, andansible <pattern> --list-hosts. - Dynamic inventory fetches hosts from an external source via a script or a plugin, and suits cloud environments.
Wrap-up #
What this post locked in:
- The inventory is the starting point that defines the hosts Ansible operates on. It splits into static and dynamic, and the exam centers on static.
- Static inventory is written in two formats, INI and YAML, and supports hosts, groups, groups of groups (children), and range notation (
web[1:3]). - Variables are split into the
group_varsandhost_varsdirectories, and connection is controlled with connection variables (ansible_host/ansible_user/ansible_port). - You verify the inventory with
ansible-inventory --listand--graph, andansible all --list-hosts. - Dynamic inventory fetches hosts from external sources like the cloud at run time via scripts and plugins.
Next — Configuration files and connection #
We’ve now defined which hosts Ansible operates on with the inventory. So where does Ansible find that inventory, how does it connect to the hosts, and how does it escalate privileges?
In #3 Configuration files and connection: ansible.cfg, ssh, become, we’ll work through the precedence of ansible.cfg and its key settings, configuring SSH key-based access, and how to escalate privileges with become — building each piece by hand.