Performances vs. Scalabilité Jenkins : Choisir la bonne voie d'optimisation

Maîtrisez la distinction cruciale entre l'optimisation des performances Jenkins et la planification de la scalabilité. Apprenez à diagnostiquer les goulots d'étranglement, qu'ils proviennent de builds individuels lents ou d'une capacité d'infrastructure insuffisante. Ce guide propose des stratégies concrètes pour optimiser les exécuteurs, tirer parti du cache de build et répartir efficacement les charges de travail afin de garantir que votre système CI/CD soit à la fois rapide et prêt à évoluer.

Performances vs. Scalabilité Jenkins : Choisir la bonne voie d'optimisation

Quand Jenkins semble lent, la première question n'est pas "Comment agrandir Jenkins ?" mais "Quel type de lenteur observons-nous ?". Une équipe avec des builds de dix minutes et une file d'attente vide a un problème différent d'une équipe avec des builds rapides attendant derrière cinquante jobs en file d'attente. L'une a besoin d'un travail sur les performances. L'autre a besoin de plus de capacité utilisable. De nombreuses pannes Jenkins surviennent parce que ces deux problèmes sont mélangés.

Je considère les performances Jenkins comme la vitesse d'une unité de travail unique : checkout, restauration des dépendances, compilation, test, empaquetage, archivage, publication. Je considère la scalabilité Jenkins comme la capacité du système à continuer à effectuer ce travail lorsque davantage d'équipes, de dépôts, de pull requests et de jobs planifiés arrivent en même temps. Vous avez généralement besoin des deux, mais vous ne les corrigez pas dans le même ordre.

Définir les concepts fondamentaux

Bien que souvent confondus, les performances et la scalabilité abordent différents aspects du comportement du système sous charge. Se concentrer sur la mauvaise métrique peut entraîner des efforts inutiles et des goulots d'étranglement persistants.

Performances Jenkins : Vitesse et efficacité

Les performances dans Jenkins concernent la rapidité avec laquelle une seule tâche ou un petit lot de tâches peut être réalisé. Elles sont mesurées par des métriques comme la durée du build, le temps d'exécution des étapes et la réactivité du contrôleur Jenkins (master).

  • Objectif : Réduire la latence et bien utiliser les ressources existantes.
  • Domaines d'intervention : Optimiser les étapes de build individuelles, minimiser les frais généraux réseau et garantir une utilisation efficace des threads d'exécution.

Scalabilité Jenkins : Gérer l'augmentation de la charge

La scalabilité fait référence à la capacité du système à gérer une quantité croissante de travail en ajoutant des ressources. Un système scalable maintient des niveaux de performance acceptables à mesure que le volume de builds simultanés, le nombre d'utilisateurs ou la complexité des pipelines augmente.

  • Objectif : Augmenter le débit et la capacité sans transformer le contrôleur en prochain goulot d'étranglement.
  • Domaines d'intervention : Répartir la charge sur plusieurs agents, mettre en œuvre un provisionnement cloud robuste et gérer la capacité du contrôleur central à gérer des charges de travail distribuées.

Quand prioriser l'optimisation des performances

L'optimisation des performances est la voie d'optimisation immédiate lorsque vous observez une latence élevée même lorsque l'utilisation des ressources est faible, ou lorsque les builds individuels prennent trop de temps par rapport aux normes historiques. Cela indique généralement des inefficacités dans le processus de build lui-même.

Diagnostiquer les goulots d'étranglement des performances

Si votre environnement Jenkins dispose de nombreux exécuteurs disponibles mais que les builds calent fréquemment ou prennent beaucoup plus de temps que prévu, concentrez-vous sur l'optimisation des performances. Les symptômes courants incluent :

  • Une opération de clone Git spécifique prenant des minutes au lieu de secondes.
  • Des temps d'exécution de scripts Groovy qui grimpent de manière inattendue.
  • Une saturation des E/S disque sur le contrôleur ou les machines agents.

Stratégies de performance actionnables

  1. Optimiser les étapes de build : Examinez les étapes du Jenkinsfile. Des commandes redondantes sont-elles exécutées ? La mise en cache locale peut-elle accélérer considérablement la résolution des dépendances (par exemple, mise en cache Maven/Gradle) ?
  2. Tirer parti du cache de build : Mettez en œuvre des stratégies pour mettre en cache les artefacts de build ou les dépendances téléchargées entre les exécutions. Cela évite les opérations réseau coûteuses et le temps de compilation pour les modules inchangés.
  3. Optimisation des threads d'exécution : Assurez-vous que le nombre d'exécuteurs par agent est correctement adapté aux ressources (CPU/RAM). Trop d'exécuteurs peuvent entraîner une surcharge de changement de contexte, nuisant aux performances.

Exemple : Ajustement du nombre d'exécuteurs

Si un seul agent avec 8 cœurs est surchargé avec 10 exécuteurs, les performances souffrent en raison d'un changement de contexte excessif. Réduire le nombre à 6 pourrait améliorer le temps de build moyen, car chaque processus obtient des ressources plus dédiées.

# Exemple de configuration dans la configuration globale des outils Jenkins ou les paramètres de l'agent
Nombre d'exécuteurs : 6  # Optimisé pour les ressources physiques

Quand prioriser la scalabilité

La scalabilité devient la préoccupation principale lorsque votre système est limité en ressources en raison d'une forte concurrence ou lorsque vous anticipez une croissance significative de l'équipe de développement ou du volume de pipelines. Si votre infrastructure actuelle peut gérer 10 builds simultanés mais que vous devez en supporter 50 le trimestre prochain, vous avez besoin de scalabilité.

Diagnostiquer les goulots d'étranglement de scalabilité

Les symptômes nécessitant une focalisation sur la scalabilité incluent :

  • De longues files d'attente de build, même en dehors des heures de pointe.
  • Le CPU ou la mémoire du contrôleur Jenkins constamment proche de 100 % de capacité à gérer les builds.
  • Des agents inactifs car il n'y a pas de créneaux disponibles, même si le contrôleur signale une capacité libre.

Stratégies de scalabilité actionnables

  1. Builds distribués (le modèle d'agent) : Le principe fondamental de la scalabilité Jenkins est de déplacer la charge de travail du contrôleur central vers des agents de build dédiés.
    • Assurez-vous que les agents sont correctement configurés et peuvent être facilement ajoutés ou supprimés.
  2. Scalabilité cloud native (provisionnement dynamique) : Utilisez des outils comme le plugin CloudBees Kubernetes ou le plugin EC2 pour lancer dynamiquement des agents à la demande lorsque la file d'attente de build s'allonge et les arrêter lorsqu'ils sont inactifs. C'est la solution de mise à l'échelle à long terme la plus efficace.
  3. Allocation des ressources du contrôleur : Si le contrôleur est un goulot d'étranglement simplement en gérant les files d'attente, la planification et les rapports, assurez-vous qu'il dispose de suffisamment de CPU dédié et de RAM abondante. Une utilisation élevée de la mémoire résulte souvent d'un trop grand nombre de jobs en cours d'exécution ou d'une rétention excessive de données historiques.

Exemple : Configuration d'un agent cloud (conceptuel)

En utilisant le plugin EC2, vous définissez un modèle qui indique à Jenkins comment lancer une nouvelle instance EC2 lorsque la profondeur de la file d'attente atteint un certain seuil, garantissant ainsi que la capacité correspond à la demande.

// Extrait de Jenkinsfile simplifié montrant l'affectation de l'agent
pipeline {
    agent {
        kubernetes {
            label 'k8s-build-pod'
            inheritFrom 'default-pod-template'
        }
    }
    stages { ... }
}

L'interaction : Performances au sein d'un système scalable

Un build aux performances médiocres occupe un exécuteur plus longtemps, empêchant le système de passer à l'échelle efficacement.

Meilleure pratique : Visez toujours une efficacité de performance de base avant de passer à l'échelle. Mettre à l'échelle un système inefficace revient simplement à payer pour plus de machines lentes.

Scénario Priorité principale Pourquoi ?
Les builds sont constamment lents ; la file d'attente est courte. Performances L'inefficacité du processus de build lui-même est la source du retard.
La file d'attente de build ne cesse de croître ; les agents sont saturés. Scalabilité Le système manque de capacité pour traiter les requêtes simultanées.
Les temps de build sont acceptables, mais le contrôleur est lent. Scalabilité/Santé du contrôleur Le contrôleur est surchargé par la gestion des métadonnées et la planification, pas par l'exécution.

Meilleures pratiques de gestion des ressources pour les deux voies

Une gestion efficace des ressources sous-tend à la fois les efforts de performance et de scalabilité :

  • Surveillance : Mettez en œuvre une surveillance robuste (par exemple, Prometheus/Grafana) pour suivre l'utilisation des exécuteurs, les temps d'attente et l'utilisation du tas JVM du contrôleur. De bonnes données dictent si vous avez besoin de plus d'exécuteurs (scalabilité) ou de builds plus rapides (performances).
  • Garbage Collection : Examinez et ajustez régulièrement les paramètres de la machine virtuelle Java (JVM) du contrôleur Jenkins. Des pauses de garbage collection excessives dégradent gravement les performances perçues.
  • Nettoyage des pipelines : Nettoyez agressivement les anciens artefacts de build et les journaux. Une utilisation excessive du disque ralentit les opérations d'E/S, ce qui a un impact sur les performances de tous les builds.

Un parcours de triage pratique

Commencez par un seul job lent et notez trois chiffres : le temps d'attente, le temps d'exécution et le temps post-build. Le temps d'attente est la durée pendant laquelle le build a attendu qu'un exécuteur le prenne en charge. Le temps d'exécution est la durée pendant laquelle le pipeline réel a fonctionné. Le temps post-build est le nettoyage, l'archivage, la publication de rapports et le travail de notification qui se produit après la fin des étapes principales. Jenkins expose une partie de ces informations dans la page de build et la vue des étapes, mais vous aurez peut-être besoin des journaux, du plugin Pipeline Stage View, de l'historique Blue Ocean ou de métriques externes pour obtenir une image claire.

Si le temps d'attente est proche de zéro et que le temps d'exécution est élevé, n'ajoutez pas encore d'agents. Ouvrez le Jenkinsfile et recherchez les travaux de configuration répétés. Un service Java qui télécharge tout l'univers Maven à chaque exécution n'est pas un problème de capacité Jenkins. Un projet Node.js qui exécute npm install à partir d'un cache froid pour chaque branche n'est pas résolu par un autre contrôleur. Une construction Docker qui invalide sa couche de dépendance parce que COPY . . se produit avant l'installation des dépendances est un problème de conception de build. Corrigez d'abord ceux-ci.

Si le temps d'attente est élevé et que le temps d'exécution est raisonnable, examinez la disponibilité des exécuteurs par étiquette. Cela compte car la capacité Jenkins n'est pas un pool global unique dans la pratique. Vous pouvez avoir de nombreux agents Linux inactifs tandis que l'étiquette windows-signing a une seule machine occupée. Vous pouvez avoir beaucoup d'exécuteurs généraux tandis que chaque job de déploiement attend le même environnement verrouillé. La question utile n'est pas "Combien d'exécuteurs avons-nous ?" mais "Combien d'exécuteurs compatibles existent pour ce travail en file d'attente ?"

Si le temps d'attente et le temps d'exécution sont tous deux élevés, traitez d'abord les performances sur les jobs les plus volumineux. Un pipeline qui s'exécute 200 fois par jour et gaspille quatre minutes par exécution brûle beaucoup plus de capacité qu'un job de version hebdomadaire qui gaspille vingt minutes. Triez les jobs par minutes d'exécuteur totales, et non par l'équipe qui se plaint le plus fort.

Signes que vous résolvez le mauvais problème

Une erreur courante consiste à ajouter des exécuteurs à un agent surchargé. Cela peut améliorer un tableau de bord pendant quelques minutes car la file d'attente diminue, mais cela rend souvent chaque build plus lent. Quatre jobs de test gourmands en CPU sur une machine à quatre cœurs peuvent très bien fonctionner. Huit jobs de test gourmands en CPU sur la même machine peuvent passer plus de temps à se battre pour le CPU, la mémoire et le disque qu'à effectuer un travail utile. Surveillez la charge moyenne, le temps de vol CPU sur les machines virtuelles, l'attente disque et l'activité de swap avant d'augmenter le nombre d'exécuteurs.

Une autre erreur consiste à tout déplacer vers des agents Kubernetes sans vérifier le coût de démarrage. Les agents éphémères sont excellents lorsque les builds sont sporadiques et que l'isolation est importante. Ils sont moins agréables lorsque chaque build passe plusieurs minutes à extraire une image volumineuse, à installer des outils et à réchauffer les caches de dépendances. Dans ce cas, vous aurez peut-être besoin d'images d'agent pré-construites, d'un registre local, d'une mise en cache d'image au niveau du nœud ou d'un petit pool d'agents chauds pour les étiquettes les plus occupées.

Le réglage du contrôleur est également mal interprété. Une interface utilisateur Jenkins lente ne signifie pas toujours que le contrôleur a besoin d'un tas plus grand. Il peut être occupé à charger des historiques de build volumineux, à restituer de grands rapports de test, à indexer de nombreux jobs ou à gérer un plugin coûteux. Plus de mémoire peut aider si le garbage collection est le problème, mais cela ne résoudra pas un plugin qui effectue un travail lourd sur le contrôleur ou une disposition de jobs qui crée des milliers de branches que personne n'utilise.

Comment j'envisagerais la séquence de travail

Pour une petite instance Jenkins, je commencerais par les dix premiers jobs en minutes d'exécuteur. Pour chacun, je supprimerais les checkouts inutiles, mettrais en cache les dépendances sur l'agent, rendrais la sélection des tests plus intentionnelle et déplacerais la génération de rapports coûteuse du contrôleur lorsque cela est possible. Je vérifierais également si chaque job a vraiment besoin d'archiver éternellement les mêmes artefacts volumineux. La rétention des artefacts est rarement glamour, mais elle affecte le disque, le temps de sauvegarde, la réactivité de l'interface utilisateur et le temps de restauration.

Pour une équipe en croissance, je définirais des étiquettes autour des besoins réels de la charge de travail : linux-small, linux-docker, windows, macos, gpu, deploy, ou tout ce qui correspond à l'environnement. Les étiquettes doivent décrire les contraintes, pas les noms d'équipe. Les étiquettes d'équipe ont tendance à créer une capacité bloquée. Les étiquettes de charge de travail facilitent le partage sécurisé des agents.

Pour une organisation plus grande, je séparerais la santé du contrôleur de la capacité de build. Le contrôleur doit coordonner, stocker la configuration, servir l'interface utilisateur et planifier le travail. Il ne doit pas compiler d'applications, exécuter des tests de navigateur, construire des images Docker ou traiter de gros rapports, sauf si vous avez une raison très spécifique. Même dans ce cas, la raison doit être temporaire.

L'étape suivante est le provisionnement dynamique. Kubernetes, EC2 et autres agents cloud fonctionnent bien lorsque vous définissez des modèles clairs, plafonnez la concurrence maximale et mesurez la latence de démarrage. Sans plafonds, un job défectueux peut créer une tempête d'agents très coûteuse. Sans métriques de démarrage, les équipes peuvent blâmer Jenkins pour des builds lents alors que la majeure partie du retard est due au temps d'extraction de l'image.

Que mesurer après les changements

Ne jugez pas une optimisation sur un seul build chanceux. Comparez une journée de travail normale avant et après le changement. Regardez la durée médiane des builds, la durée des builds aux percentiles plus lents, le temps d'attente par étiquette, l'utilisation des exécuteurs, les échecs de lancement d'agents, l'utilisation du tas du contrôleur, les pauses de garbage collection, l'utilisation du disque et la croissance des artefacts. La tendance est plus importante qu'un seul chiffre.

Un modèle utile consiste à créer une revue hebdomadaire de la capacité Jenkins. Gardez-la courte. Apportez les principales étiquettes en file d'attente, les principaux jobs en minutes d'exécuteur, les étapes communes les plus lentes et tout avertissement concernant la santé du contrôleur. Cela vous donne un moyen de choisir le prochain changement en fonction des preuves. Cela empêche également le réglage de Jenkins de devenir une panique annuelle après que le système CI soit déjà douloureux.

Petites corrections qui rapportent souvent rapidement

Les clones Git superficiels peuvent aider lorsque les jobs n'ont besoin que de la révision actuelle, mais ce n'est pas un gain universel. Certains outils de version ont besoin de tags ou d'historique. Utilisez des clones superficiels là où ils conviennent et documentez l'exception lorsqu'ils ne conviennent pas.

Les caches de dépendances sont puissants, mais les caches partagés inscriptibles peuvent devenir corrompus ou créer des comportements inter-jobs difficiles à déboguer. Préférez les caches par agent pour la plupart des gestionnaires de paquets de langage, ou utilisez un référentiel d'artefacts dédié tel que Nexus, Artifactory ou un registre de paquets comme source de vérité partagée.

Les étapes parallèles peuvent réduire le temps d'horloge murale, mais elles augmentent la pression sur les exécuteurs et les machines. Si une étape de test est divisée en six branches parallèles, assurez-vous que l'agent ou le pool d'agents peut réellement exécuter six branches sans échanger ou écraser les E/S disque. Sinon, le pipeline peut sembler plus sophistiqué tout en se terminant en même temps ou plus tard.

Le nettoyage de l'espace de travail doit être délibéré. Nettoyer chaque espace de travail avant chaque build améliore la reproductibilité mais peut détruire les avantages du cache. Ne jamais nettoyer les espaces de travail permet d'économiser du temps de configuration mais crée éventuellement une pression sur le disque et une contamination étrange du build. Un compromis pratique consiste à nettoyer après les builds échoués ou suspects, à utiliser des répertoires de cache explicites et à faire expirer les anciens espaces de travail selon un calendrier.

Une meilleure règle empirique

Si les builds sont lents alors que les exécuteurs sont disponibles, réglez le pipeline. Si les builds sont rapides mais passent leur vie dans la file d'attente, ajoutez de la capacité là où les étiquettes en file d'attente en ont besoin. Si l'interface utilisateur, la gestion des files d'attente ou l'indexation des jobs est lente, protégez et réglez le contrôleur. Le travail sur les performances Jenkins devient plus facile une fois que vous arrêtez de traiter chaque retard comme le même type de retard.