Ansible - Les rôles
Pour cette quatrième partie, nous nous intéressons aux rôles Ansible.
Les rôles Ansible
Les rôles partagent de nombreux points communs avec les playbooks. En effet, ils utilisent les mêmes instructions et le même code déclaratif. La principale différence est qu’il s’agit d’un ensemble structuré de fichiers et de dossiers qui contiendront chacun une liste d’action précise.
Un rôle Ansible est constitué d’une hiérarchie de sous dossiers dans laquelle Ansible sait qu’il va trouver ce qu’il doit faire.
Exemple de structure d’un rôle. (exemple repris de la Documentation officielle)
site.yml
webservers.yml
fooservers.yml
roles/
common/
tasks/
handlers/
files/
templates/
vars/
defaults/
meta/
webservers/
tasks/
defaults/
meta/
Dans cet exemple :
site.yml
,webservers.yml
etfooservers.yml
sont des playbooks.roles/common
est un rôle nommé “common”roles/webservers
est un rôle nommé “webservers”
Comme vous pouvez le constater, le rôle common
et le rôle webservers
n’ont pas la même liste de dossiers. Pour qu’un rôle fonctionne, il faut à minima un des dossiers qui contient un fichier main.yml
. Les dossiers contiennent les éléments suivants :
tasks
: Les tâches qui seront jouées par le rôle. C’est la même écriture qu’un Playbook à l’exception qu’il n’y a pas besoin de la déclarationhosts
.handlers
: Les tâches liées aux handlers seront à déclarer dans ce dossier.defaults
: Contient les variables par défaut du rôle. Nous verrons un peu plus loin avec les group_vars / hosts_vars.vars
: Une autre section de variables qui a un niveau de priorité sur son application différente du defaults. Elles peuvent servir à surcharger le default.files
: Contient des fichiers statiques qu’Ansible peut déployer sur les hosts (exemple : un script).templates
: Contient des fichiers dynamiques qu’Ansible peut déployer sur les hosts (exemple : la conf Apache contextualisée selon le serveur).meta
: Contient des meta data pour le rôle : la license, les dépendances…
L’intérêt principal du rôle est de pouvoir écrire un code spécialisé sur une liste concrète et cohérente d’actions, et pouvant être réutilisé au travers d’appels via les Playbook. Vulgairement, il faut voir cela comme une fonction d’un langage de programmation par exemple.
Comprendre les interactions inventaire / hosts_vars / group_vars / vars / extra-vars
L’ordre de priorité des variables
Dans sa définition, le rôle peut posséder deux fichiers de variables : le default
et le vars
. Ansible possède un ordre de priorité pour assigner une valeur à une variable. Il s’agit de la liste suivante, triée du moins prioritaire au plus prioritaire :
- Les valeurs passées en ligne de commande (exemple :
-u user
pour l’utilisateur de connexion) - Le
defaults
d’un rôle - Le fichier d’inventaire ou le
group_vars
- Le
group_vars/all
d’un inventaire - Le
group_vars/all
d’un playbook - N’importe quel autre fichier dans
group_vars/*
d’un inventaire - N’importe quel autre fichier dans
group_vars/*
d’un playbook - Fichier d’inventaire ou
hosts_vars
- Les fichiers
hosts_vars/*
d’un inventaire - Les fichiers
hosts_vars/*
d’un playbook hosts_facts
etset_facts
mis en cache- Les variables définies dans le playbook
- Les variables demandées au prompt par un playbook
- Le fichier
vars
d’un playbook - Le fichier
vars/main.yml
d’un rôle - Les variables d’un
block
(type de tâche spécifique) - Les variables d’une tâche (instruction
vars:
sur une tâche) - Instruction
include_vars
- Instruction
set_facts
etregister
de variables - Les paramètres du rôle
- L’inclusion de paramètres
- L’argument
extra-vars
de la ligne de commande (il aura toujours la priorité absolue)
Concrètement, à titre personnel, je n’utilise pas tout cet ordre de priorité. J’applique généralement le suivant :
- Valeurs par défaut spécifiées dans
defaults
- Surchargées par le
group_vars
- Eventuellement surchargées par un
set_facts
dans le code si le besoin s’en fait sentir - Argument
extra-vars
si le besoin s’en fait sentir
La documentation Ansible possède une section sur les recommandations d’usage des variables.
Comment le lien avec les fichiers de variables est fait
C’est grâce à l’inventaire qu’Ansible sait comment charger quel fichier de variable et déduire selon son ordre de priorité. En effet, quand nous avons construit notre fichier hosts
dans l’article dédié, nous avions mis des noms de groupe. Par exemple webservers
pour les serveurs web, db
pour les bases de données, etc.
Les fichiers que vous mettez dans hosts_vars
et group_vars
peuvent être nommés selon un groupe de l’inventaire. Ainsi, le fichier group_vars/webservers.yml
pourra contenir des variables spécifiques pour les membres du groupe webservers
. La portée de ces variables ne concernera que les hosts qui en font partie. C’est la même chose pour le hosts_vars
.
Initialiser un rôle
Pour créer un rôle, vous pouvez créer un dossier pourtant son nom et créer l’arborescence attendue. Sinon vous pouvez utiliser la commande ansible-galaxy
qui va vous macher le taff :)
A minima, vous devez créer un dossier roles
dans votre répertoire de sources Ansible et vous placer dedans.
Ensuite, lancez la commande suivante :
$ ansible-galaxy init monrole --offline
- Role monrole was created successfully
Dans l’introduction nous avions vu la commande ansible-galaxy
dans la liste des commandes Ansible. Il s’agit de celle qui fait le lien avec le repository public de rôles maintenu par Ansible. L’argument --offline
permet d’initialiser le rôle directement sans qu’il ne cherche à se connecter aux dépôts.
Un dossier “monrole” vient d’être créé avec toute la structure de base.
monrole/
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Un rôle simple
Nous allons créer un rôle simple de déploiement Apache HTTPD avec installation du service avec la dernière version disponible et déploiement de la conf par Ansible. Nous testons au préalable si l’exécution est faite sur un Linux de famille Red Hat car le module yum
ne fonctionnera pas sur une Debian par exemple.
On initialise le rôle.
$ ansible-galaxy init webserver --offline
- Role webserver was created successfully
Dans roles/webserver/tasks/main.yml
:
---
- name: "Check host compatibility"
assert:
that: ansible_facts['os_family']|lower == 'redhat'
msg: "Unsupported OS, this role works only for Red Hat family"
- name: "Install apache httpd"
yum:
name: "{{ apache_rpm_name }}"
state: latest
- name: "Deploy httpd.conf"
template:
src: etc_httpd_conf_httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
owner: root
group: root
mode: '0644'
Dans roles/webserver/template/etc_httpd_conf_httpd.conf.j2
nous avons repris le fichier httpd.conf
standard d’Apache auquel nous avons variabilisé le port d’écoute.
(...)
# Change this to Listen on specific IP addresses as shown below to
# prevent Apache from glomming onto all bound IP addresses.
#
#Listen 12.34.56.78:80
Listen {{ apache_listen_port }}
#
# Dynamic Shared Object (DSO) Support
(...)
Dans roles/webserver/defaults/main.yml
nous spécifions le nom du package apache et le port par défaut.
---
apache_rpm_name: httpd
apache_listen_port: 80
De ce fait, en exécutant ce rôle sans surcharge, nous installerons le package httpd et écouterons le port 80 avec.
Si on veut surcharger le port pour un inventaire, par exemple la preprod nous pouvons mettre dans le fichier host_vars
de son inventaire une surcharge de la variable.
---
apache_listen_port: 81
Ansible mettra lors le port 81 pour ce groupe de hosts.
Exécuter un rôle
Un rôle ne peut s’exécuter en tant que tel, il est nécessaire de l’invoquer via un playbook. Pour continuer dans notre exemple, nous créons un playbook nommé deploy_httpd.yml
.
Dedans nous mettrons le code suivant, notez que l’instruction tasks
est remplacée par roles
:
---
- hosts: webservers
roles:
- webserver
On lance ce playbook avec l’inventaire cible, le rôle s’exécutera dessus en prenant comme contexte les éventuelles surcharges de variables dans hosts_vars
ou group_vars
.
$ ansible-playbook -i inventory/webservers/hosts deploy_httpd.yml
Cette méthode de lancement est la plus historique pour Ansible. Avec les versions 2.3 et 2.4, ils ont introduit la possibilité d’exécuter un rôle au travers d’une liste de tâches. Il s’agit des modules import_role
et include_role
.
---
- hosts: webservers
tasks:
- import_role:
name: role1
- include_role:
name: role2
Vous remarquerez que cette syntaxe se fait au travers de l’instruction tasks
.
import_role
et include_role
ont une légère nuance. En effet, leur façon d’exécuter le rôle sera respectivement statique (import) et dynamique (include). Une inclusion dynamique dans Ansible signifie que le code est lu durant l’exécution, là où Ansible lit la totalité du playbook avant de l’exécuter. Dans ce mode d’inclusion, les options des tâches ne s’appliqueront qu’à celles-ci et ne seront pas transmises à d’éventuelles sous-tâches.
Une inclusion statique signifie qu’Ansible va lire tout le code et les tâches parentes transmettront les options et variables qu’elles produisent aux tâches filles. C’est comme l’instruction export
dans un script bash pour une variable.
Un rôle ne sera exécuté qu’une seule fois. Si le nom du rôle est répété dans le playbook, les itérations suivantes seront ignorées à moins d’activer le paramètre allow_duplicates: true
dans le fichier meta/main.yml
. A noter cependant que le même rôle peut être rappelé plusieurs fois si celui-ci a des paramètres différents, avec l’instruction vars
notamment.
Les dépendances de rôles
Le but d’un rôle est d’être réutilisable. Pour éviter qu’il soit trop spécialisé, ou bien si on doit installer des objets communs sur les environnements, il est possible d’utiliser les dépendances.
Cela se gère dans le fichier meta/main.yml
avec l’instruction dependencies
.
Exemple, nous avons un rôle “common” qui créé des utilisateurs système prédéfinis avec un UID spécifique qui doit être commun peu importe le rôle applicatif du serveur. Nous avons un rôle de déploiement Apache HTTPD et un rôle de déploiement Tomcat.
Dans le fichier meta/main.yml
de ces deux rôles nous retrouverons alors l’instruction :
---
dependencies:
- role: common
Leurs playbooks seront :
deploy_apache.yml
---
- hosts: webservers
roles:
- webserver
deploy_tomcat.yml
---
- hosts: appservers
roles:
- tomcat
A l’exécution, ces deux playbooks lanceront le rôle common
car indiqué en dépendance des rôles webserver
et tomcat
.
Conclusion
Les rôles sont la partie la plus normée et réutilisable d’un code Ansible. Cet article a été un peu dense mais je me suis concentré sur l’essentiel pour vous présenter le fonctionnement d’un rôle et son concept. A partir de là, vous pourrez commencer à écrire vos propres rôles pour vous faire la main.
Le prochain article sur Ansible, et qui concluera cette série, vous proposera quelques liens utiles, et quelques conseils et bonnes pratiques.