Mise à jour des collections Ansible avec Renovate
Dans la démarche DevOps, on parle bien d’automatisation pour éliminer les tâches chronophages et sans valeurs ajoutées pour pouvoir se concentrer sur celles qui donnent de la valeur à notre produit.
C’est dans cette optique que j’ai mis en place l’automatisation des tests de mise à jour des rôles et des dépendances de type python (ansible, ansible-lint, molecule, …) dans mes collections Ansible. Pourquoi j’utilise des collections Ansible ? Tout simplement pour rationaliser le code (éviter la répétition de code), mais aussi pour gérer efficacement les versions de toutes mes dépendances. J’utilise de multiples collections en les limitant à de simples objectifs :
- Faire la configuration de base des serveurs qui viennent d’être provisionné que ce soit avec Packer ou appliqué directement sur les serveurs.
- Faire l’installation d’un composant : nginx, varnish, apache2, java, python…
- Monter un projet en utilisant au maximum les collections plutôt que des rôles
- …
Ces collections utilisent toutes des rôles réalisant des tâches assez basiques et qui sont correctement versionnés. Je déconseille fortement d’encapsuler des rôles dans d’autres rôles et puis dans d’autres… A moins d’adorer passer des heures à chercher la source de l’erreur qui vous tombe dessus.
Structure des Groupes dans Gitlab
Je suis hyper maniaque et je passe mon temps à ranger mes affaires. Et c’est vrai aussi sous Gitlab. J’ai créé une arborescence Ansible me permettant de retrouver rapidement un rôle, une collection, un projet contenant un inventaire et quelques playbooks …
J’utilise la même arborescence sur mon pc, la seule différence se fait au niveau
des collections ou le dossier se nomme ansible_collections
et contenant un
sous-dossier du nom de votre namespace. Cela afin que les commandes
ansible-galaxy
fonctionnent. Je vous renvoie à mon explication sur les
collections Ansible
Écriture de notre collection Ansible
Pour mon exemple, je vais créer une collection ansible permettant de configurer un serveur avec tout ce qu’il faut.
Création de la structure de la collection
Créons notre collection base qui doit se trouver dans le répertoire
ansible_collections/<namespace>
:
Maintenant initialisons la structure de molecule
Vous devriez obtenir cette structure :
Créons quelques répertoires pour stocker nos playbooks, templates et nos fichiers :
Configuration de la collection Ansible
La configuration se fait via le fichier galaxy.yml :
La première partie est déjà configurée, il faut juste éditer les champs du dépôt, les collections en dépendances, de la documentation, des issues et des fichiers qui devront être exclues de l’archive de la collection.
Vous pouvez déjà lancer la commande de build de la collection pour en vérifier le contenu.
Configuration de molecule avec une image systemd
Nous allons utiliser un conteneur à base de debian11
permettant d’utiliser
systemd
. Pourquoi ? Pour tout simplement permettre de démarrer des services
comme sur des VM.
Copier ce contenu dans votre fichier molecule/default/molecule.yml
:
Pour éviter de multiplier les fichiers indiquant les rôles en dépendances, je le
place à la racine de ma collection. Je crée ensuite dans le répertoire
molecule/default
un lien pointant dessus :
Vous remarquerez que j’utilise le scm
de type git. Tout simplement pour pouvoir
renommer mon rôle en bootstrap plutôt qu’en stephrobert.bootstrap. Sinon le rôle
sera inaccessible depuis l’extérieur. Là pour y accéder, il faudra indiquer
steprobert.base.bootsrap
dans votre playbook.
Notre image est construite avec ce Dockerfile
qui doit se trouver aussi dans
le répertoire molecule/default
:
Vérifions que tout fonctionne :
Vous devriez au bout de quelques minutes obtenir un conteneur tournant en tache de fond :
Pour vous connecter dessus, il suffit de taper une des commandes suivantes :
Pour le moment le rôle ne s’est pas lancé puisque je n’ai pas encore créé mon playbook.
Ecriture du playbook
Nous allons écrire un playbook qui sera utilisé par molecule mais aussi lors de l’utilisation de la collection.
Dans le dossier playbooks
créer un fichier bootstrap.yml
et déposer ce contenu :
Pour que molecule fasse appel à notre playbook, il faut éditer le fichier converge.yml et remplacer le contenu par ceci
Relancez molecule et vous devriez voir l’installation des packages nécessaires au bon fonctionnement d’Ansible sur ma machine cible.
Ajoutons un autre rôle permettant de créer notre user admuser. Pour cela, je vais
utiliser un rôle en provenance de ma boutique claranet.users
. Editez le
fichier requirements.yml
et remplacez avec ceci :
Modifions le playbook bootstrap.yml
pour utiliser ce role :
Je vais en rester là, mais vous pourriez ajouter l’installation de sshd, de motd, de faire un peu d’hardening…
Ecriture des tests avec testinfra
On m’a demandé pourquoi j’utilise testinfra et pas Ansible pour faire mes tests ? Pour deux raisons :
- Je préfère utiliser un autre système que celui qui fait la configuration
- Je trouve plus simple d’écrire des tests en python qu’en Ansible.
Dans le dossier molecule/default/tests
éditez le fichier test_default.py
et
déposez-y ce contenu :
On lance les tests avec la commande molecule verify
:
Tous nos tests fonctionnent. Je rappelle que j’aurai dû faire du TDD en créant les tests puis en écrivant le code Ansible permettant de les résoudre.
Bon notre collection est prête à être envoyé sur notre dépôt, mais avant, attachons-nous à créer notre image Docker qui sera utilisé dans le Pipeline CI.
Construction de l’image Docker molecule
Dans cette image je vais mettre tout ce qui va permettre d’utiliser molecule : docker, ansible, ansible-lint, testinfra …
Le contenu des dépendances python requirements.txt
:
Ecriture d’une simple CI Gitlab
Pour notre pipeline d’intégration continue, nous allons utiliser trois étapes (stages gitlab) :
- Une étape de build
- Une étape de test : en fait le scenario test intègre tout
- Une étape de stockage de notre artefact (le tar.gz) sur Ansible Galaxy
En voici le contenu :
Tout est, si vous voulez valider localement votre CI, vous pouvez utiliser gcil. C’est ce que j’ai fait pour éviter les nombreux allers/retours.
Avant de pousser notre projet, il faut installer et configurer le runner. De mon
côté, je possède déjà une instance de gitlab-runner
sur une de mes machines de
mon homelab.
Ah oui, le stage push
ne sera lancé que sur la branche main. Renovate
va
créer des branches sur lequel les tests seront lancés, mais ce n’est que lorsque
le merge
sera fait que le push
s’effectuera.
Installation et Configuration du runner Gitlab
Dans un premier temps voyons comment installer un runner Gitlab-CI. Je vais utiliser un runner tournant sous Docker monté sur une machine Linux. Bien sûr, nous allons écrire et utiliser une collection ansible pour réaliser cette tâche.
Installation de Gitlab Runner
Je vais l’installer sur une Ubuntu 22.04, mais le script fonctionne pour la plupart des distributions :
Ajoutons le compte gitlab-runner aux sudoers sans mot de passe :
Ajoutez cette ligne à la fin du fichier :
Enregistrement du runner
Il faut se connecter avec le compte gitlab-runner .
Modifions la configuration pour ajouter des volumes et le passer le priviliged
à
true
, ça se trouve dans la section runners.docker
:
Enregistrer et relancer gitlab-runner :
Et finissons par ajouter le user gitlab-runner au groupe docker :
Reconnectez avec ce compte et tapez la commande docker ps
. Si tout est ok,
vous verrez la liste des containers vide.
Premier build sur notre runner
Tout est prêt pour lancer notre premier push dans notre dépôt nouvellement créé. Adaptez les commandes avec l’url de votre dépôt :
Nos trois stages passent avec succès !!!
Testons Renovate
J’ai déjà documenté l’utilisation dans ce billet.
Je vais simplement ajouter les projets de l’image et de la collection :
Allez, on lance deux pipelines renovate
! Un pour configurer le dépôt pour
renovate
et un second pour lancer réellement l’analyse des dépendances et
créez les branches en cas de maj disponibles.
Cool, renovate a bien détecté l’existence d’une nouvelle version du rôle base.
Fini de devoir redescendre le projet pour simplement checker si la collection fonctionne toujours après la mise à jour des dépendances. Sur des dizaines de rôles et une dizaine de collections ça en fait des heures de gagner. En cas d’erreur, il faudra tout de même apporter les corrections. Par contre, avec toutes ses heures gagnées, vous pourrez les consacrer à faire votre veille techno, travailler sur des projets à plus fortes valeurs ajoutés.
Pour éviter d’avoir trop de branches créées par renovate, donc éviter de faire
trop de bruit, vous pouvez grouper toutes les mises à jour dans une seule merge
request. Pour cela, il faut ajouter une règle dans le fichier renovate.json
comme ceci :
Vous obtiendrez une seule merge request portant le nom chore(deps): update all dependencies
.
Plus loin
Il ne reste plus qu’à écrire d’autres collections pour les midlleware en utilisant cette collection de base, des collections par projet utilisant la collection de base et celles des middlewares, à mettre en place renovate dessus. Après cela, vous pourrez gérer plus sereinement ceux-ci !
Vous pouvez retrouver tous les codes sources de ce projet dans les dépôts suivants :