Le mauvais groupe, il est hiérarchique… le bon groupe… il est hiérarchique, mais c’est pas pareil.

Le bon programmeur, apparemment…

Je suis récemment tombé sur une petite particularité (certains diront un quirk) d’Ansible qui m’a paru suffisamment intéressant pour tenter de l’expliquer, ou au moins d’en expliquer ma compréhension.

Et là, la marmotte d’Ansible, elle met le chocolat de l’héritage des groupes dans le papier du group_vars

Notre histoire commence un beau matin de printemps avec un playbook Ansible de bon alois qui se baladait innocemment dans la forêt enchantée du système d’information. Ce playbook contenait de nombreuses variables et de nombreux hôtes. Toutes ces variables et ces hôtes formaient une hiérarchie complexe où beaucoup de variables se surchargeaient les unes les autres dans un ballet ininterrompu et franchement difficilement compréhensible.

Du point de vue du contrôleur (donc la machine qui fait tourner Ansible), les groupes en question servent essentiellement à organiser de manière logique les choses.

Mais du point de vue de la machine que se passe-t-il ? Et du point de vue de l’hôte, comment c’est perçu ?

Tu vas pouvoir constater que la réponse n’est pas si simple que ça.

Vu de l’hôte, les groupes, on s’en tape

Du point de vue l’hôte (donc la machine sur laquelle se connecte le contrôleur Ansible), les groupes n’ont que peu d’importance : ils sont vus ni plus ni moins que comme des tags et rien de plus. D’ailleurs, à ma connaissance, ils ne sont accessibles qu’à travers la variable Ansible group_names qui contient simplement une liste, à plat donc, de tous les groupes auquel appartient l’hôte. On peut très facilement récupèrer cette liste comme suit :

---

- hosts: all
  gather_facts: false
  tasks:
    - name: tamerelol
      debug:
        msg: "{{ group_names }}"

Ce qui donne ce résultat :

ok: [HP-elite-book-cuisine.buttse.cx] => {
    "msg": [
        "borg_client",
        "borgbackup",
        "webservers",
        "disabled_syslog"
    ]
}

Cela peut être pratique dans certains cas :

  • mettre à jour seulement les machines dans le groupe X
  • exécuter une commande sur l’ensemble des machines dans le groupe Y
  • filtrer dans un template Jinja2

Mais le fait est : du point de vue de l’hôte, c’est une inforamtion plate et absolument pas hiérarchisée.

Donc si quelqu’un vous dit que les groupes Ansible sont juste des tags, il a basiquement raison, mais seulement du point de vue de l’hôte. Le contrôlleur, c’est une toute autre histoire…

Vu du contrôleur, les groupes, c’est la vie

Du point de vue de contrôleur en revanche, les groupes ont une très grande importance : c’est eux qui déterminent la façon dont les variables vont être déterminées et appliquées aux hôtes. En gros, du point de vue des group_vars de l’inventaire (et seulement de l’inventaire, je vous rappelle qu’il y a d’autres priorités dans Ansible, en particulier concernant les variables d’hôtes, les group_vars des playbooks, j’en passe et des meilleurs, la résolution est la suivante :

  • on commence par appliquer les variables qui sont dans le all (celui-ci a la priorité la plus basse donc) ;
  • si elles sont surchargées par des variables dans le premier niveau de l’inventaire, ces dernières prennent la priorité (et donc surcharge logiquement le all)
  • si des variables sont définies plus bas dans la hiérarchie des groupes, elles surchargeront logiquement le niveau de leur parent
  • et seront surchargées par les variables définies au niveau de leurs enfants

Il n’y a qu’une petite exception à cette règle, qui peut s’avérer très trompeuse dans certains cas, ce sont les variables qui sont définies au même niveau dans la hiérarchie des groupes. Dans ce cas, Ansible fait une résolution dans l’ordre alphabétique de définition des groupes (donc les groupes en a* seront surchargés par les groupes en b*, etc…).

Concrètement, si vous avez une hiérarchie de groupes qui ressemblent à ça :

> ansible-inventory -i production.yml --graph borgbackup
@borgbackup:
  |--@borg_client:
  |  |--macbook-pro-chiottes.buttse.cx
  |  |--HP-elite-book-cuisine.buttse.cx
  |--@borg_server:
  |  |--meinbackup.buttse.cx

Vous pouvez des variables « globales » (au niveau du groupe borgbackup donc) et les surcharger éventuellement au niveau du dessous. Petite illustration. Admettons que je définisse une variable borg_vars dans group_vars/borgbackup.yml comme suit :

> cat group_vars/borgbackup.yml
---

borg_vars: "je suis le niveau le plus en haut"

Et que je définisse la même variable un peu plus « bas » dans group_vars/borg_client.yml :

> cat group_vars/borg_client.yml
---

borg_vars: "je suis le niveau du client et je surcharge le serveur"

On va tenter de voir comment c’est défini derrière :

> ansible-playbook -u root -i production.yml test.yml

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

TASK [tamerelol] **************************************************************************************************************
lundi 28 novembre 2022  10:03:20 +0100 (0:00:00.032)       0:00:00.032 ********
ok: [meinbackup.buttse.cx] => {
    "msg": "je suis le niveau le plus en haut"
}
ok: [HP-elite-book-cuisine.buttse.cx] => {
    "msg": "je suis le niveau du client et je surcharge le serveur"
}
ok: [macbook-pro-chiottes.buttse.cx] => {
    "msg": "je suis le niveau du client et je surcharge le serveur"
}

Comme tu peux le constater, la hiérarchie des groupes a bien été respectée pour surcharger la variable borg_vars. Évidemment, s’il y avait eu un groupe en dessous de borg_client, on aurait aussi pu surchager la variable en question dedans aussi et ça aurait fait à peu près le même effet.

Ce dont il faut se méfier le plus, c’est si des serveurs sont définis en même temps sur plusieurs niveaux. Dans ce cas, on revient au cas initiale (résolution dans l’ordre alphabéique). On peut le démontrer assez facilement dans notre cas en ajoutant l’hôte meinbackup.buttse.cx directement dans le groupe borg_client. En faisant cela, on obtient bien :

ok: [meinbackup.buttse.cx] => {
    "msg": "je suis le niveau du client et je surcharge le serveur"
}

Parce que, du point de vue d’Ansible, borg_client est alphabétiquement avant borgbackup. D’ailleurs, borg_server étant juste après borg_client mais avant borgbackup, si j’ajoute cette variable dans group_vars/borg_server.yml, elle surcharge bien celle de group_vars/borg_client.yml, on constate bien que c’est le cas :

> cat group_vars/borg_server.yml
---

borg_vars: "normalement, je devrais avoir la priorité pour borg_server"
> ansible-playbook -u root -i production.yml test.yml
[…]
TASK [tamerelol] **************************************************************************************************************
lundi 28 novembre 2022  10:03:20 +0100 (0:00:00.032)       0:00:00.032 ********
ok: [meinbackup.buttse.cx] => {
    "msg": "normalement, je devrais avoir la priorité pour borg_server"
}
ok: [HP-elite-book-cuisine.buttse.cx] => {
    "msg": "je suis le niveau du client et je surcharge le serveur"
}
ok: [macbook-pro-chiottes.buttse.cx] => {
    "msg": "je suis le niveau du client et je surcharge le serveur"
}
[…]

Donc si quelqu’un vous dit que les groupes Ansible sont effectivement hiérarchiques, il a basiquement raison, mais seulement du point de vue du contrôleur et de la définition de la précédence des variables.

Conclusage

C’est un beau bordel mine de rien ces histoires de variables Ansible. Déjà que la précédence n’est pas toujours simple (je vous mets au défi de me dire de tête qui est plus prioritaire entre une variable d’inventaire, une variable de playbook et une variable de groupes), Ansible en rajoute une couche avec cette histoire de précédence dans les hiérarchies de groupes et de précédence dans les noms de groupe, histoire de bien simplifier les choses pour le quidam moyen.

Bref, j’espère que ça t’aura permis de mieux comprendre la chose et de ne pas te faire surprendre. Et surtout, on n’oublie pas : quand deux personnes affirment des choses contradictoires, elles ne parlent peut-être simplement pas de la même chose ou du même point de vue.