Fehlerbehebung bei langsamen Jenkins-Builds: Häufige Engpässe und Lösungen
Identifizieren und beheben Sie häufige Leistungsprobleme, die Ihre Jenkins-Builds beeinträchtigen. Dieser Leitfaden zur Fehlerbehebung bietet praktische Schritte zur Diagnose langsamer Builds durch Analyse von Logs, Optimierung der Executor-Konfiguration, Nutzung von Build-Caching-Mechanismen und Optimierung von Pipeline-Skripten für einen schnelleren, effizienteren CI/CD-Prozess.
Fehlerbehebung bei langsamen Jenkins-Builds: Häufige Engpässe und Lösungen
Langsame Jenkins-Builds schaden, weil sie Feedback verzögern. Ein Entwickler schiebt eine kleine Änderung, wartet zwanzig Minuten und erfährt dann, dass ein Test in der ersten Minute fehlgeschlagen ist. Bevor Sie etwas optimieren, trennen Sie Wartezeit, Agent-Startzeit, Checkout-Zeit, Abhängigkeitseinrichtung, Testzeit, Paketierung und Bereitstellung. Dies sind unterschiedliche Probleme mit unterschiedlichen Lösungen.
Das Ziel ist nicht, Jenkins in einem Dashboard schneller aussehen zu lassen. Das Ziel ist, das nächste nützliche Signal früher eintreffen zu lassen.
1. Erste Diagnose: Wo bleibt die Zeit?
Bevor Sie Korrekturen anwenden, müssen Sie die Ursache der Verlangsamung lokalisieren. Jenkins bietet hervorragende integrierte Werkzeuge für die Erstdiagnose.
Analyse des Build-Logs
Die unmittelbarste Ressource ist die Konsolenausgabe für einen langsamen Build. Achten Sie auf große Lücken in den Zeitstempeln zwischen aufeinanderfolgenden Schritten.
- Identifizieren Sie langlaufende Schritte: Notieren Sie, welche Build-Schritte (z.B.
mvn clean install, Skriptausführung, Abhängigkeitsdownload) die meiste Zeit verbrauchen. - Externe Aufrufe: Achten Sie auf Phasen mit Netzwerkaktivität (z.B. Abrufen externer Abhängigkeiten, Verbindung zu entfernten Artefakt-Repositories). Dies sind oft externe Abhängigkeiten, nicht Jenkins selbst.
Verwendung des Build-Zeitdiagramms
Jenkins Blue Ocean oder klassische UI-Pipelines zeigen oft eine visuelle Aufschlüsselung der Phasendauern. Nutzen Sie diese visuelle Hilfe, um zu bestätigen, welche Phasen unverhältnismäßig lang sind.
Tipp: Wenn eine bestimmte Phase über mehrere Builds hinweg konstant länger als erwartet dauert, ist dies Ihr primäres Optimierungsziel.
2. Jenkins-Infrastruktur-Engpässe
Wenn Build-Schritte selbst schnell sind, aber die Wartezeit zwischen Jobs lang ist, liegt das Problem wahrscheinlich an der Jenkins-Controller- (Master) oder Agent- (Slave) Infrastruktur.
Executor-Verfügbarkeit und Überlastung
Das häufigste Infrastrukturproblem ist unzureichende Build-Kapazität.
Executors verstehen
Executors sind die parallelen Slots, die auf einem Jenkins-Knoten zum Ausführen von Jobs verfügbar sind. Wenn ein Knoten 5 Executors hat, kann er 5 Jobs gleichzeitig ausführen.
- Symptom: Builds warten ständig in der Warteschlange, selbst wenn die CPU-/Speicherauslastung niedrig erscheint.
- Lösung: Erhöhen Sie die Anzahl der Executors auf Ihren primären Build-Knoten oder fügen Sie Ihrer Farm weitere Knoten/Agenten hinzu.
Konfigurationsprüfung (Verwaltung von Agenten): Überprüfen Sie den Agenten-Konfigurationsbildschirm. Stellen Sie sicher, dass die 'Anzahl der Executors' angemessen für die diesem Agenten zugewiesene Hardware eingestellt ist.
Controller-Last
Wenn der Jenkins-Controller-Knoten überlastet ist, kann er Jobs nicht richtig planen, selbst wenn Agenten frei sind.
- Symptome: Langsame UI-Reaktionsfähigkeit, verzögerte Build-Planung oder hohe CPU-/Speicherauslastung, die vom Systemmonitor des Controllers gemeldet wird.
- Lösung: Lagern Sie aufwändige Aufgaben (wie Kompilierung) auf Agenten aus. Stellen Sie sicher, dass der Controller über ausreichende Ressourcen (CPU, reichlich RAM) verfügt, die hauptsächlich für Verwaltungsaufgaben und nicht für das Bauen vorgesehen sind.
Festplatten-I/O-Leistung
Langsame Festplatten-Eingabe/-Ausgabe (I/O) wirkt sich auf Schritte aus, die große Dateioperationen umfassen, wie das Klonen von Git-Repositories oder das Entpacken großer Archive.
- Best Practice: Verwenden Sie schnellen Speicher (SSDs oder Netzwerkspeicher mit hohem Durchsatz) für Jenkins-Workspaces und das Jenkins-Home-Verzeichnis, insbesondere auf Build-Agenten.
3. Pipeline-Skript-Optimierung
Ineffiziente deklarative oder skriptbasierte Pipelines können unnötigen Overhead verursachen.
Workspace-Verwaltung
Große Workspaces, die mit alten Artefakten gefüllt sind, können nachfolgende Operationen wie Klonen oder Bereinigen verlangsamen.
- Verwenden Sie den
ws()-Schritt mit Bedacht: Wenn Sie eine Skript-Pipeline verwenden, achten Sie auf Operationen im gesamten Workspace. - Workspace bereinigen: Konfigurieren Sie Jobs, um den Workspace nach erfolgreichem Abschluss zu bereinigen, oder verwenden Sie den
cleanWs()-Schritt mit Bedacht. Warnung: Bereinigen Sie Workspaces nicht, wenn Sie auf inkrementelle Builds oder Artefakt-Caching zwischen Läufen angewiesen sind.
Redundante Operationen (Abhängigkeitsdownload)
Das wiederholte Herunterladen derselben Abhängigkeiten verschwendet Zeit.
- Abhängigkeiten cachen: Implementieren Sie buildtool-spezifische Caching-Strategien in der Agent-Umgebung (z.B. Maven lokales Repository, npm-Cache). Stellen Sie sicher, dass das Cache-Verzeichnis persistent und nach Möglichkeit gemeinsam genutzt wird.
// Beispiel: Sicherstellung der Maven-Repository-Persistenz auf einem Agenten
steps {
sh 'mvn -B clean install -Dmaven.repo.local=/pfad/zu/geteiltem/maven/cache'
}
Parallelisieren unabhängiger Phasen
Wenn Phasen in Ihrer Pipeline unabhängig sind, führen Sie sie gleichzeitig mit dem parallel-Block in deklarativen Pipelines aus.
pipeline {
agent any
stages {
stage('Build & Test') {
parallel {
stage('Unit Tests') {
steps { sh './run_tests.sh' }
}
stage('Statische Analyse') {
steps { sh './run_sonar.sh' }
}
}
}
stage('Paketieren') {
// Läuft, nachdem beide Build- & Test-Phasen abgeschlossen sind
steps { sh './create_jar.sh' }
}
}
}
4. Nutzung von Build-Caching-Mechanismen
Für Builds, die große Komponenten (wie Docker-Images oder kompilierte Quelldateien) wiederverwenden, ist Caching entscheidend für die Geschwindigkeit.
Docker-Layer-Caching
Wenn Ihre Pipeline Docker-Images erstellt, nutzen Sie Layer-Caching effektiv.
- Reihenfolge ist wichtig: Platzieren Sie Schritte, die sich häufig ändern (z.B.
COPY . .), später im Dockerfile als Schritte, die sich selten ändern (z.B. Installieren von Basisabhängigkeiten). - Verwenden Sie den Docker-Agenten: Wenn Sie Jenkins-Agenten verwenden, die Docker ausführen, stellen Sie sicher, dass der Build-Prozess vorhandene lokale Image-Caches nutzt, bevor ein vollständiger Pull/Build versucht wird.
Inkrementelle Builds
Stellen Sie sicher, dass Ihre Build-Tools für inkrementelle Builds konfiguriert sind, wo dies anwendbar ist (z.B. Gradles Build-Cache oder Verwendung bestimmter Compiler-Flags).
5. Agentenkonfiguration und Ressourcenzuweisung
Agenten sind der Ort, an dem die eigentliche Arbeit erledigt wird. Stellen Sie sicher, dass sie korrekt bereitgestellt und konfiguriert sind.
Hardware-Größenanpassung
Wenn die CPU-Auslastung während Builds hoch ist, benötigt der Agent mehr Rechenleistung. Wenn Builds häufig auf Ressourcen (wie Speicher) warten, skalieren Sie den RAM hoch.
Agenten-Startmethode
- Statische Agenten: Schnellerer Start, aber weniger flexibel für die Skalierung.
- Dynamische Agenten (z.B. Kubernetes- oder EC2-Agenten): Obwohl die Einrichtung etwas länger dauert, stellen diese Agenten sicher, dass Ressourcen genau dann skaliert werden, wenn sie benötigt werden, und vermeiden lange Warteschlangen zu Spitzenzeiten.
Best Practice: Stellen Sie für dynamische Skalierung sicher, dass die Startzeit für einen neuen Agenten deutlich kürzer ist als die Zeit, die ein Job in der Warteschlange hat, bevor er timeoutet. Wenn die Agentenbereitstellung 10 Minuten dauert, Jobs aber nur 3 Minuten warten, hilft die Skalierung nicht beim unmittelbaren Engpass.
Ein praktisches Playbook für langsame Builds
- Logs analysieren: Bestimmen Sie, welcher Pipeline-Schritt die meiste Zeit verbraucht.
- Executors prüfen: Überprüfen Sie, ob die Anzahl der Agenten-Executors der erwarteten gleichzeitigen Last entspricht.
- I/O optimieren: Stellen Sie sicher, dass Workspaces und Caches auf schnellem Speicher liegen.
- Abhängigkeiten cachen: Implementieren Sie Persistenz für Maven-, npm- oder andere Abhängigkeits-Caches.
- Parallelisieren: Schreiben Sie unabhängige Pipeline-Phasen um, um sie gleichzeitig auszuführen.
- Tools profilieren: Stellen Sie sicher, dass Build-Tools (Maven, Gradle) inkrementelle Build-Funktionen verwenden.
Indem Sie diese potenziellen Engpässe methodisch angehen – von der Infrastrukturkapazität bis zur Skripteffizienz – können Sie langsame, frustrierende Builds in schnelle, zuverlässige Komponenten Ihres CI/CD-Workflows verwandeln.
Eine ehrlichere Art, einen langsamen Build zu lesen
Der schnellste Weg, einen Nachmittag zu verschwenden, ist, jeden langsamen Jenkins-Build als Jenkins-Problem zu behandeln. Manchmal ist Jenkins der Engpass. Oft ist es nur der Überbringer. Eine Pipeline kann langsam erscheinen, weil sie in der Warteschlange wartet, weil der Agent lange zum Starten braucht, weil der Git-Checkout sich hinzieht, weil das Build-Tool das Internet erneut herunterlädt, weil Tests serialisiert sind oder weil ein nachgelagerter Bereitstellungsschritt auf ein anderes System wartet.
Wenn ich mir einen langsamen Job ansehe, teile ich die Gesamtzeit in vier Buckets auf: Wartezeit, Agentenbereitstellungszeit, Workspace-Einrichtungszeit und tatsächliche Build-/Testzeit. Jenkins zeigt einen Teil davon auf der Build-Seite und in der Pipeline-Phasenansicht, aber das Konsolenprotokoll ist immer noch die nützlichste Aufzeichnung. Fügen Sie Zeitstempel hinzu, wenn sie fehlen. Vergleichen Sie dann einen langsamen Lauf mit einem normalen Lauf. Sie suchen nach der ersten Stelle, an der die beiden Zeitlinien voneinander abweichen.
Wenn der langsame Lauf beispielsweise acht Minuten verbringt, bevor der erste Shell-Befehl startet, hilft das Optimieren von Maven nicht. Überprüfen Sie die Executor-Verfügbarkeit, das Label-Matching, die Cloud-Agenten-Bereitstellung und ausstehende Jobs. Wenn der langsame Lauf schnell startet, aber fünf Minuten für git fetch benötigt, überprüfen Sie die Repository-Größe, Refspecs, Tags, den Netzwerkpfad und die Workspace-Wiederverwendung. Wenn der Checkout schnell ist, aber npm ci jedes Mal langsam ist, überprüfen Sie die Cache-Persistenz und den Registry-Zugriff vom Agenten aus.
Optimieren Sie nicht aus dem Gedächtnis. Wählen Sie drei aktuelle Builds aus: einen schnellen, einen typischen und einen langsamen. Notieren Sie die Dauer jeder Phase. Diese kleine Tabelle zeigt normalerweise auf die richtige Ebene.
Wartezeit: Der Engpass, bevor der Build startet
Wartezeit ist leicht zu ignorieren, weil noch nichts fehlgeschlagen ist. Entwickler sehen nur einen Build, der dort sitzt. In Jenkins bedeutet eine lange Warteschlange normalerweise eine von vier Sachen: Es gibt nicht genug Executors, Labels sind zu eng, ein Lock serialisiert die Arbeit oder dynamische Agenten erscheinen langsam.
Beginnen Sie mit der Job-Seite und dem Executor-Status-Panel. Wenn viele Agenten im Leerlauf sind, der Job aber in der Warteschlange steht, ist der Label-Ausdruck möglicherweise zu streng. Ein Job mit der Bezeichnung linux && docker && java17 && large kann nur auf Knoten ausgeführt werden, die jedes Label erfüllen. Das kann für einen Produktions-Release-Build beabsichtigt sein, ist aber oft versehentlich für normale Pull-Request-Prüfungen. Wenn ein allgemeiner Build nur Docker und Java benötigt, binden Sie ihn nicht an eine spezielle Maschine, es sei denn, es gibt einen triftigen Grund.
Locks sind eine weitere leise Verzögerungsquelle. Das Lockable Resources Plugin ist nützlich, wenn Tests exklusiven Zugriff auf eine gemeinsame Datenbank, ein Hardwaregerät oder einen Staging-Namespace benötigen. Es wird schmerzhaft, wenn zu viel Arbeit innerhalb des Locks sitzt. Halten Sie den gesperrten Abschnitt so klein wie möglich. Bauen Sie das Artefakt außerhalb des Locks, erwerben Sie das Lock, führen Sie nur den Schritt mit der gemeinsamen Ressource aus und geben Sie es frei.
Messen Sie bei Cloud-Agenten die Startzeit separat. Ein Kubernetes-Pod, der zwei Minuten zur Planung benötigt, kann in Ordnung sein. Ein Pod, der fünfzehn Minuten benötigt, weil er bei jedem Lauf ein großes benutzerdefiniertes Image zieht, ist es nicht. Ziehen Sie gängige Images vorab, reduzieren Sie die Image-Größe oder halten Sie einen kleinen Warmpool bereit, wenn Ihr CI-Verkehr vorhersagbar ist.
Checkout-Zeit: Git kann das ganze Problem sein
Langsamer Checkout ist in älteren Jenkins-Installationen üblich, weil Repositories allmählich wachsen. Niemand bemerkt die ersten paar großen Binärdateien, dann zahlt eines Tages jeder Build für Jahre der Geschichte.
Verwenden Sie die Git-Plugin-Einstellungen sorgfältig. Ein flacher Klon kann Jobs helfen, die nur den aktuellen Commit benötigen, aber er kann Builds beschädigen, die Versionen aus Tags berechnen oder gegen vorherige Commits vergleichen. Das Abrufen von Tags kann in tag-reichen Repositories ebenfalls überraschend viel Zeit hinzufügen. Wenn der Job keine Tags benötigt, deaktivieren Sie das Abrufen von Tags. Wenn die Pipeline mehrere Repositories auscheckt, messen Sie jeden Checkout separat, damit ein langsames Abhängigkeits-Repository nicht in einer generischen "SCM"-Phase versteckt ist.
Die Workspace-Wiederverwendung ist ein Kompromiss. Die Wiederverwendung eines Workspace kann git fetch viel schneller machen, aber veraltete Dateien können seltsame Fehler verursachen. Das Löschen des Workspace vor jedem Build ist sauber, kann aber für große Monorepos teuer sein. Ein praktischer Mittelweg ist die Verwendung von sauberen Checkout-Befehlen, die unversionierte Dateien entfernen, während das .git-Verzeichnis erhalten bleibt, oder das vollständige Löschen des Workspace für fehlgeschlagene Builds und geplante Bereinigungen zu reservieren.
Auf stark ausgelasteten Agenten kann die Checkout-Geschwindigkeit auch ein Festplattenproblem sein. Wenn zehn Builds große Repositories auf demselben kleinen Volume klonen, kann die CPU in Ordnung aussehen, während die Festplatten-I/O gesättigt ist. Überprüfen Sie iostat, Cloud-Volume-Metriken oder das Speicher-Dashboard des Agenten, während Builds laufen. Das Verschieben von Workspaces auf schnellere lokale SSD-Speicher kann die Build-Zeit mehr ändern als jede Jenkins-Einstellung.
Abhängigkeits-Caches brauchen Besitzer
Caching ist nur hilfreich, wenn jemand es besitzt. Ein Cache, der zufällig verschwindet, unbegrenzt wächst oder inkompatible Tool-Versionen mischt, kann mehr Ärger verursachen, als er spart.
Für Maven und Gradle kann ein persistentes lokales Repository oder ein Build-Cache wiederholte Downloads reduzieren. Der Cache sollte außerhalb des wegwerfbaren Workspace leben. Er sollte auch für gleichzeitige Builds sicher sein. Mavens lokales Repository ist normalerweise in Ordnung für normale Abhängigkeitslesevorgänge, aber unterbrochene Downloads können beschädigte Dateien hinterlassen. Wenn Sie Prüfsummenfehler oder beschädigte Artefakte sehen, löschen Sie den spezifischen Abhängigkeitspfad, anstatt den gesamten Cache aus Gewohnheit zu löschen.
Für npm bevorzugen Sie npm ci für reproduzierbare Installationen und cachen Sie den npm-Paket-Cache anstelle von node_modules, es sei denn, Sie wissen, dass das Betriebssystem, die CPU-Architektur, die Node-Version und die Lockdatei stabil sind. Das Cachen von node_modules über verschiedene Agenten-Images hinweg ist ein klassischer Weg, um native Modulfehler zu erhalten, die nur in CI auftreten.
Für Docker-Builds ist der wertvollste Cache normalerweise der Layer-Cache. Platzieren Sie stabile Abhängigkeitsinstallationsschritte vor den Quellcode-Kopierschritten im Dockerfile. Wenn der Docker-Daemon pro Build-Pod isoliert ist und jedes Mal leer startet, hilft lokales Layer-Caching nicht viel. Verwenden Sie in diesem Fall BuildKit-Cache-Export/Import oder einen registrierungsgestützten Cache, wenn Ihre Umgebung dies unterstützt.
Testzeit: Sorgfältig parallelisieren
Tests sind oft der längste Teil einer gesunden Pipeline. Das Ziel ist nicht einfach, mehr Dinge parallel laufen zu lassen. Das Ziel ist, Feedback zu verkürzen, ohne flaky Ergebnisse zu erzeugen.
Unit-Tests lassen sich normalerweise gut parallelisieren. Integrationstests sind kniffliger, weil sie gemeinsame Datenbanken, Ports, Warteschlangen, Buckets oder externe Konten teilen können. Wenn zwei Testbranches in dasselbe Schema schreiben oder denselben Warteschlangennamen wiederverwenden, kann die parallele Ausführung die Pipeline gleichzeitig schneller und weniger zuverlässig machen. Geben Sie jedem Branch seinen eigenen Namespace, sein eigenes Datenbankschema, sein eigenes temporäres Verzeichnis und seinen eigenen Dienstportbereich, wo möglich.
Teilen Sie Testsuiten nach gemessener Dauer auf, nicht nach Dateianzahl. Zehn kleine Testdateien können schneller laufen als ein großer Browser-Test. Viele Teams erzielen bessere Ergebnisse, indem sie Testdauern aufzeichnen und Gruppen so ausbalancieren, dass jeder parallele Zweig ungefähr die gleiche Zeit benötigt.
Achten Sie auch auf langsames Scheitern. Eine Testphase, die zehn Minuten auf einen toten Dienst wartet, bevor sie fehlschlägt, ist schlimmer als eine Phase, die in dreißig Sekunden mit einem klaren Health Check fehlschlägt. Setzen Sie explizite Bereitschaftsprüfungen vor lange Testbefehle und legen Sie Timeouts für Netzwerkaufrufe fest, die hängen bleiben können.
Controller-Gesundheit ist immer noch wichtig
Build-Arbeit gehört auf Agenten, aber der Controller plant immer noch Jobs, bedient Logs, wertet Pipeline-Logik aus, lädt Plugins und bearbeitet UI-Traffic. Wenn der Controller überlastet ist, fühlt sich jeder Job langsamer an, selbst wenn Agenten freie Kapazität haben.
Achten Sie auf langsame UI-Seiten, verzögerte Konsolenlog-Updates, lange Garbage-Collection-Pausen und hohe Controller-CPU. Große Pipeline-Logs, zu viele aufbewahrte Builds, aggressives Polling und schwere Plugins können alle Last hinzufügen. Halten Sie die Build-Aufbewahrung realistisch. Archivieren Sie nur Artefakte, die Leute brauchen. Verschieben Sie große Testberichte und Logs auf externen Speicher, wenn Ihr Jenkins-Home-Volume überlastet ist.
Vermeiden Sie es, Builds auf dem Controller auszuführen. Es mag für einen kleinen Job harmlos erscheinen, aber es macht Vorfälle schwieriger zu analysieren. Der Controller sollte koordinieren. Agenten sollten kompilieren, testen, paketieren und bereitstellen.
Eine praktische Reihenfolge der Operationen
Wenn ein Team fragt, warum Jenkins langsam ist, verwenden Sie diese Reihenfolge:
- Messen Sie Wartezeit im Vergleich zur Ausführungszeit.
- Finden Sie die langsamste Phase aus aktuellen Builds.
- Vergleichen Sie einen langsamen Lauf mit einem normalen Lauf.
- Prüfen Sie, ob die Verzögerung Warten, Checkout, Abhängigkeitsdownload, Tests, Paketierung oder Bereitstellung ist.
- Beheben Sie einen Engpass und messen Sie erneut.
Dieser letzte Schritt ist wichtig. Wenn der Checkout von sechs Minuten auf eine Minute fällt, feiern Sie kurz und messen Sie weiter. Der nächste Engpass wird sichtbar. CI-Leistungsarbeit ist normalerweise eine Abfolge kleiner, verifizierter Verbesserungen und nicht eine magische Einstellung.