This guide will show you how to work with the inventory file, variables, and facts in Ansible. Understanding these concepts in Ansible is critical. Generally, Ansible uses the information provided in the inventory and variables to discover information about your remote systems. And, the information gathered is then stored as facts.
Let’s discuss each of these items in more detail and learn how to use them.
Table of Contents
Inventory file
The inventory file in Ansible stores information about physical and virtual servers, network devices, cloud instances, and much more. In fact, any device that provides SSH access can be added to the inventory file.
Ansible requires SSH to execute commands against remote hosts. In addition, Ansible must provide the username and password to the remote host to access it. Finally, you can use the Inventory file to set privilege escalation and more.
Ansible then reads the inventory file by default in the location /etc/ansible/hosts. However, you can specify a different inventory file using the -i <path> option on the command line.
As you can imagine, the Inventory can get quite complex. Thus, it is important to understand what tools and syntax features are available in Ansible to simplify this complexity and order all the managed nodes correctly.
Basic Inventory file
The most common format for an Ansible inventory file is INI or YAML.
The example inventory file below is an INI file stored in /etc/ansible/hosts:
email.domain.com
[frontend]
foo.domain.com
bar.domain.com
[backend]
192.168.10.10
192.168.10.20
192.168.10.30
As you can see, there are two named groups in this file. You can use “frontend” or “backend” references in your playbook to scope your nodes.
The following example is a YAML inventory file:
all:
hosts:
email.domain.com:
children:
webservers:
hosts:
host1.example.com:
host2.example.com:
dbservers:
hosts:
serv1.example.com:
serv2.example.com:
serv3.example.com:
We use “webservers” or “dbservers” references in your playbook to scope your nodes in this example.
As mentioned before, you can also set a custom inventory file at the command line using the -i <path> option.
The example below shows a file called company-inventory:
[webservers]
server-[a:d].domain.com
[dbservers]
192.168.30.[20:50]
In this example, we use ranges to specify a pool of nodes without listing every node. Thus, it works for both the letter and the numbers.
Next, we can view our inventory using the following command:
ansible-inventory -i company-inventory --list
Also, you can execute your playbook against a specific inventory file by adding the flag -i.
ansible-playbook -i company-inventory playbook.yml
Lastly, grouping nodes can be a powerful tool. The best way to grasp it is to look at the following example:
[routers]
10.10.10.1
10.10.10.2
[switches]
172.16.1.1
172.16.1.2
[firewalls]
192.168.1.1
[network:children]
routers
switches
firewalls
[security:children]
routers
firewalls
[lan:children]
routers
switches
Consequently, we grouped smaller groups into larger groups based on their purpose. Additionally, this concept allows you to have a nested structure of your Ansible Inventory file and provides more flexibility.
Ansible YAML inventory file examples
The first example is a YAML inventory file with four hosts, grouped under “all” and one host with two variables:
all:
hosts:
srv1.example.com:
ansible_ssh_host: 191.168.1.2
myvariable: value
srv2.example.com:
192.168.1.8:
192.168.1.10:
The second example is a YAML inventory file containing hosts belonging to the ‘webservers’ group, with shared group var:
webservers:
hosts:
srv1.example.com:
srv2.example.com:
192.168.1.7:
192.168.1.11:
vars:
http_port: 8080
Finally, the third example is a YAML inventory file with hosts using ranges and children groups:
webservers:
hosts:
srv1.example.com:
srv2.example.com:
testing:
hosts:
tst[001:006].example.com:
vars:
myvar: value1
children:
webservers:
other:
children:
webservers:
mark1.example.com
Ansible INI inventory file examples
The first example is an INI inventory file with simple grouping:
[fooland]
foo1
foo2
[barland]
bar1
bar2
The second example of an Ansible inventory INI file is more complex with grouping and variables:
# fmt: ini
# Example 1
[web]
host1
host2 ansible_port=222 # defined inline, interpreted as an integer
[web:vars]
http_port=8080 # all members of 'web' will inherit these
myvar=23 # defined in a :vars section, interpreted as a string
[web:children] # child groups will automatically add their hosts to parent group
apache
nginx
[apache]
tomcat1
tomcat2 myvar=34 # host specific vars override group vars
tomcat3 mysecret="'03#pa33w0rd'" # proper quoting to prevent value changes
[nginx]
jenkins1
[nginx:vars]
has_java = True # vars in child groups override same in parent
[all:vars]
has_java = False # 'all' is 'top' parent
# Example 2
host1 # this is 'ungrouped'
# both hosts have same IP but diff ports, also 'ungrouped'
host2 ansible_host=127.0.0.1 ansible_port=44
host3 ansible_host=127.0.0.1 ansible_port=45
[g1]
host4
[g2]
host4 # same host as above, but member of 2 groups, will inherit vars from both
# inventory hostnames are unique
Source: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ini_inventory.html.
Variables
Variables are the principal idea in any programming language or automation flow. The simplest way to define variables is to put a vars section in your playbook with the names and values.
Using the NGINX example, we can set the following parameters as variables:
vars:
key_file: /etc/nginx/ssl/nginx.key
port: 443
server_name: localhost
It is handy to group variables into files to separate them from the body of the playbook. However, variables lists can be exceptionally long. Therefore, it is a best practice to keep them in the dedicated files.
Var files are also YAML files; let’s create an nginx-vars.yml file:
key_file: /etc/nginx/ssl/nginx.key
port: 443
server_name: localhost
Consequently, you refer to this file in the playbook, and we can use this statement:
vars_files:
- nginx-vars.yml
When we run the playbooks, we often want to print the value of the variable, so we should use the following syntax:
- debug: var=varname
Facts
There is one specific type of variable that is called Ansible facts. For example, you might have noticed the following output at the top before your tasks when running your Ansible playbook.
GATHERING FACTS **************************************************
ok: [name]
When Ansible gathers facts, it connects to the node and collects various info about the host such as CPU, memory, IP address, OS, etc. This information is stored in variables called facts, and they behave just like any other variable.
The example below will gather all the facts for localhost:
- hosts: localhost
tasks:
- debug:
var: ansible_facts
Result:
PLAY [localhost] ************************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ****************************************************************************************************************************************************************************
ok: [localhost] => {
"ansible_facts": {
"_facts_gathered": true,
"all_ipv4_addresses": [
"172.39.0.1",
],
"all_ipv6_addresses": [
PLAY [localhost] ************************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ****************************************************************************************************************************************************************************
ok: [localhost] => {
"ansible_facts": {
"architecture": "x86_64",
"bios_date": "07/11/2011",
"bios_version": "A06",
...
Example to display OS distribution fact
For example, we can write this Playbook and print out the facts to find the Linux build.
- name: Display OS distribution
hosts: all
gather_facts: True
tasks:
- debug: var=ansible_distribution
The output will therefore show the facts that we are looking for:
TASK: [debug var=ansible_distribution]******************************
ok: [srv1] => {
"ansible_distribution": "SUSE"
}
ok: [server2] => {
"ansible_distribution": "Debian"
}
What are Ansible facts used for?
Well, as you know, package names differ among distributions. So, for example, the Apache webserver package is httpd for some distributions, while for other distributions, it’s named apache2.
Consequently, Ansible facts allow you, amongst others, to make your playbook distribution agnostic. For example, the script below will collect Ansible facts and then perform the required task accordingly. When installing on non-Debian systems, it automatically skips the Debian-based task and vice versa.
- hosts: all
tasks:
- package:
name: "httpd"
state: present
when ansible_facts["os_name"] == "non-debian"
- package:
name: "apache2"
state: present
when ansible_facts["os_name"] == "Debian"
Frequently asked question(s)
Ansible has a module called: gather_facts. This module runs by default at the start of each playbook to gather the facts about remote hosts.
Wrapping up
In conclusion, we looked at three important concepts about the Ansible inventory file, variables, and facts. Firstly, these tools make your playbooks reusable and applicable in many scenarios and infrastructure. Lastly, using them will help you write abstract automation flows, which will work in some infrastructures.