Ansible est un fabuleux outil d’automatisation, mais la gestion de son environnement d’exécution (version utilisée de l’outil ou de Python, librairies, dépendances système) se révèle parfois un véritable casse-tête… qui est désormais résolu par l’introduction des « Ansible Environnements Execution ». Explication détaillée de cette fonctionnalité dans cet article !
Les environnements d’exécution pour Ansible (execution environments) sont une manière d’isoler l’exécution de l’outil à l’aide de conteneurs. Ceux-ci permettent de fournir l’ensemble des dépendances que requiert le bon déroulement d’un playbook. Celles-ci peuvent être liées au système, comme une version précise de Python, ou composées d’un groupement de collections (extensions pour Ansible). Chaque environnement d’exécution permet de créer une image (de conteneur) dédiée à l’exécution d’un playbook (et rien d’autre).
Afin de bien comprendre la manière dont cette solution logicielle s’intègre au sein de l’écosystème Ansible, nous allons étudier, sommairement, l’ensemble des briques qui la forment, soit :
- Ansible Runner - un outil en ligne de commandes qui permet d’exécuter les playbooks Ansible sans installer intégralement l’outil et ses dépendances.
- Ansible Builder - un utilitaire qui permet de construire une image de conteneur (Docker ou Podman) pour exécuter un playbook à l’aide d’Ansible Runner.
- Execution Environments - l’intégration des deux précédents outils au sein d’AWX (ou Ansible Automation Platform).
1. Ansible Runner
En essence, Ansible Runner est la modularisation de la partie d’Ansible responsable d’exécuter les tâches de ses playbooks. Dans les faits, il est simplement un sous-ensemble de la commande ansible-playbook. Comme Ansible Runner peut s’exécuter au sein d’un conteneur, il est aisé de l’embarquer et d’éviter ainsi de déporter la complexité d’installation de l’outil et de ses dépendances Python sur les systèmes cibles.
En outre, Ansible Runner regroupe les résultats et informations associées à ses exécutions. Sa particularité principale est de fournir une interface commune qui n’évolue que très peu dans le temps alors que le reste d’Ansible continue lui de changer et de s’enrichir.
Il existe trois manières d’interagir avec cet outil. La première est l’utilisation en ligne de commandes de celui-ci, la seconde est par l’intermédiaire d’un conteneur et la troisième sous forme d’une librairie Python, afin d’être exploitée au sein d’un programme ou script du même langage.
1.1 Utilisation en ligne de commandes
Dans le cadre de cet article, nous allons seulement étudier l’utilisation d’Ansible Runner en ligne de commandes, afin de nous permettre de bien comprendre son fonctionnement.
Son installation se fait relativement aisément sur une distribution Linux moderne. À titre d’exemple, voici comment installer celui-ci sur un système sous Fedora (ou utilisant des paquets RPM et l’outil de gestion de paquets logiciels dnf) :
Si cet outil a été construit pour être exécuté à l’aide de commandes, il n’est pas réellement destiné à être utilisé manuellement. Avant tout, il a pour fonction d’être invoqué au sein d’un processus isolé. Néanmoins, à titre d’exemple de son fonctionnement, nous allons exécuter un simple playbook avec Ansible Runner.
Voici le contenu du playbook que nous allons exécuter :
Ci-dessous, le résultat de son exécution à l’aide d’Ansible Runner :
Si Ansible Runner est destiné à être utilisé par des processus plus que par des êtres humains, il propose néanmoins une fonctionnalité fort pratique qui mérite d’être mentionnée. En effet, de par sa nature, il est capable d’exécuter un rôle Ansible sans requérir la mise en place d’un playbook. Lors de son invocation, l’outil va générer pour son utilisateur celui-ci afin de pouvoir exécuter directement le rôle.
De manière similaire, on peut tirer profit d’Ansible Runner afin d’exécuter un module Ansible directement, sans utiliser un playbook :
Ansible Runner étant entièrement paramétrable par le processus qui l’invoque, il dispose d’une quantité importante d’options. Cependant, pour faciliter sa mise en place et son utilisation au sein d’Ansible, tous ces arguments sont aussi définissables par l’intermédiaire d’une arborescence de fichiers.
À noter qu’à la fin de son exécution, Ansible Runner produit également une arborescence de fichiers contenant les nombreuses données sur le déroulement de celle-ci. On peut y trouver le résultat de la commande et d’autres informations destinées à être consommées par le processus à la source de son invocation :
Il nous faut aussi mentionner que l’outil a plusieurs modes d’exécution. L’option run exécute directement Ansible Runner, et attend la fin du processus avant de rendre la main. À l’inverse, l’option start démarre l’outil en tâche de fond, et crée un fichier PID associé à son exécution. Pour interrompre celle-ci, on peut alors utiliser l’option stop. Une option complémentaire is-alive permet de vérifier que le processus s’exécute toujours.
Avec cette revue de haut niveau du fonctionnement d’Ansible Runner, nous sommes maintenant prêts à aborder la suite, c’est-à-dire son usage au sein d’un conteneur.
2. Encapsuler l’exécution d’Ansible Runner à l’aide d’Ansible Builder
Maintenant que nous avons vu dans les grandes lignes l’utilisation d’Ansible Runner, nous allons étudier un autre outil nommé Ansible Builder. Celui-ci va nous permettre d’isoler l’exécution du précédent au sein d’un conteneur.
Notons tout de suite qu’il est bien sûr possible de concevoir un conteneur afin d’encapsuler l’exécution d’Ansible Runner à l’aide des outils habituels. Cependant, cet outil facilite considérablement la tâche en générant pour nous le fichier Containerfile.
Lors de son exécution, Ansible Builder observe le processus suivant :
- analyse et validation du fichier de définition fourni ;
- création d’un fichier Containerfile ;
- construction de l’image à partir de celui-ci à l’aide de Podman.
Commençons par installer Ansible Builder qui, à l’inverse du précédent, ne dispose pas d’un paquet logiciel sous forme de RPM. Il nous faut donc recourir à l’utilisation de l’utilitaire python PIP.
Ansible Builder installé, il nous faut lui fournir un fichier de définition, qui lui spécifie comment construire l’image dont nous avons besoin. Ce fichier respecte les contraintes du format YAML et il se compose de plusieurs sections.
Si la fonction et le sens de la plupart de ces sections sont évidents, nous allons expliciter les plus importantes d’entre elles à l’aide de l’exemple suivant :
La section intitulée build_arg_defaults: permet de spécifier les arguments éventuellement requis pour l’étape de construction. L’attribut ansible_config: se passe de commentaires, il permet simplement de fournir, si nécessaire, le chemin vers le fichier de configuration d’Ansible en lui-même.
La section la plus importante est bien sûr celle nommée dependencies:, car c’est celle-ci qui définit les chemins vers les descripteurs des dépendances requises. Un playbook Ansible est, en fin de compte, un logiciel relativement complexe dont le bon fonctionnement dépend de la mise en place de beaucoup d’autres éléments. Ces descripteurs permettent donc d’indiquer à Ansible Builder tous les éléments requis pour que son exécution se déroule sans incident.
Dans cette section, on peut indiquer les collections Ansible nécessaires pour l’exécution de celui-ci, soit ses dépendances de haut niveau. Mais, l’on peut aussi spécifier les bibliothèques Pythons exigées par le playbook ou même les paquetages logiciels supplémentaires requis, soit des dépendances de bas niveau.
Le format du descripteur spécifiant les collections Ansible requises suit la syntaxe usuelle et déjà utilisée au sein du projet Ansible :
Le descripteur de dépendances Python est un simple fichier texte. Il liste celles-ci, une par ligne, et accepte une syntaxe intuitive pour indiquer les versions requises :
De manière pratique, ce format suit la même syntaxe que celle utilisée par la sortie standard de la commande pip freeze. On peut donc ainsi facilement générer ce fichier à partir de l’exécution de celle-ci.
Il est également possible d’indiquer un répertoire relatif à celui où s’exécute Ansible Builder et par ce biais embarquer des librairies Python qui ne seraient pas disponibles en ligne.
Enfin, le descripteur de dépendance système suit le format de l’outil bindep. Il permet d’installer celles-ci, si cette étape n’est pas déjà garantie par les collections embarquées avec le playbook. Là aussi, on peut spécifier un chemin relatif afin d’assurer le déploiement d’un paquet qui ne sera pas disponible en ligne.
Tous ces éléments proprement définis dans notre fichier image-definition.yml, nous pouvons maintenant exécuter la commande Ansible Builder :
Notez que, par commodité, la commande crée le fichier Containerfile et l’image associée. Il est possible de ne générer que le fichier de description afin de construire l’image à l’aide des outils usuels ou simplement sur un autre système :
Ci-dessous, le fichier Containerfile généré par Ansible Builder :
L’image associée à ce fichier Containerfile a aussi été générée :
On peut également directement exécuter celle-ci afin de vérifier la conformité du cadre d’exécution spécifié :
Avec tous ces éléments en place, Ansible Runner pour l’exécution et le conteneur défini et construit par Ansible Builder, nous allons pouvoir maintenant étudier comment mettre en place cet environnement d’exécution, à l’aide de la plateforme AWX, qui est le centre névralgique de toute infrastructure Ansible.
3. Mise en place d’un environnement d’exécution
Les environnements d’exécution sont conçus pour fournir un cadre cohérent, reproductible, portable et aisément partageable. Ainsi, le playbook sera exécuté de manière identique par AWX que sur un autre système utilisant le même cadre d’exécution.
Ceci facilite grandement le développement d’automatisations et de contenus pour Ansible qui sont destinés à être exécutés par AWX ou son équivalent supporté par Red Hat, Ansible Automation Platform. Plus spécifiquement, le terme d’environnement d’exécution dans le cadre d’Ansible Runner désigne le conteneur qui exécute Ansible à l’aide de celui-ci.
Notons au préalable que, comme tous les éléments gérés par AWX, les environnements d’exécution peuvent être manipulés de manière globale ou être liés à une organisation. Mais, dans tous les cas, l’utilisateur doit avoir le niveau de privilèges requis sur le système afin de pouvoir les utiliser. Ainsi, les environnements d’exécution liés à une organisation nécessitent les droits d’administrateur pour exécuter le playbook au sein de celui-ci.
Maintenant que nous avons construit notre environnement d’exécution, il ne reste plus qu’à le déployer au sein de AWX. La première étape consiste simplement à accéder à la section dédiée aux environnements d’exécution au sein de l’outil de gestion de l’infrastructure Ansible.
Ceci est illustré par la Figure 1.
Une fois dans cette section de l’interface graphique, il suffit de cliquer sur le bouton Ajouter afin de spécifier un nouvel environnement d’exécution (Figure 2).
Ceci nous redirige naturellement vers un formulaire, qui va nous permettre d’indiquer les quelques éléments nécessaires au déploiement de cet environnement d’exécution (Figure 3).
On note qu’il existe trois manières de synchroniser un environnement d’exécution avec sa source. Au choix, on peut :
- Toujours effectuer l’opération Pull afin de télécharger systématiquement l’image la plus récente pour le conteneur.
- Ne jamais réaliser cette opération pour utiliser le dernier conteneur récupéré.
- Ou encore, ne rien spécifier pour cette opération.
Toutes ces étapes accomplies, nous avons maintenant à notre disposition un nouvel environnement d’exécution. On peut désormais l’associer à n’importe quelle tâche déjà définie au sein de AWX, comme illustré sur la Figure 4.
Conclusion
À l’aide d’un simple conteneur encapsulant l’exécution d’une partie du moteur Ansible, les environnements d’exécution permettent de commodément isoler dans un processus dédié et surtout dans un environnement conforme au besoin de la tâche que l’on souhaite exécuter. C’est donc un outil aisé d’utilisation qui encapsule avec succès un nombre d’opérations relativement complexes afin de faciliter autant que possible sa mise en place et ceci tout en garantissant une exécution d’Ansible encore plus stable et robuste.