This page looks best with JavaScript enabled

[My Ansible Journey] Basics and first Playbook

 ·  ☕ 10 min read  ·  🦆 Jeremy

Part 1 : Presentation and prerequisites
Part 2 : Basics and first Playbook
Part 3 : Variables
Part 4 : Collections
––

Second part of the Ansible article series. The objective is still the same, to increase our skill on the tool based on use cases. Like the first part, it will be a mix of good practices but also of my own experience feedback. In this article, we come back to the basics of the product as well as the realization of a first Playbook. We will start the deployment of a Stack TIG (Telegraf, InfluxDB, Grafana) for the supervision of a VMware environment (vCenter + ESXi), it will be our common thread 🙂

Little reminder, the prerequisites are here.

The code for the following examples is on GitHub

Introduction and Ansible objectives

I have already writtend a quick introduction of Ansible in the first part, here we are gonna talk about use cases before giving more theoretical explanations.

Ansible was originally created to deal with configuration management on Linux server. It is agentless, so it relies on SSH to interact with the nodes (servers which will be managed by Ansible). Once connected to the nodes, Python code is executed. This is where I see the strengths of the product:

  • Agentless thanks to SSH
  • SSH allows remote control of network and security devices
  • Python, which is included in most systems nowadays, based code is executed on nodes

Windows nodes are managed with PowerShell, and any kind of application can be configured as long as they are accessible through API (like Rest)
If you have all the steps described in the prerequisites article, you should have a Ansible ready environment by now. It is really easy to execute Playbook:

1
ansible-playbook playbook.yml

Use cases

One of the most common use case is the deployment and the configuration of a software stack. But there are many others:

  • Deploying automatically network devices
  • Creation and configuration of virtual machines
  • ESXi server configuration
  • Have a Playbook ready to re-deploy a complete application.

Components

  • Control Node: Where Ansible is installed
  • Managed Nodes Remotely controlled equipment. There are listed in the inventory file
  • Tower Centralized web console. AWX is the Tower upstream
  • Playbook: The file where the desired configuration is written
  • Modules: The actual code which is executed on the nodes

We will come back to the Playbook and Modules later in the article.

Required files

To begin, we will see both mandatory and recommended files to make our Playbook works. But it is important to note there are more of them that can be useful under certain circumstances.

Playbook

In the Playbook we have to described the desired configuration. Unlike a script, here we don’t have to write test before taking an action, those tests are already present into the modules we are using, it is call idempotency.
Playbook must be written in YAML, here is an explanation regarding the syntax: https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html

Inventory

The Inventory file is where we are listing our nodes we want to manage. There is a default file here: /etc/ansible/hosts.

The inventory can be in tow different format:

  • ini
  • YAML

ini format

Most of the time we will see the inventory as a ini file, but the YAML format is also interesting.

In our example where you are setting-up a TIG stack, here is an example of the Inventory file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[tig]
influxdb ansible_host=192.168.1.10
telegraf ansible_host=192.168.1.11
grafana ansible_host=192.168.1.12

[webservers]
nginx01.domain.local
nginx02.domain.local

dns01.domain.local

If we look more closely:

  • [tig] Is the group name, it composed by the 3 following items (lines). The group can be used in the Playbook, in that case the Playbook will apply the configuration on all the members.
  • influxdb is the name we chose to give to the node. It can also be used in the Playbook/
  • ansible_host is a variable associated to the host, each variable is space separated, then we set the value with =. In our case, I specify the IP address as the host name is unresolvable.

I have add a second group (which will not be used) just to show we can create as many as we want/need. Here we can see we do not need to set the IP address if there is a DNS resolution.

At last, just a host, without any group.

For the demo, we will install the 3 software on the same server, so we will just set one line.

YAML format

Here the equivalent in YAML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
all:
  hosts:
  children:
    tig:
      hosts:
        influxdb:
            ansible_host: 192.168.1.10
        telegraf:
            ansible_host: 192.168.1.11
        grafana:
            ansible_host: 192.168.1.12
    webservers:
      hosts:
        nginx01.domain.local:
        nginx02.domain.local:
    dns01.domain.local:

I think it is clear enough to understand how it works. You can see several key words like all, host and children. Important point, all is implicitly present in the inventory.ini file, it matches all host in the inventory.

Dynamic inventory

I won’t get into too much detail about dynamic inventory as it is not the subject here. But I would like you to know it is very convenient for an inventory to be managed by an external data source. Here is two examples:

  • A vCenter server allows you to have a list of VM in your inventory
  • An IPAM which acts as a Source of Truth, like NetBox

I invite you to have a look on the official documentation to get deeper about inventory: https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html

Interactive mode

The Ansible main goal is to run Playbook, but you can also run it into an interactive mode, to help you debugging for example. If we want to know if a host is accessible, we can try:

1
ansible grafana --inventoy inventory.ini --user jlg -m ping

We try to connect to host grafana, which is defined in the inventory file with th user jlg. On this host, we execute the module ping (which is not an actual ICMP ping, it is a Python code running on the host).

Expected result:

1
2
3
4
5
6
7
grafana | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

We get the documentation of each module with the following command:

1
ansible-doc ping

Or directly from the Ansible website: https://docs.ansible.com/ansible/latest/modules/modules_by_category.html

An other example is the module command which can be use to run any sort of command on the host. The command itself is set as an argument of the module, using -a. As it is the default module, you don’t have to specify it:

1
ansible grafana --inventory inventory.ini --user jlg -a hostname
1
2
grafana | CHANGED | rc=0 >>
tig

In my opinion, the most useful functionality about using the interactive mode is the ability to display facts. I will come back to the facts and variables with Ansible in the next article. What is important here is Ansible’s ability to discover information about the managed node and record it in variables. For example:

  • Hardware information, CPU, memory, disk, network etc…
  • System information such as: IP address, OS name and version…
  • Many other things

The best being still to test it, for that one executes the module setup, without argument:

1
ansible grafana --inventoy inventory.ini --user jlg -m setup

There is very long result, here is one part:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
grafana | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.x.x"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::20c:xxxx:xxxx:xxxx"
        ],
        "ansible_apparmor": {
            "status": "enabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "02/27/2020",
        "ansible_bios_version": "6.00",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-5.4.0-42-generic",
            "maybe-ubiquity": true,
            "ro": true,
            "root": "UUID=4ac4c187-b2ed-49ff-aa93-5b4ac3f13156b"
        },

We can also apply a filter in order to limit the amount of information displayed. Here is an example for mount points:

1
ansible grafana --inventory inventory.ini --user jlg -m setup -a 'filter=ansible_mounts'

All this information can be reused later in the Playbook, for example if you want to check an OS version before running a task.

Playbook composition

Now we get to the Playbook, we’ll keep it simple here too, the goal being to introduce the concepts.

Here is the Playboo we will run:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
---
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: true
  tasks:
    - name: Set timezone to Europe/Paris
      timezone:
        name: Europe/Paris
    
    - name: Local packages should be up-to-date
      apt:
        pkg: "*"
        state: latest

    - name: Add InfluxData Apt key
      apt_key:
        url: https://repos.influxdata.com/influxdb.key
        state: present

    - name: Add stable InfluxData Apt repository
      apt_repository:
        repo: "deb https://repos.influxdata.com/{{ ansible_facts['distribution']|lower }} {{ ansible_facts['distribution_release'] }} stable"
        state: present

- name: InfluxDB
  hosts: tig
  remote_user: jlg
  become: true
  gather_facts: false
  tasks:
    - name: Install InfluxDB
      apt:
        update_cache: yes
        pkg: influxdb

    - name: Service started and enabled
      service:
        name: influxdb
        enabled: yes
        state: started

Even if a Playbook has the merit of being understandable, it deserves some explanations 😊

  1. Configuration de la Timezone
  2. Mise à jour de tous les paquets
  3. Ajout du Repository apt d’InfluxData (éditeur de de InfluxDB et Telegraf) avec la clé associée
    • Ici on a exemple de l’utilisation des Facts vu plus haut avec la récupération de l’OS (ubuntu) et de la version (focal)
  4. Si InfluxDB n’est pas présent, on l’installe.
    • Ici on précise present et non pas latest, on veut que InfluxDB soit installé, si c’est déjà le cas, on ne souhaite pas le mettre à jour.
  5. Enfn on démarre le service et on l’active (démarrage automatique)

Play

Un Playbook est divisé en plusieurs Play, ici on en retrouve deux, le premier commence dès le début avec :

1
- name: prerequisites

Le fait qu’il commence par un tiret indique que c’est une liste, et donc qu’il est proabable qu’il y en est plusieurs.

Un Play correspond à une liste de tâches qui seront exécutées suivant les informations dans l’en-tête. Ces tâches seront exécuter dans l’odre, sur chacun des nœuds. Cependant le traitement se fait en parallèle sur les nœuds (si on a précisé un groupe au lieu d’un hôte simple).

En-tête

Un en-tête par Play, la première :

1
2
3
4
5
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: yes

We find information that have already specified in the interactive mode such as the host (or group of hosts, we could have set all or grafana) and the remote user. We specify some additional information that we want to collect facts and get a privilege elevation (become).

Task

The Tasks are used to indicate the desired state of the node. If we look at the first Task :

1
2
3
    - name: Set timezone to Europe/Paris
      timezone:
        name: Europe/Paris

The name is optional and it will be used to obtain a clear display, but also as a marker if you wish to restart the Playbook from a specific point. Then comes the call of the module timezone to which we pass an argument corresponding to the Timezone.

The objective of this Task is to have the specified Timezone configured on the host. If it is the case, it will show a status Ok, if it has to modify it, it will specify Changed. In this logic, when the Task is restarted, it will show Ok and will not make any modification.

Module

The module is the component that contains the Python code that will be executed on the node, it is the one that must be idempotent and therefore make sure to make modifications only if necessary. The modules are accessible directly on Github: https://github.com/ansible/ansible/tree/stable-2.9/lib/ansible/modules.

Exécution du Playbook

At last, we can run our first Playbook with the following command :

1
ansible-playbook playbook.yml --inventory inventory.ini -K

This time no user or node is specified on which to run the Playbook, these information is indicated in the header of the Plays. We have added -K option to be asked for a sudo password.

I encourage you to run it several times to see the difference.

Cya!

Share on