Managing Hosts With Ansible

Ansible zero to hero, Part 2

#Ansible #Full course #Getting started

When we use ansible we often need to manage remote hosts, those hosts can be bare-metals, VMs, instances running on different clouds and so on. Before we start writing playbooks and tasks (will be covered later) we need to understand what infrastructure we have and how should we manage it, this will have an affect on how our playbooks will be structured. In this part we will talk about the ansible inventory, which is a file that lists the hosts we manage with the ansible playbook, learn how to define host specific variables and cover some configurations that are relevant to the way we talk to our hosts.

Ansible Inventory

The ansible inventory is mostly a list of hosts that we want to manage with ansible. It allows us to easily group a number of hosts under the same name, and define variables for each host/group.

In ansible there are 2 types of inventories:

  • static: a regular file that can be written in INI or YAML format. This is the most common and simple inventory and will be the main focus of this blog.
  • dynamic: an executable script that returns a generated static inventory that reflects the current state of the infrastructure. This is a more advanced type of inventory which will be covered briefly.

Inventory Structure

The simplest inventory is just a file listing the hosts. For example this is a simple inventory that lists 5 hosts with their FQDN:

host1.example.gzaidman.com  # dev host
host2.example.gzaidman.com  # test
host3.example.gzaidman.com  # test
host4.example.gzaidman.com  # prod
host5.example.gzaidman.com  # prod

** We could also use plain IP addresses, but as a best practice it is recommended to use FQDN or or IP addresses with aliases.

The above could be shortened with ranges:

host1.example.gzaidman.com      # dev host
host[2:3].example.gzaidman.com  # test
host[4:5].example.gzaidman.com  # prod

When we run an ansible playbook with the above inventory, ansible will try to perform each task in the playbook on all the hosts in the inventory.

Having a simple inventory with 5 hosts is not elegant but is manageable, but what if we have 50? 100? 1000?? as our infrastructure grows bigger and the system becomes more complex it is clear that we need a better way to organize our hosts, that’s where inventory groups come into play.

Ansible inventory groups are used to assign a label to a collection of hosts. For example, we can use groups in the above example:

[dev]
host1.example.gzaidman.com

[test]
host[2:3].example.gzaidman.com

[prod]
host[4:5].example.gzaidman.com

a single host can be members of multiple groups and a group can contain child groups with the :children syntax. Let’s say we want to also group the hosts by their location:

[dev]
host1.example.gzaidman.com

[test]
host[2:3].example.gzaidman.com

[prod]
host[4:5].example.gzaidman.com

[england]
host1.example.gzaidman.com
host3.example.gzaidman.com

[finland]
host2.example.gzaidman.com
host4.example.gzaidman.com

[us]
host5.example.gzaidman.com

[europe:children]
england
finland

In ansible there are two default groups that are always created:

  • all: contains every host in the inventory.
  • ungrouped: contains all hosts that don’t have another group aside from all.

To verify that the inventory is ok, we can use:

# ansible HOST/GROUP --list-hosts
ansible canada --list-hosts

Adding variables to managed hosts

Very often we will want to define variables that are specific to a host or a group of hosts.

We can add the variables directly on each host

[test]
host1.example.gzaidman.com listen_port=4321
host2.example.gzaidman.com listen_port=8642

We can also add variables for a group:

[test:vars]
ntp_server=ntp.canada.example.com
ntp_port=4567
proxy=proxy.canada.example.com

Those variables are called host and group variables and they will be available in the playbook on the host. Host and group variables are very common but defining them directly on the inventory file can make it very verbose and hard to read especially when we have a large inventory file. Ansible lets us organize host and group variables on separate files which will make our inventory cleaner and our project more maintainable. We need to create host_vars and group_vars directories, those directories can contain a file with the name of the host/group where we will define all the variables. For example we can take the inventory above, and create:

[gzaidman:example] tree
.
├── group_vars
│   └── test
├── host_vars
│   ├── host1.example.gzaidman.com
│   └── host2.example.gzaidman.com
└── inventory

The variable files should be in yaml syntax only which is a bit different from the above.

[gzaidman:example] cat test
ntp_server: ntp.canada.example.com
ntp_port: 4567
proxy: proxy.canada.example.com

[gzaidman:example] cat host1.example.gzaidman.com
listen_port: 4321

[gzaidman:example] cat host2.example.gzaidman.com
listen_port: 8642

You can also create directories named after your groups or hosts and place variable files in them. Ansible will read all the files in these directories in lexicographical order. An example with the ‘test’ group:

./example/group_vars/test/ntp_settings ./example/group_vars/test/proxy_settings

This can be very useful to keep your variables organized when a single file gets too big, or when you want to use Ansible Vault (covered later) on some group variables.

Inventory Location:

The inventory can be in the following locations (ordered by precedence):

  1. parameter: ansible/ansible-playbook –inventory PATHNAME or -i PATHNAME.
  2. Ansible configuration: with the inventory=INVENTORY_PATH.
  3. default location: /etc/ansible/host system’s default static inventory file.

Configurations

There are a lot of flags we can set to control the way ansible connects to our hosts. We define does as inventory variables for a specific host/group. For the full list of configurations visit the Docs.

Some of the most common ones are:

  • ansible_connection: The why ansible will try to connect to the host. Bt default ansible executes playbooks over SSH with a connection type called smart, but there are other connection types:
    • SSH Based: paramiko, ssh
    • Non-SSH based: local, docker
  • ansible_host: The name of the host to connect to, if different from the alias you wish to give to it.
  • ansible_port: The connection port number, if not the default (22 for ssh).

Examples

To see examples on different inventories go to the docs on:

https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#inventory-setup-examples

Dynamic Inventory

When working with numerous machines or in an environment where machines come and go very quickly, it can be hard to keep the static inventory files up-to-date. Ansible supports dynamic inventory scripts that retrieve current information from external sources, allowing the inventory to be created in real time based on the current env. These scripts collect information from an external source and create JSON format inventory files.

If the inventory file is executable, then it is treated as a dynamic inventory program and Ansible attempts to run it to generate the inventory. If the file is not executable, then it is treated as a static inventory.

If a dynamic inventory script does not exist for the infrastructure in use, you can write a custom dynamic inventory program. This is a link to the ansible doc for writing a dynamic inventory link. A few things we should keep in mind:

  • Can be written in any programming language, but must have an appropriate interpreter line (for example, #!/usr/bin/python).
  • The script should return inventory information in JSON format.
  • When passed the –list option, the script must print a JSON-encoded hash/dictionary of all the hosts and groups in the inventory.
  • When passed the –host HOST option, the script must print a JSON hash/dictionary consisting of variables that are associated with that host, or an empty JSON hash/dictionary.

Configuring Ansible

On each ansible project it is recommended to create a configuration file to override the default configurations based on the project requirements. Ansible configuration file is located at /etc/ansible/ansible.cfg, to override some settings we need to create a project configuration file at the project root directory with the same name ansible.cfg. The stock configuration should be sufficient for most project, and you can examine the /etc/ansible/ansible.cfg file to see the default configuration you can override.

The Ansible configuration file consists of several sections, each section is enclosed with ‘[SECTION_NAME]’.

We will talk about two sections that relate to the way ansible connects to hosts.

Some configurations which relate to host management (they can als be overridden at the playbook level):

[defaults]

This is the first section in the file and it sets defaults for Ansible operation.

  • inventory - Specifies the path to the inventory file.
  • remote_user - specify a different remote user, by default it will try to log in with the user which ran the ansible command via SSH.
  • ask_pass - Whether or not to prompt for an SSH password. Can be false if using SSH public key authentication.

[privilege_escalation]

Configures how Ansible performs privilege escalation on managed hosts

  • become - Whether to automatically switch user on the managed host after connecting. This can also be specified by a play.
  • become_method - How to switch user (default is sudo).
  • become_user - The user to switch to on the managed host (default is root).
  • become_ask_pass - Whether to prompt for a password for your become_method. Defaults to false.

We can see all the configuration options with the ansible-config list command.

** It’s important to understand that the above options on the default section refer to the initial connections meaning how to connect to the host and the options on the privilege_escalation refer to what to do once you are connected.

Configuration File Precedence

  1. ANSIBLE_CONFIG environment variable
  2. ./ansible.cfg
  3. ~/ansible.cfg
  4. /etc/ansible/ansible.cfg

The recommended practice is to create an ansible.cfg file in a directory from which you run Ansible commands (meaning option 2). We cab check which configuration file is being used with ansible –version.