Arrête le polling : fais passer Jenkins de pollSCM aux webhooks
pollSCM demande à ton serveur Git « du nouveau ? » à intervalle fixe. Avec les webhooks, le serveur Git répond à l'instant même où il y en a. Voici comment migrer — et quand le polling reste le bon choix.
Écrit par Loris Siegenthaler · Créateur de BuildCaptainPresque chaque instance Jenkins commence de la même façon. Quelqu'un crée un job, coche Poll SCM, tape * * * * * parce qu'il veut des builds rapides, et passe à autre chose. Deux ans plus tard, quatre cents jobs font la même chose, le log d'accès du serveur Git est rempli à 95 % par Jenkins, et les builds mettent toujours jusqu'à une minute à démarrer.
Le polling n'est pas une erreur. C'est juste la solution de repli, et la plupart des équipes le font tourner comme réglage par défaut. Voici ce qu'il fait vraiment, ce qu'il coûte, et comment basculer sur des webhooks les jobs qui n'en ont pas besoin.
Ce que pollSCM fait vraiment
Le déclencheur pollSCM planifie une vérification périodique du dépôt configuré sur le job. À chaque passage, Jenkins demande au SCM si la révision qu'il a construite en dernier est toujours la tête de la branche. Si ce n'est pas le cas, un build est mis en file. Si c'est le cas, rien ne se passe et le poll est consigné dans le Git Polling Log du job.
Dans un pipeline déclaratif, ça ressemble à ceci :
pipeline {
agent any
triggers {
pollSCM('H/5 * * * *')
}
stages {
stage('Build') {
steps {
sh './gradlew build'
}
}
}
}
triggers dans un Jenkinsfile ne prend effet qu'une fois que Jenkins a lu ce fichier au moins une fois — ce qui veut dire lancer le job manuellement la première fois. Le déclencheur est une configuration qui vit dans le dépôt, et Jenkins ne peut pas la connaître avant d'avoir extrait le dépôt.Il existe deux variantes de polling, et elles ne coûtent pas pareil. Le plugin Git peut en général répondre à « la tête a-t-elle bougé ? » avec un simple git ls-remote vers le dépôt distant — pas de workspace, pas de clone, pas d'agent. Mais si la configuration SCM du job a besoin d'un workspace pour décider (certains réglages changelog/exclusions de chemins, certains autres plugins SCM), Jenkins doit occuper un exécuteur et toucher un vrai checkout à chaque poll. C'est cette version-là qui fait mal.
La syntaxe cron, et pourquoi H compte
Jenkins utilise cinq champs — minute, heure, jour du mois, mois, jour de la semaine — et ajoute un caractère que le cron classique n'a pas : H.
H signifie « hash ». Jenkins hache le nom du job en une valeur stable dans la plage autorisée, si bien que chaque job reçoit son propre décalage et le garde. C'est la différence entre quatre cents jobs qui pollent tous à la seconde 00 de chaque minute et quatre cents jobs répartis uniformément sur l'intervalle.
# Every five minutes, at a per-job offset inside each five-minute window
H/5 * * * *
# Once an hour, at a per-job minute
H * * * *
# Once a night, some time between 02:00 and 04:59
H H(2-4) * * *
# Twice a day, on weekdays only
H H(8-9),H(16-17) * * 1-5
N'écris jamais * * * * *. Ça veut dire « chaque minute », ça empile tous les jobs sur le même tick, et la page de configuration du job t'avertira à ce sujet. Si tu as réellement besoin d'une réaction à la minute près, c'est le signe le plus clair possible qu'il te faut un webhook, pas un poll plus rapide.
La même syntaxe pilote le déclencheur cron (construire selon un planning, changements ou pas) et l'intervalle Scan Repository Triggers des projets multibranches : elle vaut donc la peine d'être apprise une fois pour toutes.
Ce que le polling te coûte
Trois factures distinctes, et les équipes ne remarquent en général que la troisième.
La charge sur le serveur SCM. Un job qui polle toutes les cinq minutes, c'est 288 requêtes par jour. Quatre cents jobs, c'est 115 000 requêtes par jour contre ton hébergeur Git, presque toutes pour répondre « non, rien n'a changé ». Les instances GitLab ou Bitbucket auto-hébergées le sentent directement. Les fournisseurs hébergés ont des limites de débit que le polling authentifié consomme — c'est comme ça que des équipes découvrent que leur Jenkins est la raison pour laquelle leur quota d'API est épuisé avant midi.
La charge sur le contrôleur. Le polling tourne sur le contrôleur Jenkins, dans un pool de threads borné. Quand les polls se mettent à durer plus longtemps que prévu — un serveur distant lent, un dépôt qui exige un workspace, un accroc réseau — le pool s'engorge et Jenkins t'avertit que les threads de polling sont à sec. À ce stade, les polls sont en retard, donc les builds sont en retard — et l'avantage que le polling devait t'apporter est justement celui que tu as perdu.
La latence, toujours. Avec un intervalle de cinq minutes, l'attente moyenne entre un push et le démarrage d'un build est de deux minutes et demie, et le pire cas de cinq. Ce délai est invisible sur un tableau de bord et extrêmement visible pour le développeur qui a poussé un correctif et reste là devant un écran où il ne se passe rien. La documentation du plugin Git ne tourne pas autour du pot : pour minimiser le délai entre un push et un build, configure le dépôt distant pour qu'il notifie Jenkins avec un webhook.
Les webhooks avec GitHub
Le plugin GitHub expose un endpoint unique sur ton Jenkins pour tous les dépôts :
https://jenkins.example.com/github-webhook/
Le slash final est obligatoire. Dans le dépôt GitHub, va dans Settings → Webhooks → Add webhook et renseigne :
- Payload URL — l'URL ci-dessus.
- Content type —
application/json. Le plugin accepte aussiapplication/x-www-form-urlencoded, mais JSON est le choix conventionnel et celui de tous les exemples. - Secret — un secret partagé, si tu en as configuré un côté Jenkins. Mets-en un ; un déclencheur de build non authentifié est une porte ouverte.
- Events — « Just the push event » pour un job simple ; ajoute les pull requests pour du multibranche.
Sur le job Jenkins, coche GitHub hook trigger for GITScm polling. Le nom dit exactement ce qui se passe : le hook ne construit pas aveuglément, il dit à Jenkins de poller tout de suite. Jenkins confirme le changement auprès du dépôt, puis construit. Tu gardes la fiabilité du polling et tu te débarrasses de son planning.
Dans un pipeline déclaratif :
pipeline {
agent any
triggers {
githubPush()
}
stages { /* … */ }
}
Pour les pipelines multibranches et les dossiers d'organisation, le plugin GitHub Branch Source utilise le même endpoint /github-webhook/, mais l'effet est différent : un événement push déclenche un scan de la branche concernée au lieu d'un build d'un job fixe. C'est ce qui fait apparaître les nouvelles branches et les nouvelles pull requests dans Jenkins en quelques secondes au lieu d'attendre le prochain scan périodique. Configure le webhook une seule fois au niveau de l'organisation GitHub et chaque dépôt qu'elle contient est couvert.
Les webhooks avec GitLab
Le plugin GitLab fonctionne par projet plutôt qu'avec un endpoint global unique. Chaque job reçoit sa propre URL :
https://jenkins.example.com/project/<job-name>
Les jobs dans des dossiers incluent le chemin du dossier — /project/platform/api-service. La page de configuration du job affiche l'URL exacte à côté de la case Build when a change is pushed to GitLab, avec un bouton Generate pour le token secret. Copie les deux dans Settings → Webhooks du projet GitLab : l'URL dans le champ URL, le token dans Secret token. Les projets multibranches utilisent la même forme d'URL.
Ne sois pas tenté de pointer le webhook GitLab vers /job/<name>/build à la place. Le README du plugin est explicite : cela le court-circuite complètement — tu perds les données de branche et de merge request du payload, et avec elles chaque filtre qui en dépend.
pipeline {
agent any
triggers {
gitlab(
triggerOnPush: true,
triggerOnMergeRequest: true,
branchFilterType: 'All'
)
}
stages { /* … */ }
}
Si tu es sur autre chose que GitHub ou GitLab — Gitea, un miroir auto-hébergé, un outil interne qui pousse des tags — le plugin Generic Webhook Trigger t'offre un endpoint protégé par token à /generic-webhook-trigger/invoke?token=… et te laisse lier des valeurs du payload JSON à des paramètres de build. Pour les projets multibranches, le plugin Multibranch Scan Webhook Trigger fait la même chose pour les scans à /multibranch-webhook-trigger/invoke?token=… — et accepte le token dans un header plutôt qu'en paramètre de requête, ce qui le garde hors des logs d'accès.
Quand le polling reste la bonne réponse
Les webhooks exigent une chose que tous les Jenkins n'ont pas : une route du serveur SCM vers Jenkins. Garde le polling quand
- Jenkins est derrière un pare-feu d'entreprise ou sur un réseau privé sans chemin entrant depuis ton hébergeur Git, et que tu ne veux pas percer un trou dans le pare-feu juste pour ça ;
- tu n'administres pas le dépôt et ne peux pas y ajouter de webhooks ;
- la source n'est pas du tout un système capable de webhooks — le serveur SVN d'un fournisseur, un miroir d'artefacts, un dépôt de fichiers.
Dans ces cas-là, polle délibérément plutôt que par accident : H/5 * * * * pour quelque chose de réellement interactif, H/30 * * * * ou H * * * * pour tout le reste, et du polling léger façon ls-remote partout où le plugin SCM le permet. Un polling assumé, en solution de repli, avec un intervalle raisonnable, c'est de la bonne ingénierie. Poller chaque minute sur quatre cents jobs parce que personne n'a revisité le réglage par défaut, non.
L'hybride est aussi légitime, et c'est souvent là que tu veux aboutir : les webhooks comme voie rapide, plus un poll lent — quotidien, ou toutes les quelques heures — comme filet qui rattrape les événements perdus lors d'un redémarrage de Jenkins ou d'un échec de livraison de webhook.
Quiet period : le réglage qui évite les rafales de builds
Une fois que les déclencheurs partent instantanément, les rafales deviennent visibles. Un développeur pousse trois commits en vingt secondes, ou un script pousse sur cinq branches d'un coup, et Jenkins met un build en file pour chacun.
La quiet period est la réponse intégrée. Quand un build est déclenché, Jenkins le retient dans la file pendant un certain nombre de secondes ; les déclenchements suivants du même job qui arrivent dans cette fenêtre sont fusionnés dans le build en attente au lieu d'être mis en file derrière lui. La valeur globale par défaut est de cinq secondes (Manage Jenkins → System), et chaque job peut la surcharger sous Advanced — ou dans un pipeline avec options { quietPeriod(30) }.
Deux choses à savoir. La fenêtre n'est pas prolongée par les déclenchements suivants : un build qui attend depuis quatre secondes démarre quand même une seconde plus tard, quel que soit le nombre de pushes entre-temps. Et cinq secondes est un défaut raisonnable pour le polling, mais souvent trop court une fois passé aux webhooks. Si ton équipe pousse en rafales, 30 à 60 secondes sur les jobs coûteux transforment cinq builds redondants en un seul, pour le seul prix d'une demi-minute de latence que personne n'allait remarquer.
Des déclencheurs rapides méritent un retour rapide
Voici ce qui change après la migration. Avec le polling, le délai entre pousser et savoir était dominé par le déclencheur — tu poussais, tu allais chercher un café, tu revenais vérifier Jenkins. Avec les webhooks, le build démarre en quelques secondes, et la partie la plus lente de la boucle, c'est désormais toi : l'onglet de navigateur auquel tu dois penser, le tableau de bord que tu rafraîchis, le canal Slack où la notification arrive au milieu de deux cents autres.
C'est le problème pour lequel nous avons construit notre propre outil — ce qui suit est donc, en toute transparence, de la publicité maison. BuildCaptain est notre app — un client Jenkins natif pour la barre des menus macOS. Épingle les pipelines qui comptent pour toi et leur statut s'affiche dans la barre des menus ; quand l'un passe au rouge, tu reçois une notification native, et un clic ouvre le graphe des étapes, le log du step en échec et un bouton de relance. Tu viens de faire réagir Jenkins instantanément à tes pushes — ceci se charge de te prévenir tout aussi vite.
BuildCaptain