Ansible - L'inventaire

Partie 2

Deuxième partie de la série consacrée à Ansible. Cette fois nous parlerons plus en détail de l'inventaire, à quoi ça sert, comment l'organiser, comment l'exploiter, etc.

L'inventaire Ansible

L'inventaire est la liste de hosts qu'Ansible est capable de gérer. Il peut se définir de deux façons : sous forme INI ou bien en YAML. L'inventaire par défaut est celui situé dans /etc/ansible/hosts, mais celui-ci peut être un fichier appelé à la volée.

Exemple d'inventaire au format INI :

mail.example.com

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
three.example.com

Le même, en YAML.

all:
  hosts:
    mail.example.com:
  children:
    webservers:
      hosts:
        foo.example.com:
        bar.example.com:
    dbservers:
      hosts:
        one.example.com:
        two.example.com:
        three.example.com:

Groupement de hosts

Pour que l'inventaire soit facile à maintenir et l'exécution d'Ansible cohérente, il est possible de regrouper les hosts sous différentes bannières. De même, vous pouvez faire des groupes de groupes pour éviter de répéter plusieurs fois les mêmes éléments et ainsi potentiellement les oublier en cas de rajout.

Deux groupes par défaut existent systématiquement, qu'ils soient nommés ou non :

  • all : prend la totalité des hosts de l'inventaire
  • ungrouped : prend uniquement les hosts membres d'aucun groupe en dehors de all.

Pour grouper des hosts, il suffit d'utiliser l'instruction children qui permet de définir la hiérarchie.

Admettons que nous avons une application dont les serveurs sont répartis à plusieurs emplacements géographiques, et avec une plateforme de production située à Paris et une pré-production à Lille. On pourrait concevoir l'inventaire ainsi :

all:
  hosts:
    srv1.example.com
  children:
    webservers:
      hosts:
        web1.example.com
        web2.example.com
        web3.example.com
    dbservers:
      hosts:
        db1.example.com
        db2.example.com
        db3.example.com
    paris:
      hosts:
        web1.example.com
        web3.example.com
        db1.example.com
        db3.example.com
    lille:
      hosts:
        web2.example.com
        db2.example.com
    prod:
      hosts:
        web1.example.com
        web2.example.com
        db1.example.com
        db2.example.com
    preprod:
      hosts:
        web3.example.com
        db3.example.com

Dans cet exemple, les servers webX et dbX sont répétés entre leur rôle, leur emplacement, et leur type de plateforme. L'écriture suivante permet d'exploiter les sous groupes et limiter la redondance d'information.

all:
  hosts:
    srv1.example.com
  children:
    webservers:
      hosts:
        web1.example.com
        web2.example.com
        web3.example.com
    dbservers:
      hosts:
        db1.example.com
        db2.example.com
        db3.example.com
    paris:
      hosts:
        web1.example.com
        web3.example.com
        db1.example.com
        db3.example.com
    lille:
      hosts:
        web2.example.com
        db2.example.com
    prod:
      children:
        paris:
    preprod:
      children:
        lille:

Avec cet exemple, nous savons que la production est à Paris et la pre-production à Lille. Il suffit donc d'indiquer le groupe Paris et Lille à leurs emplacements respectifs. Par contre, on garde un dédoublement d'information concernant le rôle technique des serveurs et leur localisation. Et si la preprod et la prod venaient à s'inverser, les plateformes seraient les mêmes mais les membres du groupe prod et preprod changeront.

Les groupes webservers et dbservers peuvent être inutiles aussi si jamais dans notre besoin, nous considérons que nous gérons les plateformes par localisation et non par rôle technique.

En version INI, nous aurions le résultat suivant :

srv1.example.com #le serveur n'est pas groupé

[prod:children]
paris

[preprod:children]
lille

[webservers]
web1.example.com
web2.example.com
web3.example.com

[dbservers]
db1.example.com
db2.example.com
db3.example.com

[paris]
web1.example.com
web3.example.com
db1.example.com
db3.example.com

[lille]
web2.example.com
db2.example.com

Les variables d'inventaires

Il est parfaitement possible d'assigner des variables pour un host au sein de l'inventaire, par exemple un port d'écoute, une IP, un user, etc. Néanmoins, si ce besoin commence à devenir trop conséquent, il conviendra de recourir à des fichiers séparés qui seront nativement chargés par Ansible : les group_vars et hosts_vars notamment.

Exemple d'attribution de variable à un host.

[lille]
web1.example.com http_port=80
web2.example.com http_port=81

Les variables de hosts sont utiles pour définir des informations de bas niveau tel que des configurations spécifiques SSH. Exemple pour indiquer à Ansible le port et l'utilisateur de connexion :

[lille]
web1.example.com ansible_port=1234 ansible_user=user1
web2.example.com ansible_port=2222 ansible_user=user2

Group_vars et inventaires

Comme dit plus haut, si on a besoin de gérer beaucoup de variables sur un déploiement, il conviendra d'utiliser les fichiers group_vars ou hosts_vars. Le lien entre le fichier group_vars et l'inventaire se fait avec le nom du groupe.

Exemple d'arborescence :

inventory/
    production/
        hosts #ce fichier contient l'inventaire
        group_vars/
            all.yml #ce fichier contient des variables applicables à l'ensemble des hosts de l'inventaire
            webservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe webservers
            dbservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe dbservers
        hosts_vars/
            all.yml #ce fichier contient des variables applicables à l'ensemble des hosts de l'inventaire
            webservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe webservers
            dbservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe dbservers
    preprod/
        hosts #ce fichier contient l'inventaire
        group_vars/
            all.yml #ce fichier contient des variables applicables à l'ensemble des hosts de l'inventaire
            webservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe webservers
            dbservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe dbservers
        hosts_vars/
            all.yml #ce fichier contient des variables applicables à l'ensemble des hosts de l'inventaire
            webservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe webservers
            dbservers.yml # ce fichier contient des variables applicables uniquement aux membres du groupe dbservers

La principale différence avec une arborescence de ce type, c'est que nous utilisons deux fichiers d'inventaire différents. Un pour la prod, un pour la preprod. L'inventaire est le fichier host présent dans le sous dossier inventory/preprod et inventory/prod.

Cela change la façon dont on appellera l'inventaire lors d'une exécution Ansible.

# en prod
ansible-playbook -i inventory/production/hosts monplaybook.yml

# en preprod
ansible-playbook -i inventory/preprod/hosts monplaybook.yml

Il s'agit ainsi d'une approche différente de l'inventaire que nous avions fait en exemple. En effet, dans notre inventaire nous avions mis la totalité de nos serveurs de preprod et prod et deux groupes pour les différencier. Ces groupes auraient permis de lancer des actions en contextualisant le code (si membre du groupe prod, alors...). Avec des inventaires séparés entre prod et preprod, le code exécuté peut théoriquement être le même et c'est l'inventaire qui le contextualisera.

Nous verrons comment exploiter les group_vars et host_vars dans le billet qui sera dédié aux rôles Ansible.

Points d'attention

L'inventaire est un élément fortement structurant d'un playbook ou d'un rôle Ansible. Son organisation conditionnera comment vous écrirez vos procédures, c'est donc un composant qui nécessite une certaine réflexion.

Comprendre la portée des variables du host_vars et du group_vars est essentiel pour éviter des réécritures de code coûteuses en temps.

Exemples de portée de variables :

  • L'application déployée par Ansible possède des arborescences de dossier censées être identiques entre tous les hosts qui ont le rôle "webserver" => ces valeurs sont à mettre dans group_vars/webservers.yml
  • L'application déployée par Ansible est un ensemble de plusieurs bases de données qui ont chacune un nom d'instance différent => cette valeur est à mettre dans hosts_vars/dbservers.yml
  • L'application écoute le port 443 en production et 4443 en preproduction => cette variable est à mettre dans le group_vars avec la valeur attendue pour le contexte
  • Les serveurs web déployés sur les hosts écoutent chacun un port différent, un load balancer expose en frontal le port utilisé par les clients => le port spécifique de chaque host est à mettre dans le hosts_vars

Nous verrons dans le billet consacré aux rôles comment exploiter les variables du group_vars et hosts_vars.

Conclusion

Nous avons donc vu ce qu'est l'inventaire et comment on peut l'organiser et l'exploiter. A titre personnel, j'ai une préférence pour le format INI que je trouve beaucoup plus lisible que le YAML pour le coup.

Dans le prochain billet, nous rédigerons et exécuterons notre premier playbook.