Schluss mit Polling: Jenkins von pollSCM auf Webhooks umstellen
pollSCM fragt deinen Git-Server im Takt: „Gibt es was Neues?“ Mit Webhooks antwortet der Git-Server in dem Moment, in dem es etwas gibt. So gelingt die Migration – und in diesen Fällen bleibt Polling die richtige Wahl.
Geschrieben von Loris Siegenthaler · Macher von BuildCaptainFast jede Jenkins-Instanz beginnt gleich. Jemand legt einen Job an, setzt den Haken bei Poll SCM, tippt * * * * *, weil die Builds schnell sein sollen, und macht weiter. Zwei Jahre später tun vierhundert Jobs dasselbe, das Access-Log des Git-Servers besteht zu 95 % aus Jenkins, und Builds brauchen trotzdem bis zu einer Minute, um zu starten.
Polling ist nicht falsch. Es ist nur der Fallback – und die meisten Teams betreiben es als Standard. Hier liest du, was es wirklich tut, was es kostet und wie du die Jobs, die es nicht brauchen, auf Webhooks umziehst.
Was pollSCM wirklich tut
Der pollSCM-Trigger plant eine periodische Prüfung gegen das Repository, das am Job konfiguriert ist. Bei jedem Lauf fragt Jenkins das SCM, ob die zuletzt gebaute Revision noch die Spitze des Branches ist. Ist sie es nicht, wird ein Build eingereiht. Ist sie es, passiert nichts, und der Poll landet im Git Polling Log des Jobs.
In einer deklarativen Pipeline sieht das so aus:
pipeline {
agent any
triggers {
pollSCM('H/5 * * * *')
}
stages {
stage('Build') {
steps {
sh './gradlew build'
}
}
}
}
triggers-Block in einem Jenkinsfile greift erst, wenn Jenkins die Datei mindestens einmal gelesen hat – sprich: Du musst den Job das erste Mal manuell laufen lassen. Der Trigger ist Konfiguration, die im Repo lebt, und Jenkins kann nichts von ihr wissen, bevor es das Repo ausgecheckt hat.Es gibt zwei Spielarten von Polling, und sie sind nicht gleich teuer. Das Git-Plugin kann die Frage „Hat sich die Spitze bewegt?“ meist mit einem einzigen git ls-remote gegen das Remote beantworten – kein Workspace, kein Clone, kein Agent. Braucht die SCM-Konfiguration des Jobs für die Entscheidung aber einen Workspace (bestimmte changelog-/Pfadausschluss-Setups, manche andere SCM-Plugins), muss Jenkins bei jedem Poll einen Executor belegen und einen echten Checkout anfassen. Das ist die Variante, die wehtut.
Die Cron-Syntax – und warum H wichtig ist
Jenkins nutzt fünf Felder – Minute, Stunde, Monatstag, Monat, Wochentag – und ergänzt ein Zeichen, das normales Cron nicht kennt: H.
H steht für „Hash“. Jenkins hasht den Namen des Jobs zu einem stabilen Wert innerhalb des erlaubten Bereichs – jeder Job bekommt seinen eigenen Versatz und behält ihn. Das ist der Unterschied zwischen vierhundert Jobs, die alle bei Sekunde 00 jeder Minute pollen, und vierhundert Jobs, die gleichmäßig über das Intervall verteilt sind.
# 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
Schreib niemals * * * * *. Es bedeutet „jede Minute“, es stapelt jeden Job auf denselben Tick, und die Konfigurationsseite des Jobs warnt dich davor. Wenn du wirklich Reaktionszeit im Minutenbereich brauchst, ist das das deutlichste denkbare Zeichen dafür, dass du einen Webhook willst – keinen schnelleren Poll.
Dieselbe Syntax steuert den cron-Trigger (Bauen nach Zeitplan, unabhängig von Änderungen) und das Intervall der Multibranch-Scan Repository Triggers – es lohnt sich also, sie einmal zu lernen.
Was Polling dich kostet
Drei getrennte Rechnungen – und Teams bemerken meist nur die dritte.
Last auf dem SCM-Server. Ein Job, der alle fünf Minuten pollt, sind 288 Anfragen am Tag. Vierhundert Jobs sind 115.000 Anfragen am Tag gegen deinen Git-Host – und im Grunde alle antworten „nein, nichts geändert“. Selbst gehostete GitLab- oder Bitbucket-Instanzen spüren das direkt. Gehostete Anbieter haben Rate-Limits, an denen authentifiziertes Polling zehrt – so entdecken Teams, dass ihr Jenkins der Grund ist, warum die API-Quota schon mittags aufgebraucht ist.
Last auf dem Controller. Polling läuft auf dem Jenkins-Controller, in einem begrenzten Thread-Pool. Wenn Polls länger dauern als erwartet – ein langsames Remote, ein Repository, das einen Workspace braucht, ein Netzwerkschluckauf –, staut sich der Pool, und Jenkins warnt dich, dass die Polling-Threads ausgehungert sind. Ab dem Punkt laufen Polls zu spät, also starten Builds zu spät – und genau der Vorteil, den Polling dir eigentlich verschaffen sollte, ist dahin.
Latenz, immer. Bei einem Fünf-Minuten-Intervall beträgt die durchschnittliche Wartezeit zwischen Push und Build-Start zweieinhalb Minuten, im schlimmsten Fall fünf. Diese Verzögerung ist auf einem Dashboard unsichtbar und extrem sichtbar für den Entwickler, der gerade einen Fix gepusht hat und zusieht, wie nichts passiert. Die Dokumentation des Git-Plugins sagt es unumwunden: Um die Verzögerung zwischen Push und Build zu minimieren, konfiguriere das Remote-Repository so, dass es Jenkins per Webhook benachrichtigt.
Webhooks mit GitHub
Das GitHub-Plugin stellt auf deinem Jenkins einen einzigen Endpunkt für alle Repositories bereit:
https://jenkins.example.com/github-webhook/
Der Schrägstrich am Ende ist Pflicht. Gehe im GitHub-Repository zu Settings → Webhooks → Add webhook und setze:
- Payload URL – die URL oben.
- Content type –
application/json. Das Plugin akzeptiert auchapplication/x-www-form-urlencoded, aber JSON ist die übliche Wahl und die, die jedes Beispiel verwendet. - Secret – ein gemeinsames Geheimnis, falls du auf Jenkins-Seite eines konfiguriert hast. Setze es – ein unauthentifizierter Build-Trigger ist eine offene Tür.
- Events – „Just the push event“ für einen einfachen Job; für Multibranch kommen Pull Requests dazu.
Setze am Jenkins-Job den Haken bei GitHub hook trigger for GITScm polling. Der Name sagt es schon: Der Hook baut nicht blind, er sagt Jenkins, es solle jetzt sofort pollen. Jenkins bestätigt die Änderung gegen das Repository und baut dann. Die Verlässlichkeit des Pollings bleibt dir erhalten – nur sein Zeitplan fliegt raus.
In einer deklarativen Pipeline:
pipeline {
agent any
triggers {
githubPush()
}
stages { /* … */ }
}
Für Multibranch-Pipelines und Organization Folders nutzt das GitHub Branch Source-Plugin denselben /github-webhook/-Endpunkt, aber die Wirkung ist eine andere: Ein Push-Event löst einen Scan des betroffenen Branches aus statt eines Builds eines festen Jobs. Genau das lässt neue Branches und neue Pull Requests innerhalb von Sekunden in Jenkins auftauchen statt erst beim nächsten periodischen Scan. Konfiguriere den Webhook einmal auf Ebene der GitHub-Organisation, und jedes Repository darin ist abgedeckt.
Webhooks mit GitLab
Das GitLab-Plugin arbeitet pro Projekt statt über einen globalen Endpunkt. Jeder Job bekommt seine eigene URL:
https://jenkins.example.com/project/<job-name>
Jobs in Ordnern enthalten den Ordnerpfad – /project/platform/api-service. Die Konfigurationsseite des Jobs zeigt die exakte URL neben der Checkbox Build when a change is pushed to GitLab, dazu einen Generate-Button für das Secret-Token. Kopiere beides in Settings → Webhooks des GitLab-Projekts: die URL ins URL-Feld, das Token in Secret token. Multibranch-Projekte verwenden dieselbe URL-Form.
Lass dich nicht verleiten, den GitLab-Webhook stattdessen auf /job/<name>/build zu richten. Das README des Plugins sagt ausdrücklich, dass dies das Plugin komplett umgeht – du verlierst die Branch- und Merge-Request-Daten aus der Payload und mit ihnen jeden Filter, der davon abhängt.
pipeline {
agent any
triggers {
gitlab(
triggerOnPush: true,
triggerOnMergeRequest: true,
branchFilterType: 'All'
)
}
stages { /* … */ }
}
Arbeitest du mit etwas, das weder GitHub noch GitLab ist – Gitea, ein selbst gehosteter Mirror, ein internes Tool, das Tags pusht –, gibt dir das Generic Webhook Trigger-Plugin einen token-geschützten Endpunkt unter /generic-webhook-trigger/invoke?token=… und lässt dich Werte aus der JSON-Payload an Build-Parameter binden. Für Multibranch-Projekte tut das Multibranch Scan Webhook Trigger-Plugin dasselbe für Scans unter /multibranch-webhook-trigger/invoke?token=… – und akzeptiert das Token als Header statt als Query-Parameter, was es aus den Access-Logs heraushält.
Wann Polling weiterhin die richtige Antwort ist
Webhooks brauchen eine Sache, die nicht jedes Jenkins hat: eine Route vom SCM-Server zu Jenkins. Bleib beim Polling, wenn
- Jenkins hinter einer Firmen-Firewall oder in einem privaten Netz ohne eingehenden Pfad von deinem Git-Host sitzt und du dafür kein Loch in die Firewall bohren willst;
- du das Repository nicht administrierst und ihm keine Webhooks hinzufügen kannst;
- die Quelle gar kein webhook-fähiges System ist – der SVN-Server eines Anbieters, ein Artefakt-Mirror, eine Dateiablage.
Polle in diesen Fällen bewusst statt aus Versehen: H/5 * * * * für etwas wirklich Interaktives, H/30 * * * * oder H * * * * für alles andere – und leichtgewichtiges Polling im ls-remote-Stil, wo immer das SCM-Plugin es unterstützt. Polling als bewusster Fallback mit vernünftigem Intervall ist solides Engineering. Jede Minute auf vierhundert Jobs zu pollen, weil niemand den Standard hinterfragt hat, ist es nicht.
Auch ein Hybrid ist legitim – und oft genau der Zustand, bei dem du am Ende landen willst: Webhooks als schneller Pfad, plus ein langsamer Poll – täglich oder alle paar Stunden – als Rückfallnetz für Events, die durch einen Jenkins-Neustart oder eine fehlgeschlagene Webhook-Zustellung verloren gingen.
Quiet Period: die Einstellung gegen Build-Stürme
Sobald Trigger sofort feuern, werden Schübe sichtbar. Ein Entwickler pusht drei Commits in zwanzig Sekunden, oder ein Skript pusht auf fünf Branches gleichzeitig – und Jenkins reiht für jeden einen Build ein.
Die Quiet Period ist die eingebaute Antwort. Wird ein Build ausgelöst, hält Jenkins ihn einige Sekunden in der Queue; weitere Trigger für denselben Job, die in diesem Fenster eintreffen, gehen in dem wartenden Build auf, statt dahinter eingereiht zu werden. Der globale Standard sind fünf Sekunden (Manage Jenkins → System), und jeder Job kann ihn unter Advanced überschreiben – oder in einer Pipeline mit options { quietPeriod(30) }.
Zwei Dinge solltest du wissen. Das Fenster wird durch spätere Trigger nicht verlängert: Ein Build, der vier Sekunden gewartet hat, startet trotzdem eine Sekunde später – egal, wie viele Pushes noch eintreffen. Und fünf Sekunden sind ein vernünftiger Standard fürs Polling, aber oft zu kurz, sobald du auf Webhooks bist. Pusht dein Team in Schüben, machen 30 bis 60 Sekunden auf den teuren Jobs aus fünf redundanten Builds einen – und der einzige Preis ist eine halbe Minute Latenz, die niemandem aufgefallen wäre.
Schnelle Trigger verdienen schnelles Feedback
Das ändert sich nach der Migration: Beim Polling bestimmte der Trigger, wie viel Zeit zwischen deinem Push und der Gewissheit verging – du hast gepusht, bist Kaffee holen gegangen und hast danach in Jenkins nachgesehen. Mit Webhooks startet der Build in Sekunden, und der langsamste Teil der Schleife bist jetzt du: der Browser-Tab, den du selbst im Blick behalten musst, das Dashboard, das du aktualisierst, der Slack-Kanal, in dem die Benachrichtigung zwischen zweihundert anderen untergeht.
Genau für dieses Problem haben wir unser eigenes Tool gebaut – das Folgende ist also ganz offen Werbung in eigener Sache. BuildCaptain ist unsere App – ein nativer macOS-Menüleisten-Client für Jenkins. Hefte die Pipelines an, die dich interessieren, und ihr Status sitzt in der Menüleiste; wird eine rot, bekommst du eine native Mitteilung, und ein Klick öffnet den Stage-Graphen, das Log des fehlgeschlagenen Steps und einen Rebuild-Button. Du hast Jenkins gerade beigebracht, sofort auf deine Pushes zu reagieren – das hier sorgt dafür, dass du es auch sofort erfährst.
BuildCaptain