Ansible - Maîtriser les inventaires statiques
Publié le : 7 janvier 2021 | Mis à jour le : 1 mars 2023Table des matières
Continuons notre formation Ansible avec au menu aujourd’hui les inventaires Ansible.
Un inventaire est une source de données listant les machines cibles devant être gérés par Ansible qui peuvent être organisée en groupes. On distingue deux type d’inventaire :
- les inventaires statiques constitué d’un fichier décrivant la hiérarchie des serveurs
- les inventaires dynamiques fourni par un système centralisé recensant tous les serveurs de l’infrastructure (ex aws).
Dans ce billet je vais vous donner quelques conseils pour gérer plus facilement vos variables définis dans vos inventaires.
Configurer la source de votre inventaire
Ansible utilise des plugins
pour gérer les différentes source
d’inventaires. Il existe toute une série de plugins et pour les lister il suffit
de lancer la commande:
ansible-doc -t inventory -l
Les plugins d’inventaires sont gérés au niveau de la configuration d’ansible via le fichier ansible.cfg, que je vous conseille de mettre dans le répertoire de votre projet, car le plus souvent il sera fonction de celui-ci. Si vous n’en avez pas vous pouvez le générer :
ansible-config init --disabled > ansible.cfg
Par défaut, Ansible propose les suivants :
- host_list: une simple liste d’hôte
- script: un script qui construit un inventaire dynamique à partir d’une source de données
- yaml: l’inventaire en format de fichier de données YAML
- toml: l’inventaire en format de fichier de données TOML
- ini: l’inventaire en format de fichier de données INI
- auto:
Pour modifier cette liste il faut se rendre dans la section inventories et éditer enable_plugins
[inventory]
enable_plugins = host_list, script, auto, yaml, ini, toml
Ici nous allons laisser ceux définis par défaut et nous utiliserons le format le plus courant qui est host_list.
Constitution de votre fichier hosts
Je vous conseille de garder ce nom !
Organisation d’un inventaire statique
Ce que l’on rencontre le plus souvent dans les bonnes pratiques Ansible pour créer un inventaire statique est l’utilisation de la structure suivante:
inventories
├── production
| ├── hosts
│ ├── group_vars
│ │ ├── all
│ │ │ └── all.yml
│ │ ├── dbservers
│ │ │ └── all.yml
│ │ └── webservers
│ │ └── all.yml
│ └── host_vars
│ ├── prodhost1.yml
│ └── prodhost2.yml
└── staging
├── group_vars
│ ├── all
│ │ └── all.yml
│ ├── dbservers
│ │ └── all.yml
│ └── webservers
│ └── all.yml
├── host_vars
│ └── stagehost1.yml
└── hosts
En effet il faut bien séparer vos différents environnements (le cloisonnement) pour éviter de lancer des actions sur la production alors que vous vouliez le faire sur l’environnement de test. Vous avez certainement remarqué que nous retrouvons des dossiers _vars qui vont nous servir à stocker nos variables.
La priorité de lecture des variables par Ansible
Avant de vous lancer dans la création de variables il est important de connaître la priorité de lecture qu'Ansible applique lors de l’éxécution d’une commande. Ansible priorise la source des variables, de la plus petite à la plus grande dans cet ordre :
- command line values (par exemple, -u my_user qui n’est pas une variable)
- role defaults (défini dans role/defaults/main.yml) 1
- inventory file or script group vars 2
- inventory group_vars/all 3
- playbook group_vars/all 3
- inventory group_vars/* 3
- playbook group_vars/* 3
- inventory file or script host vars 2
- inventory host_vars/* 3
- playbook host_vars/* 3
- host facts / cached set_facts 4
- play vars
- play vars_prompt
- play vars_files
- role vars (défini dans role/vars/main.yml)
- block vars (seulement pour les tasks dans un block)
- task vars (seulement pour les tasks)
- include_vars
- set_facts / registered vars
- role (et include_role) params
- include params
- extra vars ( par exemple, -e “user=my_user”)
On voit que les variables passées dans les paramètres de la commande
ansible-playbook
(-e) est le plus fort, il gagne sur tout le monde.
Imaginez une infrastructure gérée avec des dizaines de playbooks, des dizaines de fichiers de variables, des dizaines de groupes, … Si on ne s’organise par correctement, le risque est de rapidement d’en perdre le contrôle !
Un peu de pratique
Dans cet exercice nous allons créer l’environnement de test, ici staging, dans lequel nous aurons 3 machines répartis dans 2 groupes: webservers et dbservers.
Commençons par créer l’arborescence ci-dessus en se limitant à un environnement staging :
mkdir -p inventories/staging/{group_vars,host_vars}\{all,webservers,dbservers}
touch inventories/staging/hosts
touch inventories/staging/group_vars/{all,webservers,dbservers}.yml
touch inventories/staging/hosts_vars/stagehost{1,2}.yml
Dans le fichier inventories/staging/hosts mettez y ceci:
stagehost1
[webservers]
stagehost2
[dbservers]
stagehost3
Lançons la commande ansible-inventory en indiquant notre inventaire :
ansible-inventory -i inventories/staging --graph --vars # pour afficher les variables
@all:
|--@dbservers:
| |--stagehost3
|--@ungrouped:
| |--stagehost1
|--@webservers:
| |--stagehost2
Remarquez que notre machine stagehost1.yml fais partie d’aucun group (ungrouped).
Maintenant plaçons une variable dans le fichier inventories/staging/group_vars/all.yml ceci :
VARIABLE: "common"
PACKAGES_COMMON:
- python3
Relançons la commande ci-dessus :
ansible-inventory -i inventories/staging --graph --vars
@all:
|--@dbservers:
| |--stagehost3
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{VARIABLE = common}
|--@ungrouped:
| |--stagehost1
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{VARIABLE = common}
|--@webservers:
| |--stagehost2
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{VARIABLE = common}
|--{PACKAGES_COMMON = ['python3']}
|--{VARIABLE = common}
On retrouve bien la variable sur l’ensemble des hosts de notre inventaire.
Dans le fichier inventories/staging/webservers.yml ajouter ceci:
VARIABLE: "webserver"
PACKAGES_GROUP:
- nginx
Et dans le fichier inventories/staging/dbservers.yml ajouter ceci:
VARIABLE: "dbserver"
PACKAGES_GROUP:
- mysql
Relançons la commande :
ansible-inventory -i inventories/staging --graph --vars
@all:
|--@dbservers:
| |--stagehost3
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{PACKAGES_GROUP = ['mysql']}
| | |--{VARIABLE = dbserver}
| |--{PACKAGES_GROUP = ['mysql']}
| |--{VARIABLE = dbserver}
|--@ungrouped:
| |--stagehost1
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{VARIABLE = common}
|--@webservers:
| |--stagehost2
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{PACKAGES_GROUP = ['nginx']}
| | |--{VARIABLE = webserver}
| |--{PACKAGES_GROUP = ['nginx']}
| |--{VARIABLE = webserver}
|--{PACKAGES_COMMON = ['python3']}
|--{VARIABLE = common}
Maintenant dans le fichier inventories/staging/host_vars/stagehost1.yml ajouter ceci :
PACKAGES_HOST:
- net-tools
@all:
|--@dbservers:
| |--stagehost3
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{PACKAGES_GROUP = ['mysql']}
| | |--{VARIABLE = dbserver}
| |--{PACKAGES_GROUP = ['mysql']}
| |--{VARIABLE = dbserver}
|--@ungrouped:
| |--stagehost1
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{PACKAGES_HOST = ['net-tools']}
| | |--{VARIABLE = common}
|--@webservers:
| |--stagehost2
| | |--{PACKAGES_COMMON = ['python3']}
| | |--{PACKAGES_GROUP = ['nginx']}
| | |--{VARIABLE = webserver}
| |--{PACKAGES_GROUP = ['nginx']}
| |--{VARIABLE = webserver}
|--{PACKAGES_COMMON = ['python3']}
|--{VARIABLE = common}
Maintenant voyons comment concaténer les trois variables PACKAGES_COMMON, PACKAGES_GROUP et PACKAGES_HOST en une seule dans un playbook.
---
- hosts: all
gather_facts: false
tasks:
- name: my appender
ansible.builtin.set_fact:
PACKAGES: '{{PACKAGES_HOST | default ([]) + PACKAGES_COMMON}}'
- name: debug
ansible.builtin.debug:
var: PACKAGES, VARIABLE
Je ne demande pas la collecte des facts ici pour éviter de surcharger l’affichage.
Lançons le et comme mes hosts n’existe pas je demande une connection locale (-c local):
ansible-playbook -c local -i inventories/staging playbook.yml
On obtient bien le résultat attendu.
ok: [stagehost1] => {
"PACKAGES,VARIABLE": "(['net-tools', 'python3'], 'common')"
}
ok: [stagehost3] => {
"PACKAGES,VARIABLE": "(['mysql', 'python3'], 'dbserver')"
}
ok: [stagehost2] => {
"PACKAGES,VARIABLE": "(['nginx', 'python3'], 'webserver')"
}
Astuces
On a vu comment afficher les variables avec la commande ansible-inventory
mais
faisons également dans le playbook. Il suffit de remplacer PACKAGES dans la
partie debug par hostvars[inventory_hostname]. Relancez et vous remarquerez que
des informations essentielles vous sont fournis pour comprendre le
fonctionnement d’Ansible.
Pour finir ajoutez une machine stagehost4 dans le groupe [webservers] et affecter lui ceci dans son fichier host_vars:
VARIABLE: "titi"
PACKAGES_HOST:
- htop
Vous devez obtenir ceci :
ok: [stagehost1] => {
"PACKAGES,VARIABLE": "(['net-tools', 'python3'], 'common')"
}
ok: [stagehost2] => {
"PACKAGES,VARIABLE": "(['nginx', 'python3'], 'webserver')"
}
ok: [stagehost4] => {
"PACKAGES,VARIABLE": "(['htop', 'nginx', 'python3'], 'titi')"
}
ok: [stagehost3] => {
"PACKAGES,VARIABLE": "(['mysql', 'python3'], 'dbserver')"
Passez stagehost4 dans le group et regardez les valeurs, y a rien qui vous choque ?
On retrouve la propriété PACKAGE_GROUP avec nginx !!!
C’est avec ce genre de situation qu’on perd vite le contrôle.
Que se passe t’il ? C’est la précédence qui donne ce résultat. Le fait est que ansible lit la valeur des variables de l’hôte, pas du groupe. Autrement dit, il ne peut y avoir qu’une seule valeur pour une variable sur un hôte. Si vous voulez régler ce problème il faut surcharger la variable au niveau du host.
Quelques bonnes pratiques pour garder le contrôle
Pour les variables communes :
- Ne mettre dans l’inventaire que des variables globales propre à l’infrastructure;
- Pour les autres variables les mettre au niveau du playbook;
- Éviter autant que possible de mettre des machines dans plusieurs groupes en créer plutôt un troisième;
- Utiliser la commande
ansible-inventory --list
pour vérifier que le résultat obtenu est conforme à l’attendu;
Plus loin
Si vous voulez plus de tutorials Ansible je vous renvoie sur le billet de l'introduction à ansible