Ansible Inventories

Let's fist create a file with the name ansible.cfg,

[defaults]
inventory = hosts

Next a file with the name hosts.ini,

[all]
centos1

In here, the ansible.cfg file is refering to an invenstory file with the name "hosts" and the hosts.ini file consist a host with the name "centos1". (In here it is assumed that a host with the name centos1 is reachable at this point)

Now first, I will remove the known hosts file from the ssh directory.

$ rm -rf /home/ansible/.ssh/known_hosts

Next when I try to ping to my host, it should asks me to verify the fingerprint, and I'm not going to accept it.

$ ansible all -m ping
The authenticity of host 'centos1 (172.18.0.8)' can't be established.
ECDSA key fingerprint is SHA256:gdRFM1dy+ntzCjU1mJi5oBS1k5enVlS/bPz6Wms59Ck.
Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C [ERROR]: User interrupted execution

To get around with this, I'm going to ping the hosts again with a varible set up.

$ ANSIBLE_HOST_KEY_CHECKING=False ansible all -m ping
centos1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

The variable ANSIBLE_HOST_KEY_CHECKING instructs ansible to ignore to fingerprint verification when it is set to False. But sometimes it could be repetitive to specify this variable when executing commands. This can also be configured in the ansible.cfg file.

[defaults]
inventory = hosts
host_key_checking = False

To make sure host's are not there, I'm going to remove the known hosts file,

$ rm -rf /home/ansible/.ssh/known_hosts

Then I'm going to ping our hosts again,

$ ansible all -m ping
centos1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

Now let me slightly modify our invenstory file hosts.ini like below,

[centos]
centos1
centos2
centos3

[ubuntu]
ubuntu1
ubuntu2
ubuntu3

Though we have grouped these under their os type, we can still use the all key word which will take all hosts into consideration.

$ ansible all -m ping -o
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

We can specify only a one type of os types like below,

$ ansible centos -m ping -o
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}

This even works with other types of commands such as listing,

$ ansible ubuntu --list-hosts
  hosts (3):
    ubuntu1
    ubuntu2
    ubuntu3

Well, this works with regular expressions as well (any number or charters and digits),

$ ansible ~.*3 --list-hosts
  hosts (2):
    centos3
    ubuntu3

Sometimes it is essential that we run our commands as the root user. In order to perform that, we can specify the ansible_user parameter in our inventory file.

[centos]
centos1 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1
ubuntu2
ubuntu3

To verify this, I'm going to use a different module called command with the parameter id like below,

$ ansible all -m command -a 'id' -o
ubuntu2 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)
ubuntu1 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)
centos3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu3 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)

Output of the above command shows us that connections to the centos has made as root while for ubuntu hosts it still uses the ansible user.

This brings us to another interesting scenario. What if we want to connect to our hosts as a normal user, but needs to perform some tasks with escalated user? in that case we can use ansible_become_true parameter set to true and ansible_become_pass set to the password. For example, let's modify our inventory file like below,

[centos]
centos1 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password

Next we can execute the same command to see the user being used,

$ ansible all -m command -a 'id' -o
centos2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)

Ansible by default assumes the ssh port is 22. But there could be situations where the ssh port is different. In that case we can specify the ssh port in one of two ways mentioned in below,

[centos]
centos1 ansible_user=root ansible_port=2222
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password

or

[centos]
centos1:2222 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password

And if we ping again, we might be able to connect to the hosts again,

$ ansible all -m ping -o
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

We can also specify a control gorup where it will not use and ssh connection.

[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_user=root ansible_port=2222
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password

It is also possible to specify ranges in the inventory files.

[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_user=root ansible_port=2222
centos[2:3] ansible_user=root

[ubuntu]
ubuntu[1:3] ansible_become=true ansible_become_pass=password

Let's check all the hosts to verify this,

$ ansible all --list-hosts
  hosts (7):
    ubuntu-c
    centos1
    centos2
    centos3
    ubuntu1
    ubuntu2
    ubuntu3

But still our inventory file has duplicates. For example we specify the ansible user for all groups. This can be addressed using group vars. These group vards will be fed into each record during the execution.

[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

Let's ping again and see if this works,

$ ansible all -m ping -o
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

We can futher group things using a parent:child relationship like below,

[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

[linux:children]
centos
ubuntu

We can now call everything falling under linux group and see if it still working,

$ ansible linux -m ping -o
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

Now let's motify the inventory like below,

[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

[linux:children]
centos
ubuntu

[all:vars]
ansible_port=1234

You can notice that I've specified variables section for all where I define the ssh port as 1234. This indeed is a wrong port. However, these variables have a precedence effect. Since I have specified the correct port for centos1 host along with it, it will work fine, but others might fail.

$ ansible all -m ping -o
centos2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos2 port 1234: Connection refused
centos3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos3 port 1234: Connection refused
ubuntu1 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu1 port 1234: Connection refused
ubuntu2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu2 port 1234: Connection refused
ubuntu3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu3 port 1234: Connection refused
ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}

It is also possible to write the inventory files in YAML format. But in that case we have to explicitly specify the inveontory file in the ansible.cfg file,

[defaults]
inventory = hosts.yaml
host_key_checking = False

Then we can declare our inventory file in YAML format like below,

---
control:
  hosts:
    ubuntu-c:
      ansible_connection: local
centos:
  hosts:
    centos1:
      ansible_port: 2222
    centos2:
    centos3:
  vars:
    ansible_user: root
ubuntu:
  hosts:
    ubuntu1:
    ubuntu2:
    ubuntu3:
  vars:
    ansible_become: true
    ansible_become_pass: password
linux:
  children:
    centos:
    ubuntu:
...

Similarly it is also possible to specify the the inventory file in JSON format,

{
    "control": {
        "hosts": {
            "ubuntu-c": {
                "ansible_connection": "local"
            }
        }
    },
    "centos": {
        "hosts": {
            "centos1": {
                "ansible_port": 2222
            },
            "centos2": null,
            "centos3": null
        },
        "vars": {
            "ansible_user": "root"
        }
    },
    "ubuntu": {
        "hosts": {
            "ubuntu1": null,
            "ubuntu2": null,
            "ubuntu3": null
        },
        "vars": {
            "ansible_become": true,
            "ansible_become_pass": "password"
        }
    },
    "linux": {
        "children": {
            "centos": null,
            "ubuntu": null
        }
    }
}

When using a differnt inventory file formats or multiple file, you can specify the inventory using the i flag in the command line,

$ ansible all -i hosts.yaml --list-hosts
  hosts (7):
    ubuntu-c
    centos1
    centos2
    centos3
    ubuntu1
    ubuntu2
    ubuntu3

Last updated