AnsibleVoici le douzième article de la formation Ansible. Dans mon précédent article, je vous ai présenté les handlers et la possibilité d’exécuter une tâche seulement en cas de changement. Aujourd’hui nous allons nous intéresser aux variables.

Ansible offre la possibilité d’utiliser des variables dans les tasks, à condition que celles-ci aient été définies quelque part auparavant. Sachez qu’il existe une bonne vingtaine de méthodes différentes pour faire ceci. Non, ne partez pas en courant. Nous allons prendre notre temps et découvrir tout cela en mettant un pied devant l’autre.

Atelier pratique

Placez-vous dans le répertoire du treizième atelier pratique :

$ cd ~/formation-ansible/atelier-13

Voici les quatre machines virtuelles de cet atelier :

Machine virtuelle Adresse IP
ansible 10.23.45.10
rocky 10.23.45.20
debian 10.23.45.30
suse 10.23.45.40

Démarrez les VM :

$ vagrant up

Connectez-vous au Control Host :

$ vagrant ssh ansible

L’environnement de cet atelier est préconfiguré et prêt à l’emploi :

  • Ansible est installé sur le Control Host.
  • Le fichier /etc/hosts du Control Host est correctement renseigné.
  • L’authentification par clé SSH est établie sur les trois Target Hosts.
  • Le répertoire du projet existe et contient une configuration de base et un inventaire.
  • Direnv est installé et activé pour le projet.
  • Le validateur de syntaxe yamllint est également disponible.

Rendez-vous dans le répertoire des playbooks :

$ cd ansible/projets/ema/playbooks/
direnv: loading ~/ansible/projets/ema/.envrc
direnv: export +ANSIBLE_CONFIG

Play vars

Une des nombreuses possibilités pour définir des variables consiste à inclure une section vars dans le play. Dans l’exemple ci-dessous, deux variables sont d’abord définies dans la section vars. Dans un deuxième temps, elles sont affichées grâce au module debug :

---  # vars1.yml

- hosts: localhost
  gather_facts: false

  vars:
    color: blue
    number: 42

  tasks:
    - debug:
        msg: "Color: {{color}}, Number: {{number}}"

...

Jusqu’ici, rien de particulier à signaler :

$ ansible-playbook vars1.yml 

PLAY [localhost] *********************************************************************

TASK [debug] *************************************************************************
ok: [localhost] => {
    "msg": "Color: blue, Number: 42"
}

PLAY RECAP ***************************************************************************
localhost  : ok=1  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

AstuceNotez au passage la syntaxe avec les doubles accolades {{...}} qui nous permet d’accéder au contenu des variables.

Extra vars

Une autre possibilité pour définir des variables consiste à les fournir en argument en ligne de commande avec l’option -e (ou --extra-vars) :

$ ansible-playbook vars1.yml -e color=red

TASK [debug] *************************************************************************
ok: [localhost] => {
    "msg": "Color: red, Number: 42"
}

L’option -e pourra d’ailleurs être réitérée autant de fois que vous voulez :

$ ansible-playbook vars1.yml -e color=red -e number=99

TASK [debug] *************************************************************************
ok: [localhost] => {
    "msg": "Color: red, Number: 99"
}

Précédences des variables

Cette première manipulation des variables nous permet de tirer une conclusion : lorsqu’on définit une seule et même variable à plusieurs endroits, une de ces définitions va l’emporter sur les autres. Concrètement, nous savons déjà qu’une variable extra vars est plus forte qu’une variable play vars.

Vous vous doutez bien que ces règles de précédence sont clairement définies. Jetez un œil dans la documentation officielle à la section Using Variables. Vous y trouverez le classement officiel :

AstuceNon, je ne vous demande pas d’apprendre ce classement par cœur. En revanche, rien ne vous empêche de comprendre son fonctionnement :

  • Il comporte en tout et pour tout 22 types de variables.
  • Repérez les play vars en position 12.
  • Repérez les extra vars en position 22.
  • Le classement va manifestement du plus faible au plus fort.
  • Il en résulte qu’une variable extra vars l’emportera toujours sur toutes les autres.

set_fact

Dans certains cas de figure, il devient nécessaire de définir des variables au cours de l’exécution d’un play. Dans ce cas, set_fact vous permet d’ajouter des variables à un play ou même de redéfinir des variables existantes :

---  # set_fact.yml

- hosts: localhost
  gather_facts: false

  tasks:

    - name: Define variables
      set_fact:
        color: yellow
        number: 13

    - debug:
        msg: "Color: {{color}}, Number: {{number}}"

...

Rien de bien surprenant ici non plus :

$ ansible-playbook set_fact.yml 

TASK [Define variables] ********************************************************************
ok: [localhost]

TASK [debug] *******************************************************************************
ok: [localhost] => {
    "msg": "Color: yellow, Number: 13"
}
  • Dans la liste des précédences, les variables set_fact occupent la position 19. On va dire qu’elles sont plutôt « fortes ».
  • Un fact est une variable spécifique à l’hôte. À partir du moment où elle a été définie, elle ne sera visible que pour l’hôte qui vient d’exécuter la tâche correspondante.
  • Ne vous tracassez pas trop sur cette distinction pour l’instant. Nous aborderons les facts Ansible en temps et en heure, et les choses vont s’éclaircir.

group_vars

Pour gérer les différences entre les systèmes, il nous faut une méthode qui permet d’attribuer une valeur à une variable en fonction de la cible. Un exemple pratique va vous permettre de comprendre ce fonctionnement. Dans l’exemple suivant, on inclut tous les Target Hosts, mais les variables mycolor et mynumber ne sont pas définies :

---  # vars2.yml

- hosts: all
  gather_facts: false

  tasks:
    - debug:
        msg: "Color: {{mycolor}}, Number: {{mynumber}}"

...

Comme il faut s’y attendre, l’accès au contenu d’une variable non définie résulte en une erreur d’exécution conséquente, pour ne pas dire fatale :

$ ansible-playbook vars2.yml 

PLAY [all] *********************************************************************************

TASK [debug] *******************************************************************************
fatal: [rocky]: FAILED! => {"msg": "The task includes an option with an undefined variable.
fatal: [debian]: FAILED! => {"msg": "The task includes an option with an undefined variable.
fatal: [suse]: FAILED! => {"msg": "The task includes an option with an undefined variable.

PLAY RECAP *********************************************************************************
debian     : ok=0  changed=0  unreachable=0  failed=1  skipped=0  rescued=0  ignored=0   
rocky      : ok=0  changed=0  unreachable=0  failed=1  skipped=0  rescued=0  ignored=0   
suse       : ok=0  changed=0  unreachable=0  failed=1  skipped=0  rescued=0  ignored=0

En dehors des extra vars, est-ce qu’il existe une méthode pour définir des variables à l’extérieur d’un playbook ? La réponse est oui. En effet, Ansible va chercher ce genre d’information dans un répertoire group_vars :

$ mkdir -v ~/ansible/projets/ema/group_vars
mkdir: created directory '/home/vagrant/ansible/projets/ema/group_vars'

Le groupe all contient la liste complète de tous nos Target Hosts. On va donc créer un fichier all.yml correspondant à l’intérieur du répertoire group_vars, avec des valeurs par défaut pour tous les systèmes cible :

---  # group_vars/all.yml

mycolor: white
mynumber: 97

...

À partir de là, notre playbook s’exécute correctement :

$ ansible-playbook vars2.yml 

PLAY [all] ***************************************************************************

TASK [debug] *************************************************************************
ok: [rocky] => {
    "msg": "Color: white, Number: 97"
}
ok: [debian] => {
    "msg": "Color: white, Number: 97"
}
ok: [suse] => {
    "msg": "Color: white, Number: 97"
}

PLAY RECAP ***************************************************************************
debian     : ok=1  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   
rocky      : ok=1  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   
suse       : ok=1  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

C’est déjà pas mal, mais nous n’avons toujours pas de paramétrage individuel. Qu’à cela ne tienne, jetez un œil dans l’inventaire de cet atelier, qui définit un groupe [redhat_hosts] avec l’hôte rocky comme seul et unique membre :

[testing]
rocky
debian
suse

[redhat_hosts]
rocky

Admettons qu’on souhaite « colorier en rouge » tous nos systèmes Red Hat. Dans ce cas, on pourrait très bien créer un fichier group_vars/redhat_hosts.yml en partant du principe qu’Ansible s’en servira pour la définition des variables du groupe correspondant :

---  # group_vars/redhat_hosts.yml

mycolor: red

...

Résultat des courses :

TASK [debug] *************************************************************************
ok: [rocky] => {
    "msg": "Color: red, Number: 97"
}
ok: [debian] => {
    "msg": "Color: white, Number: 97"
}
ok: [suse] => {
    "msg": "Color: white, Number: 97"
}

Là aussi, jetez un œil à la précédence des variables :

  • group_vars/all.yml occupe la quatrième place (pas terrible)
  • group_vars/*.yml occupe la sixième place (un chouïa plus fort)

host_vars

Ansible vous offre également la possibilité de paramétrer des cibles individuelles. Le fonctionnement est analogue aux group vars, au détail près qu’Ansible va chercher ses informations dans un répertoire host_vars correspondant :

$ mkdir -v ~/ansible/projets/ema/host_vars
mkdir: created directory '/home/vagrant/ansible/projets/ema/host_vars'

Admettons qu’on souhaite « colorier en vert » notre système SUSE. Dans ce cas, il suffit de créer un fichier host_vars/suse.yml en partant du principe qu’Ansible s’en servira pour la définition des variables de l’hôte correspondant :

---  # host_vars/suse.yml

mycolor: green

...

Et voici le résultat :

TASK [debug] *************************************************************************
ok: [rocky] => {
    "msg": "Color: red, Number: 97"
}
ok: [debian] => {
    "msg": "Color: white, Number: 97"
}
ok: [suse] => {
    "msg": "Color: green, Number: 97"
}

Vous vous doutez bien que les paramètres d’un hôte individuels doivent l’emporter sur les paramètres de groupe. Là aussi, jetez un œil à la précédence des variables : host_vars/*.yml se trouve en neuvième position.

En mode interactif

Une option moins courante consiste à définir vos variables au début de l’exécution d’un play à l’aide d’une section vars_prompt comme ceci :

---  # prompting.yml

- hosts: localhost
  gather_facts: false

  vars_prompt:

    - name: var1
      prompt: Please select a value for var1
      default: 42
      private: false

    - name: var2
      prompt: And now for var2 (secret)
      private: true

  tasks:
    - debug:
        msg: "var1 is {{var1}}, var2 is {{var2}}"

...

Voilà ce que ça donne :

$ ansible-playbook prompting.yml 
Please select a value for var1 [42]: 99
And now for var2 (secret): ***********

PLAY [localhost] *************************************************************************

TASK [debug] *****************************************************************************
ok: [localhost] => {
    "msg": "var1 is 99, var2 is yatahongaga"
}

Si vous lancez le playbook avec des extra vars, la saisie interactive ne s’affiche plus en raison de la précédence :

$ ansible-playbook prompting.yml -e var1=38 -e var2=zamooche

PLAY [localhost] *************************************************************************

TASK [debug] *****************************************************************************
ok: [localhost] => {
    "msg": "var1 is 38, var2 is zamooche"
}

Structures complexes

Avec Ansible, les variables peuvent très bien contenir des structures complexes, comme on peut le voir dans l’exemple ci-dessous :

---  # vars-complex.yml

- hosts: suse
  gather_facts: false

  vars:

    motd:
      hello: Bonjour cher visiteur !
      quote:
        - Chat échaudé craint la charrue avant la peau de l'ours.
        - Chassez le naturiste, il revient au bungalow.
      lotto: [2, 8, 17, 33, 34, 42]

  tasks:

    - name: Upload /etc/motd
      copy:
        dest: /etc/motd
        content: |
          {{ motd.hello }}

          Citation du jour: {{ motd.quote[1] }}

          Les numéros du loto: {{ motd.lotto | join(', ') }}

...

AstuceLe fichier /etc/motd contient le message du jour (Message Of The Day) censé s’afficher juste après la connexion au shell sur les systèmes Unix/Linux.

Exécutez le playbook et connectez-vous à l’hôte suse :

$ ssh suse
Last login: Sun Sep 22 07:58:19 2024 from 10.23.45.10
Bonjour cher visiteur !

Citation du jour: Chassez le naturiste, il revient au bungalow.

Les numéros du loto: 2, 8, 17, 33, 34, 42

Tout le monde est là ?

Dans certains cas de figure, ce n’est pas une mauvaise idée de vérifier d’emblée si toutes les variables nécessaires à l’exécution d’un playbook sont correctement définies. Le module assert va nous assister dans cette démarche :

---  # assert.yml

- hosts: debian
  gather_facts: false

  tasks:

    - assert:
        that:
          - hostname is defined and hostname != ''
        fail_msg: The hostname variable is undefined or empty.

    - name: Set hostname
      hostname:
        name: "{{ hostname }}"

    - debug:
        msg: Let's check if we see this.

...

La condition en-dessous de that doit être remplie, faute de quoi le play s’interrompt :

$ ansible-playbook assert.yml 

PLAY [debian] ************************************************************************

TASK [assert] ************************************************************************
fatal: [debian]: FAILED! => {
    "assertion": "hostname is defined and hostname != ''",
    "changed": false,
    "evaluated_to": false,
    "msg": "The hostname variable is undefined or empty."
}

PLAY RECAP ***************************************************************************
debian     : ok=0  changed=0  unreachable=0  failed=1  skipped=0  rescued=0  ignored=0

Le même play avec une variable hostname définie :

$ ansible-playbook assert.yml -e hostname=sandbox

PLAY [debian] ************************************************************************

TASK [assert] ************************************************************************
ok: [debian] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Set hostname] ******************************************************************
changed: [debian]

TASK [debug] *************************************************************************
ok: [debian] => {
    "msg": "Let's check if we see this."
}

PLAY RECAP ***************************************************************************
debian     : ok=3  changed=1  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

Quittez le Control Host :

$ exit

Supprimez toutes les VM :

$ vagrant destroy -f

Exercice

Placez-vous dans le répertoire du quatorzième atelier pratique :

$ cd ~/formation-ansible/atelier-14

Voici les quatre machines virtuelles de cet atelier :

Machine virtuelle Adresse IP
control 10.23.45.10
target01 10.23.45.20
target02 10.23.45.30
target03 10.23.45.40

Les VM tournent toutes sous Rocky Linux. Démarrez-les :

$ vagrant up

Connectez-vous au Control Host :

$ vagrant ssh control

L’environnement de cet atelier est préconfiguré et prêt à l’emploi :

  • Ansible est installé sur le Control Host.
  • Le fichier /etc/hosts du Control Host est correctement renseigné.
  • L’authentification par clé SSH est établie sur les trois Target Hosts.
  • Le répertoire du projet existe et contient une configuration de base et un inventaire.
  • Direnv est installé et activé pour le projet.
  • Le validateur de syntaxe yamllint est également disponible.

Rendez-vous dans le répertoire du projet :

$ cd ansible/projets/ema/
direnv: loading ~/ansible/projets/ema/.envrc
direnv: export +ANSIBLE_CONFIG
$ ls -l
total 8
-rw-r--r--. 1 vagrant vagrant  65 Sep 19 14:26 ansible.cfg
-rw-r--r--. 1 vagrant vagrant 128 Sep 19 14:26 inventory
drwxr-xr-x. 2 vagrant vagrant   6 Sep 19 14:26 playbooks
  • Écrivez un playbook myvars1.yml qui affiche respectivement votre voiture et votre moto préférée en utilisant le module debug et deux variables mycar et mybike définies en tant que play vars.
  • En utilisant les extra vars, remplacez successivement l’une et l’autre marque – puis les deux à la fois – avant d’exécuter le play.
  • Écrivez un playbook myvars2.yml qui fait essentiellement la même chose que myvars1.yml, mais en utilisant une tâche avec set_fact pour définir les deux variables.
  • Là aussi, essayez de remplacer les deux variables en utilisant des extra vars avant l’exécution du play.
  • Écrivez un playbook myvars3.yml qui affiche le contenu des deux variables mycar et mybike mais sans les définir. Avant d’exécuter le playbook, définissez VW et BMW comme valeurs par défaut pour mycar et mybike pour tous les hôtes, en utilisant l’endroit approprié.
  • Effectuez le nécessaire pour remplacer VW et BMW par Mercedes et Honda sur l’hôte target02.
  • Écrivez un playbook display_user.yml qui affiche un utilisateur et son mot de passe correspondant à l’aide des variables user et password. Ces deux variables devront être saisies de manière interactive pendant l’exécution du playbook. Les valeurs par défaut seront microlinux pour user et yatahongaga pour password. Le mot de passe ne devra pas s’afficher pendant la saisie.

Quittez le Control Host :

$ exit

Supprimez toutes les VM :

$ vagrant destroy -f

Lire la suite : Variables enregistrées


La rédaction de cette documentation demande du temps et des quantités significatives de café espresso. Vous appréciez ce blog ? Offrez un café au rédacteur en cliquant sur la tasse.

 

Catégories : Formation

0 commentaire

Laisser un commentaire

Emplacement de l’avatar

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *