Générer des livrables fiables avec Jenkins
Pour maximiser la compatibilité avec l’environnement de production, certaines applications ou exigences clients peuvent nécessiter l’utilisation d’une machine de génération spécifique. Cette approche permet de standardiser les versions des outils de génération et de s’assurer que les livrables fonctionneront de manière optimale sur les machines cibles.
Afin d’atteindre cet objectif, il est recommandé de développer et de générer les logiciels sur un système d’exploitation identique à celui utilisé en production. Pour ce faire, nous mettons généralement en place une machine dédiée, qui est équipée des outils nécessaires à la génération et au packaging des logiciels.
Industrialiser la génération des applications
Pourquoi industrialiser ?
Dans un contexte où l’optimisation des coûts et l’efficacité opérationnelle sont cruciaux, il est essentiel de limiter les interventions manuelles, surtout pour des tâches à faible valeur ajoutée. Si la génération d’une release est une étape indispensable, les étapes de génération intermédiaires le sont beaucoup moins. Néanmoins, il est important de s’assurer régulièrement que l’application est correctement compilée et empaquetée, en particulier dans un environnement où plusieurs développeurs contribuent simultanément au même code source.
Ainsi, l’utilisation d’une machine dédiée qui nécessite l’intervention manuelle des développeurs pour créer les livrables va à l’encontre de cet objectif. Cela pose encore plus de problèmes dans les projets complexes, où la génération peut prendre entre 15 et 30 minutes !
Quelles-sont les solutions ?
Heureusement, cette problématique n’a pas échappé à la communauté informatique qui a créé diverses solutions pour répondre à ce besoin. Certaines sont propriétaires et imposent une exposition sur Internet, comme GitHub action ou CircleCI. D’autres sont plus respectueuses des politiques de confidentialité, et permettent d’héberger les solutions directement sur une infrastructure privée. Nous retrouvons alors les produits comme GitLab ou Jenkins. Quelle que soit la situation, ces solutions permettent aux équipes de développement de définir des pipelines qui automatisent diverses opérations, y compris le processus de génération.
Intégration avec Jenkins
L’infrastructure privée sur laquelle notre équipe intervient propose les outils GitLab et Jenkins. Cependant, la DSI privilégie Jenkins pour l’exécution de pipelines. Ils mettent donc à disposition des équipes projets des nœuds génériques. Cependant, la criticité du logiciel que nous développons nécessite une machine de génération avec un système d’exploitation et des outils précis.
Pour rentrer dans les détails, nous déployons l’application sur une machine Linux RedHat 8 64 bits, et elle se décompose en trois modules :
- Un module écrit en C pour réaliser des calculs lourds, ou pour appeler des API système ;
- Un second écrit en Java capable de communiquer avec le module C, et qui réalise les traitements de gestion ;
- Un dernier pour le frontend écrit en JavaScript, HTML et CSS. Il expose à un tableau de bord qui présente les résultats de traitement des processus C et Java.
L’application doit donc être générée manuellement sur une machine virtuelle (VM) où sont installés le système d’exploitation RedHat 8, et les outils de compilation de chaque module (make, gcc, maven, node).
Afin d’automatiser les tâches de génération sur cette machine, nous devons déclarer cette VM comme slave Jenkins. Elle pourra alors exécuter les pipelines de génération spécifiques à ce logiciel.
Configuration de la VM
Dans un primer temps, nous allons configurer la VM pour qu’elle puisse s’interfacer avec le master Jenkins.
Installation de Java 11
Notre serveur Jenkins (le nœud master) s’appuie sur la version 11 de Java. L’agent Jenkins du nœud slave requière donc cette même version de Java pour exécuter le jar agent.jar.
Sur la VM, bien qu’il soit possible d’installer Java 11 depuis les dépôts de paquets, il est important de conserver l’accès à Java 8, déjà installé et nécessaire pour le build de l’application.
Si Java 11 est installé via dnf, Linux utilisera cette version par défaut pour les builds, au lieu de Java 8, qui est requis.
Nous devons donc réaliser une installation alternative de Java afin que l’agent puisse l’utiliser, tout en gardant la version 8 comme version par défaut pour la commande java.
Pour cela, il est possible d’installer un openjdk 11 accessible depuis le site officiel. La procédure à réaliser est la suivante :
$ sudo su -
$ cd /opt/
$ curl -L https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz \
-o openjdk-11.0.2_linux-x64_bin.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 178M 100 178M 0 0 35.9M 0 0:00:04 0:00:04 --:--:-- 37.8M
$ tar -xzf openjdk-11.0.2_linux-x64_bin.tar.gz
$ rm openjdk-11.0.2_linux-x64_bin.tar.gz
$ /opt/jdk-11.0.2/bin/java -version <1>
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)Le binaire java est fonctionnel.
User Jenkins
Afin de limiter les droits des jobs Jenkins sur la VM, nous allons créer un utilisateur jenkins ayant des droits limités à l’exécution des jobs dans son propre home.
$ sudo su -
$ groupadd jenkins <1>
$ adduser -g jenkins --home /home/jenkins --shell /bin/bash jenkins <2>Création du groupe jenkins, qui est nécessaire à la commande adduser.
Ajout de l’utilisateur jenkins en spécifiant son home.
Déclarer l’agent
Création du nœud
La déclaration d’un agent Jenkins slave est réalisée via l’interface d’administration du serveur master. Nous devons donc nous authentifier avec un compte ayant les droits d’administration.
- Accéder à l’interface de gestion des nœuds : Administrer Jenkins > Gérer les nœuds.
- Cliquer sur le bouton « New Node ».
- Donner un nom pertinent à l’agent (par exemple,
my-projet-vm-name-agent). - Sélectionner le type « Permanent Agent ».
- Cliquer sur « créer ».
Configuration du nœud
Une fois créé, l’interface Jenkins affiche le formulaire de configuration du slave Jenkins. Configurer les éléments importants suivants :
- « Number of executors » : il est conseillé de déclarer un exécuteur par CPU (sur Linux, vous pouvez trouver l’information dans
/proc/cpuinfo). - « Répertoire de travail du système distant » : Nous devons déclarer
/home/jenkins(le home de l’utilisateurjenkins). - « Etiquettes » : Ce paramètre est optionnel, et il spécifie un label commun qui autorise l’utilisation d’un exécuteur sur tout slave appartenant à un groupe de VMs similaires.
- « Utilisation » : Dans notre cas, les builds sont spécifiques à une application, j’utilise donc « Réserver ce nœud uniquement pour les jobs qui lui sont attachés ».
- « Méthode de lancement » : Dans mon cas, je choisis « Launch agent by connecting it to the controller ». Sachez que cette option influence la finalisation de la procédure de configuration du slave.
- « Disponibilité » : J’utilise « Maintenir l’agent activé le plus longtemps possible », car la VM est toujours allumée.
- Cliquer sur « Enregistrer ».
Installation de l’agent
Déploiement de l’agent
Une fois le nœud configuré, la page de gestion des nœuds nous indique que notre nouveau slave existe. La croix rouge indique qu’il est déconnecté, cela signifie qu’il est encore inactif.
Pour connecter ce nouveau nœud, nous devons y cliquer dessus. Jenkins nous fournit alors la procédure à exécuter pour connecter l’agent.
Connectons-nous à la VM, et exécutons les commandes suivantes :
$ sudo su -
$ mkdir /opt/jenkins-agent <1>
$ cd /opt/jenkins-agent
$ curl -sO https://${SERVER_NAME}/jnlpJars/agent.jar <2> <3>
$ echo "${SECRET}" > secret-file <4>
$ chown -R jenkins:jenkins /opt/jenkins-agent /opt/jdk-11.0.2/ <5>Le dossier /opt/jenkins-agent me permet de gérer indépendamment les fichiers liés à l’agent.
Remplacer ${SERVER_NAME} par l’adresse HTTP de votre master Jenkins.
Si votre serveur est exposé sur le port HTTPS avec un certificat auto-signé, vous pouvez ajouter l’option -k à la commande curl.
Remplacer ${SECRET} par le secret mentionné sur l’interface d’administration de Jenkins.
Penser à modifier les droits pour permettre à l’utilisateur jenkins d’utiliser les fichiers des deux répertoires.
Tester l’agent Jenkins
Avant de finaliser l’installation de l’agent, nous allons effectuer un premier test pour s’assurer que l’agent fonctionne correctement. Pour cela, nous pouvons exécuter la commande fournie sur l’interface d’administration Jenkins. Dans notre cas, nous devons utiliser la commande faisant référence au fichier secret-file que nous avons créé.
$ su - jenkins <1>
$ cd /opt/jenkins-agent/
$ /opt/jdk-11.0.2/bin/java -jar agent.jar \
-jnlpUrl https://${SERVER_NAME}/manage/computer/${AGENT_NAME_URLENCODED}/jenkins-agent.jnlp \ <2>
-secret @secret-file -workDir "/home/jenkins" <3>
...
INFO: ConnectedExécutons la commande avec l’utilisateur jenkins que sera responsable de l’exécution des jobs.
N’oubliez pas de remplacer ${SERVER_NAME} et ${AGENT_NAME_URLENCODED} par vos données.
Nous utilisons ici, la commande java faisant référence à celui installé en version 11.
L’agent indique qu’il est « Connected », nous pouvons donc considérer que notre installation est opérationnelle !
Gérer l’agent grâce à systemd
Nous allons nous appuyer sur systemd, le gestionnaire de service Linux, pour gérer le lancement et l’arrêt de l’agent. Cela nous permettra notamment de lancer automatique l’agent au boot de la VM.
Pour cela, nous allons créer le fichier de configuration du service sous /opt/jenkins-agent/jenkins-agent.service. Le contenu du fichier doit être le suivant :
[Unit]
Description=Jenkins Agent
After=network.target
[Service]
User=jenkins
Group=jenkins
WorkingDirectory=/opt/jenkins-agent
ExecStart=/opt/jdk-11.0.2/bin/java -jar agent.jar -jnlpUrl ${JNLP_URL} \ <1>
-secret @secret-file -workDir "/home/jenkins"
Restart=always
[Install]
WantedBy=multi-user.targetRemplacer ${JNLP_URL} par l’URL spécifique à votre agent.
Le fichier de configuration du service est maintenant prêt, nous allons l’activer et le lancer via les commandes suivantes :
$ systemctl enable /opt/jenkins-agent/jenkins-agent.service <1>
Created symlink /etc/systemd/system/multi-user.target.wants/jenkins-agent.service → /opt/jenkins-agent/jenkins-agent.service.
Created symlink /etc/systemd/system/jenkins-agent.service → /opt/jenkins-agent/jenkins-agent.service.
$ systemctl daemon-reload
$ systemctl start jenkins-agent.service <2>
$ systemctl status jenkins-agent.service
● jenkins-agent.service - Jenkins Agent
Loaded: loaded (/opt/jenkins-agent/jenkins-agent.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2024-07-16 12:39:19 UTC; 39s ago
Main PID: 53386 (java)
Tasks: 34 (limit: 22920)
Memory: 98.2M
CGroup: /system.slice/jenkins-agent.service
└─53386 /opt/jdk-11.0.2/bin/java -jar agent.jar -jnlpUrl ... -workDir /home/jenkins
... java[53386]: INFO: ConnectedCette commande permet d’activer le service pour être automatiquement lancé au boot du système.
Cette commande lance notre agent immédiatement.
Conclusion
Dans un contexte où la sécurité et l’isolation des processus sont des préoccupations majeures, l’utilisation d’une machine dédiée est parfois indispensable pour la génération de logiciels. Lorsque l’équipe de développement souhaite adopter une approche DevOps, l’intégration et la livraison continue sont des pratiques centrales qui requièrent la mise en place de pipelines automatisés.
La procédure précédente détaille comment transformer une machine Linux dédiée à la génération, en un slave Jenkins. Cet agent est capable de répondre aux attentes d’automatisation d’une équipe projet pour laquelle les contraintes de génération sont importantes.
Cependant, l’utilisation de Jenkins pour ajouter des agents peut s’avérer difficile, car elle nécessite une collaboration étroite avec l’administrateur du master Jenkins. Cette problématique est moins prononcée avec d’autres solutions telles que les GitLab Runner, qui offrent une plus grande autonomie dans la gestion des agents.
En conclusion, il est essentiel d’évaluer l’opportunité d’interfacer des machines spécifiques avec un ordonnanceur. Un tel système peut contribuer à l’automatisation d’une partie du cycle de livraison d’une application. La décision d’intégrer cette technologie doit être prise en considérant les objectifs spécifiques de l’organisation, et les compétences techniques de l’équipe en matière de pratiques DevOps.
