Pour un rendu optimal, activez JavaScript

[My Ansible Journey] Les Variables

 ·  ☕ 8 min de lecture  ·  🦆 Jeremy

Partie 1 : Présentation et prérequis
Partie 2 : Bases et premiers Playbook
Partie 3 : Les Variables
Partie 4 : Les Collections
––

Après une grande pause (et une installation au Québec 🙃), on repart avec la troisième partie ! Cette fois on va essayer de faire plus court en se concentrant sur un seul sujet: les variables.

Le code des exemples qui suivent sont toujours sur GitHub

Variables

La gestion des variables dans Ansible est, pour moi, un des aspects les plus complexe du langage. La principale raison est le fait qu’il est possible de les déclarer à de très nombreux endroits (22 emplacements pour être précis). La liste dans la documentation précise l’ordre de surcharge suivant là où on a déclaré les variables. Spoiler alert, on utilise jamais les 22 emplacements, sinon on rend notre code illisible et on multiplie les chances de faire des erreurs. Dans la suite de l’article, on va évoquer quelques exemples pour la déclaration et l’utilisation de variables, mais aussi comment les organiser dans notre projet.

Variables dans l’inventaire

Premier exemple qu’on a déjà vu lors de la création de l’inventaire, la variable ansible_host :

1
tig ansible_host=192.168.188.129

C’est une variable spéciale, mais cela reste une variable. De la même manière on peut déclarer d’autres varibles dans le fichier inventaire :

1
tig ansible_host=192.168.188.129 my_var="this is my variable"

Quelques restrictions pour les noms de variables : doit commencer par une lettre, peut contenir lettre, chiffre et underscore, pas d’espace.

Pour que la variable puisse être utilisée dans un Playbook, il faut que ce dernier fasse référence au host (ou groupe) de l’inventaire. Dans notre cas on a déclaré la variable pour le host tig, il faudra donc le précisier dans l’entête du Play, on peut alors imaginer le Playbook suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: true
  tasks:
    - name: Affichage variable
      debug:
        msg: "{{ my_var }}"

Pour appeler une variable on l’entoure d’une double accolade et de quote (double ou simple). En réalité on se sert de Jinja2 (langage de templating pour Python), les doubles accolades indiquent à Jinja qu’il doit procéder à un remplacement par une variable.

On utilise le module debug pour faire ces tests, il est simple d’utilisation, et comme son nom l’indique, il sert pour le débug !

Après l’exécution du Playbook, on obtient le résultat suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/ansible/ansible-tp-tig$ ansible-playbook playbook-display-var.yml -i inventory.ini -K
BECOME password:

PLAY [Prerequisites] ***************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [tig]

TASK [Affichage variable] **********************************************************************************************
ok: [tig] => {
    "msg": "this is my variable"
}

PLAY RECAP *************************************************************************************************************
tig                        : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

On évite de déclarer les variables dans l’inventaire, ce n’est pas son usage. L’important c’est de comprendre qu’il y a un lien entre les variables et les hosts / groups de l’inventaire. On va y revenir plus loin.

Dans le Playbook

La manière la plus simple de déclarer des variables (la encore ce n’est pas l’idéal, mais instructif 🤓), c’est de le faire directement dans le Playbook. Si on reprend notre exemple précédent, cela donnerait ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
- name: Prerequisites
  hosts: tig
  gather_facts: yes
  remote_user: jlg
  become: true
  vars:
    my_var: "this is my variable"
    
  tasks:
    - name: Variable deplay
      debug:
        msg: "{{ my_var }}"

Les listes et dictionnaires

On retrouve aussi les classiques listes et dictionnaires, que l’on va pouvoir déclarer de cette façon :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  vars:
    my_var: "this is my variable"
    my_list:
      - first
      - second
      - third
    my_dict:
      field1: first
      field2: second
      field3: third

Et on les appelle dans le Playbook :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  tasks:
    - name: Variable display
      debug:
        msg: "{{ my_var }}"
    - name: List display
      debug:
        msg: "{{ my_list[0] }}"
    - name: Dict display
      debug:
        msg: "{{ my_dict['field1'] }}"

Ou on peut faire un peu plus complexe, avec une liste de dictionnaire :

1
2
3
4
5
    list_of_dict:
      - field1: first
        field2: second
      - field1: third
        field2: fourth

Puis la parcourir :

1
2
3
4
    - name: List of dict display
      debug:
        msg: "{{ item.field1 }}"
      loop: "{{ list_of_dict }}"

Le mot clé item permet de prendre la place de l ‘élément courant de la liste.
On aura l’occasion de revenir sur d’autres exemples avec les boucles (plus d’infos ici), mais c’est déjà un premier apperçu.

Un fichier de variable global

Maintenant que l’on a vu quelques exemples pratiques, passons à la manière dont nous allons les organiser dans nos projets. On va essayer de regrouper au maximum la déclaration des variables, par défaut il n’existe pas de fichier précis où les déclarer, mais il y a quand même des endroits à privilégier !

On pourrait créer un fichier de varible à la racine et l’importer dans le Playbook au moment souhaité. On va cependant préférer une méthode permettant au fichier d’être importé automatiquement, et cela grace au nom qu’on va lui donner.
Il y a deux dossiers prévus à cet effet :

  • groups_vars/
  • hosts_vars/

Dans ces dossiers on peut placer un fichier de variable. On a vu dans un précédent exemple que les variables sont liés aux hosts et groups de l’inventaire, pour l’import automatique il suffit de nommer le fichier d’après le nom du groupe ou hôte souhaité (et de le mettre dans le répertoire adapté !). Dans notre cas, on va utiliser le groupe implicite qui contient tous les hosts : all, le fichier de variable va donc s’appeler : group_vars/all.yml.

On déclare notre variable :

1
influxdb_database_name: vsphere

Utilisation de la variable avec InfluxDB

Lors du précédent article on avait réalisé un Play permettant d’installer InfluxDB, ici nous allons aller plus loin et passer à la création de la base de données qui servira à héberger nos métriques vSphere.

Rappel du Play existant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
- 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

Ce Play permet l’installation et le démarrage du service InfluxDB.

Lorsque l’on démarre le service InfluxDB pour la première fois, la base « internal » va être créée. On va préférer attendre que cette base soit créée avant de passer à la base vsphere.

Voici la Task permettant de faire cette vérification :

1
2
3
4
5
6
7
8
9
    - name: Wait for _internal database to be created after initial installation
      uri:
        url: "http://127.0.0.1:8086/query"
        method: POST
        body: 'q=SHOW DATABASES'
      register: response
      until: '"_internal" in (response | to_json)'
      retries: 5
      delay: 5

Cela mérite quelques explications je pense. Premièrement on utilise le module uri pour interroger un service web (API Rest dans notre cas), on précise l’url (avec Ansible les modules sont exécutés sur le nœud géré, donc localhost), la méthode et le body. On retrouve la documentation détaillé de ce module ici.

Ensuite l’idée est d’analyser le retour de l’appel API en cherchant le nom de la base de données qui est crée par défaut. On essaie jusque 5 fois, toutes les 5 secondes, car la base ne se crée pas tout de suite.

Une fois cette étape validée on est prêt pour la création de notre base de donnée :

1
2
3
4
5
6
7
8
    - name: Create database
      uri:
        url: "http://127.0.0.1:8086/query"
        method: POST
        body: 'q=CREATE DATABASE "{{ influxdb_database_name }}"'
      register: response
      changed_when: response.status == 200
      when: "influxdb_database_name not in (response.json | json_query('results[0].series[0].values[*][0]'))"

Une fois que ces deux Task sont ajoutées dans le Playbook, vous pouvez procéder à l’exécution avec le résultat suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
PLAY [InfluxDB] ******************************************************************************************

TASK [Install InfluxDB] **********************************************************************************
changed: [tig]

TASK [Service started and enabled] ***********************************************************************
changed: [tig]

TASK [Wait for _internal database to be created after initial installation] ******************************
FAILED - RETRYING: Wait for _internal database to be created after initial installation (5 retries left).
FAILED - RETRYING: Wait for _internal database to be created after initial installation (4 retries left).
ok: [tig]

TASK [Database created] **********************************************************************************
changed: [tig]

PLAY RECAP ***********************************************************************************************
tig                        : ok=9    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Conclusion

En général dans un projet on essaye de se limiter à 2 ou 3 emplacements où définir nos variables.
Personnellement, j’aime m’organiser de la manière suivante :

  • Dans les Roles (on y reviendra dans un futur article), définition de variable par défaut
    • Exemple, définir un port d’écoute par défaut
    • Très faible priorité
  • Fichiers de variables dédiés aux Playbooks dans group_vars/
    • Fichier de variable principal
    • Si il est nommé comme un groupe, les variables seront disponibles pour les hosts de ce groupe (all est un groupe)
    • Priorité plus élevée
  • Dans le fichier inventaire (ini ou YAML)
    • On a vu l’exemple où l’on définissait l’IP d’un host dans l’inventaire
    • Priorité encore plus élevée

A bientôt pour un article sur les Collections, cette fois sans laisser 6 mois entre les posts 😇

Partagez