Dépannage des Builds Jenkins Lents : Goulots d'Étranglement Courants et Solutions
Identifiez et résolvez les problèmes de performance courants qui affectent vos builds Jenkins. Ce guide de dépannage propose des étapes pratiques pour diagnostiquer les builds lents en analysant les logs, en optimisant la configuration des exécuteurs, en exploitant les mécanismes de cache de build et en rationalisant les scripts de pipeline pour un processus CI/CD plus rapide et plus efficace.
Dépannage des Builds Jenkins Lents : Goulots d'Étranglement Courants et Solutions
Les builds Jenkins lents nuisent car ils retardent le retour d'information. Un développeur pousse une petite modification, attend vingt minutes, puis apprend qu'un test a échoué dès la première minute. Avant de régler quoi que ce soit, séparez le temps de file d'attente, le temps de démarrage de l'agent, le temps d'extraction, la configuration des dépendances, le temps de test, le packaging et le déploiement. Ce sont des problèmes différents avec des correctifs différents.
L'objectif n'est pas de faire paraître Jenkins plus rapide dans un tableau de bord. L'objectif est de faire arriver le prochain signal utile plus tôt.
1. Diagnostic Initial : Où va le Temps ?
Avant d'appliquer des correctifs, vous devez identifier la source du ralentissement. Jenkins fournit d'excellents outils intégrés pour le diagnostic initial.
Analyse du Journal de Build
La ressource la plus immédiate est la sortie console pour un build lent. Recherchez de grands écarts dans les horodatages entre les étapes séquentielles.
- Identifier les Étapes Longues : Notez quelles étapes de build (par exemple,
mvn clean install, exécution de script, téléchargement de dépendances) consomment le plus de temps. - Appels Externes : Faites attention aux étapes impliquant une activité réseau (par exemple, récupération de dépendances externes, connexion à des dépôts d'artefacts distants). Ce sont souvent des dépendances externes, pas Jenkins lui-même.
Utilisation du Graphique de Temps de Build
L'interface utilisateur classique de Jenkins Blue Ocean ou les pipelines classiques affichent souvent une ventilation visuelle des durées des étapes. Utilisez cette aide visuelle pour confirmer quelles étapes sont disproportionnellement longues.
Astuce : Si une étape spécifique prend systématiquement plus de temps que prévu sur plusieurs builds, c'est votre cible d'optimisation principale.
2. Goulots d'Étranglement de l'Infrastructure Jenkins
Si les étapes de build elles-mêmes sont rapides mais que le temps d'attente entre les jobs est long, le problème vient probablement de l'infrastructure du contrôleur Jenkins (maître) ou de l'agent (esclave).
Disponibilité et Surcharge des Exécuteurs
Le problème d'infrastructure le plus courant est une capacité de build insuffisante.
Comprendre les Exécuteurs
Les exécuteurs sont les emplacements parallèles disponibles sur un nœud Jenkins pour exécuter des jobs. Si un nœud a 5 exécuteurs, il peut exécuter 5 jobs simultanément.
- Symptôme : Les builds sont constamment en file d'attente, même lorsque l'utilisation du CPU/Mémoire semble faible.
- Solution : Augmentez le nombre d'exécuteurs sur vos nœuds de build principaux, ou ajoutez plus de nœuds/agents à votre parc.
Vérification de Configuration (Gestion des Agents) : Vérifiez l'écran de configuration de l'agent. Assurez-vous que le 'Nombre d'exécuteurs' est défini de manière appropriée pour le matériel alloué à cet agent.
Charge du Contrôleur
Si le nœud du contrôleur Jenkins est en difficulté, il ne peut pas planifier correctement les jobs, même si les agents sont libres.
- Symptômes : Réactivité lente de l'interface utilisateur, planification de build retardée, ou utilisation élevée du CPU/mémoire signalée par le moniteur système du contrôleur.
- Solution : Déchargez les tâches coûteuses (comme la compilation) sur les agents. Assurez-vous que le contrôleur dispose de ressources adéquates (CPU, RAM suffisante) dédiées principalement aux tâches de gestion, et non au build.
Performances d'E/S Disque
Les entrées/sorties (E/S) disque lentes affectent les étapes impliquant des opérations sur de gros fichiers, comme le clonage de dépôts Git ou le déballage de grandes archives.
- Meilleure Pratique : Utilisez un stockage rapide (SSD ou stockage réseau à haut débit) pour les espaces de travail Jenkins et le répertoire personnel de Jenkins, en particulier sur les agents de build.
3. Optimisation des Scripts de Pipeline
Des pipelines déclaratifs ou scriptés inefficaces peuvent introduire une surcharge inutile.
Gestion de l'Espace de Travail
Les grands espaces de travail remplis d'artefacts anciens peuvent ralentir les opérations ultérieures comme le clonage ou le nettoyage.
- Utilisez l'Étape
ws()avec Sagesse : Si vous utilisez un Pipeline Scripté, soyez attentif aux opérations sur l'ensemble de l'espace de travail. - Nettoyer l'Espace de Travail : Configurez les jobs pour nettoyer l'espace de travail après une exécution réussie, ou utilisez l'étape
cleanWs()avec discernement. Attention : Ne nettoyez pas les espaces de travail si vous comptez sur des builds incrémentaux ou la mise en cache d'artefacts entre les exécutions.
Opérations Redondantes (Téléchargement de Dépendances)
Télécharger les mêmes dépendances à plusieurs reprises fait perdre du temps.
- Mise en Cache des Dépendances : Mettez en œuvre des stratégies de mise en cache spécifiques à l'outil de build dans l'environnement de l'agent (par exemple, dépôt local Maven, cache npm). Assurez-vous que le répertoire de cache est persistant et partagé si possible.
// Exemple : Assurer la persistance du dépôt Maven sur un agent
steps {
sh 'mvn -B clean install -Dmaven.repo.local=/chemin/vers/cache/maven/partagé'
}
Parallélisation des Étapes Indépendantes
Si les étapes de votre pipeline sont indépendantes, exécutez-les simultanément à l'aide du bloc parallel dans les Pipelines Déclaratifs.
pipeline {
agent any
stages {
stage('Build & Test') {
parallel {
stage('Tests Unitaires') {
steps { sh './run_tests.sh' }
}
stage('Analyse Statique') {
steps { sh './run_sonar.sh' }
}
}
}
stage('Package') {
// S'exécute après la fin des deux étapes Build & Test
steps { sh './create_jar.sh' }
}
}
}
4. Exploitation des Mécanismes de Cache de Build
Pour les builds qui réutilisent des composants volumineux (comme les images Docker ou les fichiers sources compilés), la mise en cache est cruciale pour la vitesse.
Mise en Cache des Couches Docker
Si votre pipeline construit des images Docker, utilisez efficacement la mise en cache des couches.
- L'Ordre Compte : Placez les étapes qui changent fréquemment (par exemple,
COPY . .) plus tard dans le Dockerfile que les étapes qui changent rarement (par exemple, l'installation des dépendances de base). - Utilisez l'Agent Docker : Lorsque vous utilisez des agents Jenkins exécutant Docker, assurez-vous que le processus de build exploite les caches d'images locaux existants avant de tenter un pull/build complet.
Builds Incrémentaux
Assurez-vous que vos outils de build sont configurés pour les builds incrémentaux le cas échéant (par exemple, le cache de build de Gradle, ou l'utilisation d'indicateurs de compilation spécifiques).
5. Configuration de l'Agent et Allocation des Ressources
Les agents sont l'endroit où le travail lourd est effectué. Assurez-vous qu'ils sont correctement provisionnés et configurés.
Dimensionnement du Matériel
Si la saturation du CPU est élevée pendant les builds, l'agent a besoin de plus de puissance de traitement. Si les builds attendent fréquemment des ressources (comme la mémoire), augmentez la RAM.
Méthode de Lancement de l'Agent
- Agents Statiques : Démarrage plus rapide, mais moins flexibles pour la mise à l'échelle.
- Agents Dynamiques (par exemple, Agents Kubernetes ou EC2) : Bien que la configuration prenne un peu plus de temps, ces agents garantissent que les ressources sont dimensionnées précisément en cas de besoin, évitant les longues files d'attente pendant les périodes de pointe.
Meilleure Pratique : Pour la mise à l'échelle dynamique, assurez-vous que le temps de lancement d'un nouvel agent est nettement plus rapide que le temps nécessaire à un job pour expirer dans la file d'attente. Si le provisionnement de l'agent prend 10 minutes, mais que les jobs n'attendent que 3 minutes, la mise à l'échelle n'aidera pas le goulot d'étranglement immédiat.
Un Guide Pratique pour les Builds Lents
- Analysez les Journaux : Déterminez quelle étape du pipeline consomme le plus de temps.
- Vérifiez les Exécuteurs : Vérifiez que le nombre d'exécuteurs de l'agent correspond à la charge simultanée attendue.
- Optimisez les E/S : Assurez-vous que les espaces de travail et les caches résident sur un stockage rapide.
- Mettez en Cache les Dépendances : Implémentez la persistance pour Maven, npm ou d'autres caches de dépendances.
- Parallélisez : Réécrivez les étapes de pipeline indépendantes pour qu'elles s'exécutent simultanément.
- Profilez les Outils : Assurez-vous que les outils de build (Maven, Gradle) utilisent les fonctionnalités de build incrémental.
En abordant méthodiquement ces goulots d'étranglement potentiels—de la capacité de l'infrastructure à l'efficacité des scripts—vous pouvez transformer des builds lents et frustrants en composants rapides et fiables de votre flux de travail CI/CD.
Une Façon Plus Honnête de Lire un Build Lent
La façon la plus rapide de perdre un après-midi est de traiter chaque build Jenkins lent comme un problème Jenkins. Parfois, Jenkins est le goulot d'étranglement. Souvent, il n'est que le messager. Un pipeline peut sembler lent parce qu'il attend dans la file d'attente, parce que l'agent met du temps à démarrer, parce que l'extraction Git traîne, parce que l'outil de build télécharge à nouveau Internet, parce que les tests sont sérialisés, ou parce qu'une étape de déploiement en aval attend un autre système.
Quand je regarde un job lent, je divise le temps total en quatre catégories : temps de file d'attente, temps de provisionnement de l'agent, temps de configuration de l'espace de travail, et temps réel de build/test. Jenkins montre une partie de cela dans la page de build et la vue des étapes du pipeline, mais le journal de la console est encore l'enregistrement le plus utile. Ajoutez des horodatages s'ils manquent. Ensuite, comparez une exécution lente avec une exécution normale. Vous cherchez le premier endroit où les deux chronologies divergent.
Par exemple, si l'exécution lente passe huit minutes avant que la première commande shell ne démarre, régler Maven n'aidera pas. Vérifiez la disponibilité des exécuteurs, la correspondance des étiquettes, le provisionnement des agents cloud et les jobs en attente. Si l'exécution lente démarre rapidement mais passe cinq minutes sur git fetch, regardez la taille du dépôt, les refspecs, les tags, le chemin réseau et la réutilisation de l'espace de travail. Si l'extraction est rapide mais que npm ci est lent à chaque fois, inspectez la persistance du cache et l'accès au registre depuis l'agent.
N'optimisez pas de mémoire. Choisissez trois builds récents : un rapide, un typique et un lent. Notez la durée de chaque étape. Ce petit tableau pointe généralement vers la bonne couche.
Temps de File d'Attente : Le Goulot d'Étranglement Avant le Début du Build
Le temps de file d'attente est facile à ignorer car rien n'a encore échoué. Les développeurs voient juste un build qui attend. Dans Jenkins, une longue file d'attente signifie généralement l'une des quatre choses suivantes : il n'y a pas assez d'exécuteurs, les étiquettes sont trop restrictives, un verrou sérialise le travail, ou les agents dynamiques sont lents à apparaître.
Commencez par la page du job et le panneau d'état des exécuteurs. Si de nombreux agents sont inactifs mais que le job est en file d'attente, l'expression d'étiquette est peut-être trop stricte. Un job étiqueté linux && docker && java17 && large ne peut s'exécuter que sur les nœuds qui correspondent à chaque étiquette. Cela peut être intentionnel pour un build de version de production, mais c'est souvent accidentel pour les vérifications normales de pull request. Si un build général n'a besoin que de Docker et Java, ne le liez pas à une machine spéciale à moins qu'il n'y ait une raison réelle.
Les verrous sont une autre source silencieuse de retard. Le plugin Lockable Resources est utile lorsque les tests ont besoin d'un accès exclusif à une base de données partagée, un périphérique matériel ou un espace de noms de staging. Cela devient douloureux lorsque trop de travail se trouve à l'intérieur du verrou. Gardez la section verrouillée aussi petite que possible. Construisez l'artefact en dehors du verrou, acquérez le verrou, exécutez uniquement l'étape de ressource partagée, et relâchez-le.
Pour les agents cloud, mesurez le temps de démarrage séparément. Un pod Kubernetes qui met deux minutes à planifier peut convenir. Un pod qui met quinze minutes parce qu'il tire une grande image personnalisée à chaque exécution ne convient pas. Pré-tirez les images courantes, réduisez la taille de l'image, ou gardez un petit pool chaud si votre trafic CI est prévisible.
Temps d'Extraction : Git Peut Être Tout le Problème
L'extraction lente est courante dans les installations Jenkins plus anciennes car les dépôts grossissent progressivement. Personne ne remarque les premiers gros binaires, puis un jour chaque build paie pour des années d'historique.
Utilisez les paramètres du plugin Git avec soin. Un clone superficiel peut aider les jobs qui n'ont besoin que du commit actuel, mais il peut casser les builds qui calculent les versions à partir des tags ou comparent avec les commits précédents. La récupération des tags peut également ajouter un temps surprenant dans les dépôts riches en tags. Si le job n'a pas besoin de tags, désactivez la récupération des tags. Si le pipeline extrait plusieurs dépôts, chronométrez chaque extraction séparément afin qu'un dépôt de dépendance lent ne se cache pas à l'intérieur d'une étape "SCM" générique.
La réutilisation de l'espace de travail est un compromis. Réutiliser un espace de travail peut rendre git fetch beaucoup plus rapide, mais les fichiers obsolètes peuvent créer des échecs étranges. Effacer l'espace de travail avant chaque build est propre mais peut être coûteux pour les grands monorepos. Un juste milieu pratique consiste à utiliser des commandes d'extraction propres qui suppriment les fichiers non suivis tout en conservant le répertoire .git, ou à réserver les effacements complets de l'espace de travail pour les builds échoués et le nettoyage planifié.
Sur les agents occupés, la vitesse d'extraction peut également être un problème de disque. Si dix builds clonent de grands dépôts sur le même petit volume, le CPU peut sembler correct alors que les E/S disque sont saturées. Vérifiez iostat, les métriques de volume cloud ou le tableau de bord de stockage de l'agent pendant que les builds s'exécutent. Déplacer les espaces de travail vers un stockage SSD local plus rapide peut changer le temps de build plus que n'importe quel paramètre Jenkins.
Les Caches de Dépendances Ont Besoin d'un Propriétaire
La mise en cache n'est utile que lorsque quelqu'un en est propriétaire. Un cache qui disparaît aléatoirement, croît sans limites ou mélange des versions d'outils incompatibles peut créer plus de problèmes qu'il n'en résout.
Pour Maven et Gradle, un dépôt local persistant ou un cache de build peut réduire les téléchargements répétés. Le cache doit vivre en dehors de l'espace de travail jetable. Il doit également être sûr pour les builds simultanés. Le dépôt local de Maven est généralement correct pour les lectures de dépendances normales, mais les téléchargements interrompus peuvent laisser des fichiers corrompus. Si vous voyez des erreurs de somme de contrôle ou des artefacts corrompus, effacez le chemin de dépendance spécifique au lieu de supprimer tout le cache par habitude.
Pour npm, préférez npm ci pour des installations reproductibles et mettez en cache le cache de paquets npm plutôt que node_modules à moins que vous ne sachiez que le système d'exploitation, l'architecture CPU, la version de Node et le fichier de verrouillage sont stables. Mettre en cache node_modules sur différentes images d'agent est un moyen classique d'obtenir des échecs de modules natifs qui ne se produisent que dans CI.
Pour les builds Docker, le cache le plus précieux est généralement le cache de couches. Placez les étapes d'installation de dépendances stables avant les étapes de copie du code source dans le Dockerfile. Si le démon Docker est isolé par pod de build et démarre vide à chaque fois, la mise en cache locale des couches n'aidera pas beaucoup. Dans ce cas, utilisez l'export/import du cache BuildKit ou un cache basé sur un registre si votre environnement le supporte.
Temps de Test : Parallélisez avec Précaution
Les tests sont souvent la partie la plus longue d'un pipeline sain. L'objectif n'est pas simplement d'exécuter plus de choses en parallèle. L'objectif est de raccourcir le retour d'information sans créer de résultats instables.
Les tests unitaires se parallélisent généralement bien. Les tests d'intégration sont plus délicats car ils peuvent partager des bases de données, des ports, des files d'attente, des buckets ou des comptes externes. Si deux branches de test écrivent dans le même schéma ou réutilisent le même nom de file d'attente, l'exécution parallèle peut rendre le pipeline plus rapide et moins fiable en même temps. Donnez à chaque branche son propre espace de noms, schéma de base de données, répertoire temporaire et plage de ports de service si possible.
Divisez les suites de tests par durée mesurée, pas par nombre de fichiers. Dix petits fichiers de test peuvent s'exécuter plus rapidement qu'un grand test de navigateur. De nombreuses équipes obtiennent de meilleurs résultats en enregistrant les durées des tests et en équilibrant les groupes afin que chaque branche parallèle prenne à peu près le même temps.
Surveillez également les échecs lents. Une étape de test qui attend un service mort pendant dix minutes avant d'échouer est pire qu'une étape qui échoue en trente secondes avec une vérification de santé claire. Mettez des vérifications de disponibilité explicites avant les longues commandes de test et définissez des délais d'attente autour des appels réseau qui peuvent se bloquer.
La Santé du Contrôleur Compte Toujours
Le travail de build appartient aux agents, mais le contrôleur planifie toujours les jobs, sert les journaux, évalue la logique du pipeline, charge les plugins et gère le trafic de l'interface utilisateur. Si le contrôleur est surchargé, chaque job semble plus lent même lorsque les agents ont une capacité libre.
Recherchez des pages d'interface utilisateur lentes, des mises à jour de journaux de console retardées, de longues pauses de garbage collection et un CPU élevé du contrôleur. Les grands journaux de pipeline, trop de builds conservés, le polling agressif et les plugins lourds peuvent tous ajouter de la charge. Gardez une rétention de build réaliste. Archivez uniquement les artefacts dont les gens ont besoin. Déplacez les grands rapports de test et les journaux vers un stockage externe si votre volume personnel Jenkins est en difficulté.
Évitez d'exécuter des builds sur le contrôleur. Cela peut sembler inoffensif pour un petit job, mais cela rend les incidents plus difficiles à raisonner. Le contrôleur doit coordonner. Les agents doivent compiler, tester, empaqueter et déployer.
Un Ordre Pratique des Opérations
Lorsqu'une équipe demande pourquoi Jenkins est lent, utilisez cet ordre :
- Mesurez le temps de file d'attente par rapport au temps d'exécution.
- Trouvez l'étape la plus lente parmi les builds récents.
- Comparez une exécution lente avec une exécution normale.
- Vérifiez si le retard est dû à l'attente, l'extraction, le téléchargement des dépendances, les tests, le packaging ou le déploiement.
- Corrigez un goulot d'étranglement et mesurez à nouveau.
Cette dernière étape est importante. Si l'extraction passe de six minutes à une minute, célébrez brièvement et continuez à mesurer. Le prochain goulot d'étranglement deviendra visible. Le travail de performance CI est généralement une séquence d'améliorations petites et vérifiées plutôt qu'un paramètre magique.