Maîtriser l'optimisation des exécuteurs Jenkins pour des builds plus rapides
Les exécuteurs Jenkins sont les unités fondamentales d'exécution des tâches, déterminant le nombre de jobs ou d'étapes qu'un agent (nœud) ou le contrôleur (maître) peut exécuter simultanément. Une mauvaise configuration des exécuteurs est l'une des causes les plus courantes de pipelines CI/CD lents, entraînant de longues files d'attente de builds, des conflits de ressources et une perte de temps pour les développeurs.
Ce guide propose des stratégies expertes pour calculer, configurer et surveiller les exécuteurs Jenkins. En ajustant efficacement ces ressources, vous pouvez maximiser le débit, réduire la latence des builds et garantir que votre système CI/CD fonctionne avec une efficacité maximale, accélérant considérablement le cycle de livraison.
Comprendre le modèle d'exécuteur Jenkins
Un exécuteur est essentiellement un emplacement pour l'exécution d'un job. Lorsqu'un build est déclenché, Jenkins l'attribue à un exécuteur disponible sur le nœud approprié. Le nombre total d'exécuteurs sur tous les nœuds définit la concurrence maximale du système.
Exécuteurs sur le contrôleur (Maître)
Dans les architectures Jenkins modernes, le contrôleur doit principalement gérer l'orchestration, la planification et l'interaction avec l'interface utilisateur. La meilleure pratique dicte de définir le nombre d'exécuteurs sur le contrôleur à 0. Si vous devez exécuter de petits jobs administratifs peu gourmands en ressources sur le contrôleur, utilisez un maximum de 1 ou 2 exécuteurs. L'exécution de builds lourds sur le contrôleur risque d'entraîner une instabilité et une dégradation des performances pour l'ensemble de l'environnement Jenkins.
Exécuteurs sur les nœuds agents
Les nœuds agents (souvent appelés "esclaves") sont des machines ou des conteneurs dédiés où le travail de build réel est effectué. Ces nœuds doivent être configurés avec la majorité des exécuteurs de votre système. Le nombre correct d'exécuteurs par agent est crucial et dépend entièrement des ressources de l'agent et de la nature des tâches qu'il effectue.
Calcul du nombre optimal d'exécuteurs
Déterminer le nombre idéal d'exécuteurs n'est pas une formule universelle ; cela nécessite d'analyser le type de travail effectué (limité par les I/O ou par le CPU).
1. La règle du "CPU-bound" (Recommandation par défaut)
Si vos builds sont principalement CPU-bound (par exemple, compilation lourde, tests unitaires complexes, traitement d'images), le nombre optimal d'exécuteurs devrait généralement correspondre étroitement au nombre de cœurs CPU disponibles afin d'éviter la surcharge due au changement de contexte (thrashing).
- Formule :
Exécuteurs = Nombre de cœurs CPU
2. L'ajustement "I/O-bound"
Si vos builds sont principalement I/O-bound (par exemple, interactions prolongées avec des bases de données, transferts réseau, téléchargements/téléversements de fichiers étendus, résolution de dépendances comme Maven/npm), le processeur peut passer un temps significatif à attendre des données. Dans ce scénario, vous pouvez souvent utiliser en toute sécurité plus d'exécuteurs que de cœurs physiques.
- Formule :
Exécuteurs = (Nombre de cœurs CPU) * 1,5à(Nombre de cœurs CPU) * 2
⚠️ Avertissement : Les limites de la concurrence
Bien qu'augmenter le nombre d'exécuteurs améliore le débit, dépasser la mémoire ou la capacité d'E/S du nœud entraînera des rendements décroissants. Tous les jobs en cours d'exécution partagent la RAM totale disponible et la vitesse du disque. La surcharge du nœud provoque des temps d'attente I/O élevés et une collecte de déchets excessive, rendant les jobs individuels plus lents, même si la concurrence globale est plus élevée.
Scénario d'exemple pratique
Considérons un agent avec 8 cœurs CPU et 16 Go de RAM :
- Scénario A (compilation Java/C++ gourmande en CPU) : Commencez avec 8 exécuteurs. Surveillez l'utilisation du CPU. Si l'utilisation soutenue est élevée (90 % et plus), 8 est optimal. Si elle diminue pendant les attentes de compilation, vous pourriez essayer 10.
- Scénario B (téléchargements de dépendances/tests gourmands en I/O) : Commencez avec 12 exécuteurs (8 * 1,5). Surveillez le temps d'attente I/O. Si le temps d'attente I/O est faible, envisagez de passer à 16.
Stratégies de configuration pour des performances optimales
Le nombre d'exécuteurs est géré au niveau de la configuration du nœud dans Jenkins.
1. Configuration statique de l'agent
Pour les agents persistants, vous définissez manuellement le nombre d'exécuteurs lors de la configuration du nœud.
Étapes (UI Jenkins) :
1. Naviguez vers Gérer Jenkins -> Gérer les nœuds et les clouds.
2. Sélectionnez le nœud agent spécifique et cliquez sur Configurer.
3. Dans la section Propriétés du nœud, définissez le champ # d'exécuteurs en fonction de votre calcul.
2. Utilisation du parallélisme de pipeline
Jenkins moderne utilise des pipelines déclaratifs ou scriptés (Jenkinsfile). L'optimisation des exécuteurs est souvent réalisée au sein d'un seul job en divisant le travail en étapes parallèles. Cela utilise plusieurs exécuteurs disponibles sur un ou plusieurs agents simultanément.
Si un seul job est défini avec deux étapes parallèles, il consomme deux emplacements d'exécuteur (un pour chaque branche parallèle) jusqu'à ce que ces branches se terminent.
// Exemple de Jenkinsfile utilisant l'exécution parallèle
pipeline {
agent { label 'build-server' }
stages {
stage('Setup') { /* ... */ }
stage('Build and Test') {
parallel {
stage('Build Backend') {
steps { sh './gradlew build' }
}
stage('Run Frontend Tests') {
steps { sh 'npm test' }
}
}
}
stage('Deploy') { /* ... */ }
}
}
3. Mise à l'échelle dynamique (agents cloud)
Pour les environnements utilisant des fournisseurs de cloud (EC2, Kubernetes, Azure) via des plugins comme Kubernetes Plugin ou EC2 Plugin, les limites statiques d'exécuteurs sont moins pertinentes. Au lieu de cela, vous définissez des limites d'instances ou des modèles de pods.
- Kubernetes : Les exécuteurs sont généralement définis à 1 par pod/conteneur, car le pod lui-même est jetable et dimensionné précisément pour le job. L'objectif d'optimisation se déplace vers le provisionnement rapide de nouveaux pods lorsque la file d'attente s'allonge, plutôt que la gestion des emplacements sur une machine persistante.
- EC2 : Les exécuteurs sont généralement définis à 1, et la capacité est mise à l'échelle en démarrant de nouvelles instances EC2 temporaires lorsque la demande l'exige.
4. Limitation spécifique au job
Pour éviter les conflits de ressources entre des jobs très exigeants, utilisez le plugin Throttle Concurrent Builds ou les outils de pipeline natifs. Cela garantit qu'un nombre spécifique et limité d'instances d'un job particulier peuvent s'exécuter à la fois, quelle que soit la disponibilité globale des exécuteurs.
Identifier et résoudre les goulots d'étranglement
L'optimisation nécessite une surveillance continue. Vous avez besoin de visibilité sur les raisons pour lesquelles les jobs sont en attente et sur les points de surcharge du système.
Métriques clés à surveiller
| Métrique | Indication de goulot d'étranglement |
|---|---|
| Longueur de la file d'attente | Trop peu d'exécuteurs au total. Les jobs attendent des emplacements. |
| Temps d'attente moyen dans la file | Des valeurs élevées signifient que les ressources sont rares ou mal étiquetées. |
| Utilisation du CPU de l'agent | Une utilisation soutenue à 100 % suggère un nombre insuffisant de cœurs pour le nombre d'exécuteurs actuel (CPU-bound). |
| Temps d'attente I/O du disque de l'agent | Des temps d'attente élevés indiquent que les processus I/O-bound sont en forte concurrence pour l'accès au disque. |
| Utilisation de la mémoire d'échange de l'agent | Le nœud manque de RAM, entraînant un effondrement des performances. Diminuez les exécuteurs ou augmentez la mémoire. |
Dépannage pratique
- Analyser les jobs en attente : Vérifiez la file d'attente des builds de Jenkins. Si les jobs attendent constamment une étiquette spécifique, ce groupe d'agents correspondant a besoin de plus de capacité (plus d'agents ou plus d'exécuteurs par agent).
- Examiner les journaux des nœuds : Recherchez les messages d'erreur liés aux limitations de ressources ou aux performances de disque lentes.
- Augmenter la spécificité des agents : Utilisez les étiquettes de manière extensive. Dédiées certains agents à forte ressource (par exemple, 64 Go de RAM) pour les jobs gourmands en mémoire et les agents à faible ressource pour les jobs simples, garantissant la disponibilité des ressources en cas de besoin.
Bonnes pratiques pour la gestion des exécuteurs
- Évitez le sur-provisionnement : Bien qu'il soit tentant de définir un nombre très élevé d'exécuteurs, un changement de contexte excessif ralentit tous les jobs en cours d'exécution. Ajustez de manière itérative : augmentez de 2, surveillez pendant une semaine, puis ajustez à nouveau.
- Utilisez le nettoyage des ressources : Assurez-vous que les espaces de travail sont nettoyés régulièrement (
cleanWs()dans le pipeline) pour libérer les I/O disque, ce qui affecte directement l'efficacité de l'exécuteur. - Maximisez l'utilisation du cache : Employez la mise en cache des builds (par exemple, dépôts Maven/Gradle partagés, mise en cache des couches Docker) pour réduire les demandes d'I/O de la résolution de dépendances, transformant efficacement les jobs I/O-bound lents en jobs plus rapides et légèrement moins exigeants, vous permettant d'exécuter plus d'exécuteurs en toute sécurité.
- Isolation du maître : Réitérez l'importance de définir les exécuteurs du maître à 0 ou 1. Si le maître tombe en panne en raison d'un épuisement des ressources, l'ensemble du système CI s'arrête.
Résumé
Maîtriser l'optimisation des exécuteurs Jenkins est essentiel pour maintenir un pipeline CI/CD rapide et fiable. La stratégie fondamentale consiste à équilibrer la concurrence avec la disponibilité des ressources. Commencez par calculer précisément le nombre optimal d'exécuteurs en fonction des cœurs CPU de l'agent et du type de charge de travail. Ensuite, tirez parti de la mise à l'échelle dynamique et du parallélisme de pipeline pour garantir que le travail est distribué efficacement, minimisant les temps d'attente dans la file et maximisant le débit du système.