Effektive Jenkins-Build-Caching-Strategien für CI/CD-Geschwindigkeit
Beschleunigen Sie Ihre Jenkins-CI/CD-Pipelines durch die Beherrschung von Build-Caching-Strategien. Dieser Leitfaden beschreibt praktische Methoden zur Wiederverwendung von Abhängigkeiten, Compiler-Ausgaben und Docker-Layern über mehrere Builds hinweg. Erfahren Sie, wie Sie Workspace-Aufbewahrung, Docker-Build-Optionen und gemeinsame Caching-Techniken nutzen, um redundante Aufgaben zu minimieren und Ihre Integrations- und Bereitstellungsprozesse erheblich zu beschleunigen.
Effektive Jenkins-Build-Caching-Strategien für CI/CD-Geschwindigkeit
Jenkins-Build-Caching ist keine einzelne Funktion. Es ist eine Reihe von Entscheidungen darüber, was zwischen Builds sicher wiederverwendet werden kann. Ein guter Cache spart Abhängigkeits-Downloads, Docker-Layer, Compiler-Arbeit und Paketmanager-Metadaten. Ein schlechter Cache verbirgt fehlerhafte Builds, füllt Festplatten oder führt dazu, dass eine Pipeline auf einem Agenten erfolgreich ist und auf einem anderen fehlschlägt.
Beginnen Sie damit, den langsamsten wiederholten Schritt im Job-Log zu identifizieren. Wenn jeder Build zwei Minuten damit verbringt, Maven-Artefakte herunterzuladen, cachen Sie Maven. Wenn Docker jedes Mal dieselben Basis-Layer neu erstellt, beheben Sie das Docker-Caching. Wenn Tests langsam sind, weil die Kompilierung bei jedem Lauf von Null beginnt, verwenden Sie den eigenen Cache des Build-Tools, bevor Sie ein Jenkins-Level-Archiv erfinden.
Workspaces behalten, wenn sie helfen, bereinigen, wenn sie schaden
Der einfachste Cache ist der vorhandene Jenkins-Workspace auf einem persistenten Agenten. Wenn derselbe Job auf demselben Knoten läuft, können Dateien, die vom vorherigen Build übrig geblieben sind, wiederverwendet werden.
Das kann bei Tools wie Maven, Gradle, npm, pnpm, Cargo und Go helfen. Es kann auch zu seltsamen Fehlern führen, wenn generierte Dateien, alte Testberichte oder veraltete Build-Ausgaben im Workspace verbleiben.
Ein häufiger Kompromiss ist, nur den Quellbaum zu bereinigen und dedizierte Cache-Verzeichnisse außerhalb davon zu behalten:
pipeline {
agent { label 'linux-build' }
environment {
MAVEN_OPTS = '-Dmaven.repo.local=/var/cache/jenkins/maven'
npm_config_cache = '/var/cache/jenkins/npm'
}
stages {
stage('Checkout') {
steps {
deleteDir()
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn -B test'
}
}
}
}
Dies hält den Workspace reproduzierbar, während heruntergeladene Abhängigkeiten wiederverwendet werden. Stellen Sie sicher, dass die Berechtigungen des Cache-Verzeichnisses mit dem Benutzer übereinstimmen, der den Agenten ausführt.
Abhängigkeiten mit den Regeln des Tools cachen
Abhängigkeits-Caches funktionieren am besten, wenn der Paketmanager sie kontrolliert. Archivieren und stellen Sie node_modules nicht über nicht verwandte Agenten hinweg wieder her, es sei denn, Sie haben einen triftigen Grund. Es ist normalerweise sicherer, den Download-Store des Paketmanagers zu cachen.
Für npm:
environment {
npm_config_cache = "${WORKSPACE}/.npm-cache"
}
steps {
sh 'npm ci'
}
Für pnpm:
environment {
PNPM_STORE_PATH = "${WORKSPACE}/.pnpm-store"
}
steps {
sh 'pnpm install --frozen-lockfile'
}
Für Maven verwenden Sie einen stabilen lokalen Repository-Pfad:
sh 'mvn -B -Dmaven.repo.local=/var/cache/jenkins/m2 test'
Für Gradle halten Sie GRADLE_USER_HOME stabil und aktivieren Sie den Gradle-Build-Cache im Projekt, wenn angemessen:
environment {
GRADLE_USER_HOME = '/var/cache/jenkins/gradle'
}
steps {
sh './gradlew test --build-cache'
}
Die Lockfile ist Ihre Sicherheitsleine. Wenn sich package-lock.json, pnpm-lock.yaml, pom.xml oder build.gradle ändert, sollte das Tool die richtigen neuen Abhängigkeiten abrufen. Wenn ein Cache weiterhin alte oder beschädigte Pakete ausliefert, löschen Sie ihn und lassen Sie ihn vom Tool neu aufbauen.
Docker-Layer-Caching
Docker-Caching hängt davon ab, wo der Docker-Daemon Layer speichert. Auf einem langlebigen VM-Agenten kann docker build Layer automatisch wiederverwenden. Auf ephemeren Kubernetes-Agenten landet der nächste Build oft auf einem frischen Pod ohne Layer-Verlauf.
Für persistente Docker-Agenten setzen Sie langsam ändernde Dockerfile-Zeilen zuerst:
FROM node:22-bookworm
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm test
Wenn Sie das gesamte Repository vor npm ci kopieren, invalidiert jede Quellcodeänderung die Abhängigkeitsebene.
Für ephemere Agenten verwenden Sie BuildKit-Registry-Caching:
docker buildx build \
--cache-from type=registry,ref=registry.example.com/my-app:buildcache \
--cache-to type=registry,ref=registry.example.com/my-app:buildcache,mode=max \
-t registry.example.com/my-app:${GIT_COMMIT} \
--push .
Dieser Cache wird über die Registry anstelle des lokalen Daemons geteilt. Er ist normalerweise besser für Kubernetes-basierte Jenkins-Agenten geeignet, als zu versuchen, ein beschreibbares Docker-Layer-Verzeichnis in viele Pods zu mounten.
Artefakte archivieren, nicht Caches, es sei denn, Sie meinen es ernst
archiveArtifacts ist nützlich für Build-Ausgaben, die Sie behalten möchten: JARs, Testberichte, Coverage-Dateien, generierte Pakete. Es ist kein großartiger universeller Cache-Speicher. Große Abhängigkeitsarchive verlangsamen den Controller, erhöhen den Speicherdruck und erschweren die Bereinigung.
Wenn Sie einen agentenübergreifenden Cache benötigen, bevorzugen Sie einen echten externen Cache-Speicherort: ein Artefakt-Repository, einen Objektspeicher-Bucket, einen Paket-Proxy oder einen Registry-Cache. Für Java-Builds liefert ein Repository-Manager wie Nexus oder Artifactory oft bessere Ergebnisse als das Kopieren von .m2 zwischen Jenkins-Agenten. Für Docker ist ein registry-gestützter BuildKit-Cache vorhersagbarer als das Tarballing von Layer-Verzeichnissen.
Cache-Schlüssel sichtbar machen
Ein Cache sollte einen Grund haben, gültig zu sein. Gute Cache-Schlüssel umfassen das Betriebssystem, die Architektur, die Hauptsprachversion, die Paketmanager-Version und den Lockfile-Hash.
Zum Beispiel könnte ein Node-Cache-Schlüssel basieren auf:
linux-amd64-node22-npm10-sha256(package-lock.json)
Sie benötigen kein ausgefallenes Cache-Plugin, um die Idee anzuwenden. Sogar ein Verzeichnisname kann den Schlüssel tragen:
sh '''
LOCK_HASH=$(sha256sum package-lock.json | awk '{print $1}')
export npm_config_cache="/var/cache/jenkins/npm/node22-${LOCK_HASH}"
npm ci
'''
Dies vermeidet die Wiederverwendung eines Abhängigkeits-Caches über inkompatible Tool-Versionen hinweg. Es macht auch die Bereinigung weniger mysteriös, da alte Schlüssel leicht zu identifizieren sind.
Achten Sie auf die Fehlermodi
Caching hat einige bekannte Fehlermuster:
- Builds bestehen nur auf einem Agenten, weil dieser Agent eine versteckte Datei im Workspace hat.
- Die Festplatte füllt sich, weil alte Caches nie bereinigt werden.
- Docker-Images werden von Grund auf neu erstellt, weil das Dockerfile volatile Dateien zu früh kopiert.
- Ein gemeinsam genutzter Cache wird beschädigt, wenn mehrere Builds gleichzeitig darauf schreiben.
- Das Wiederherstellen eines riesigen Caches dauert länger als das Herunterladen von Abhängigkeiten von einem nahegelegenen Paket-Proxy.
Build-Logs sollten zeigen, ob der Cache verwendet wird. Maven zeigt an, wann es Abhängigkeiten herunterlädt. Docker zeigt CACHED oder Cache-Fehler mit BuildKit-Ausgabe an. Gradle hat Build-Scan und Konsolenausgabe für das Cache-Verhalten. Wenn ein Cache unsichtbar ist, fügen Sie Logging zu seinem Pfad, seiner Größe und seinem Schlüssel hinzu.
Ein praktischer Rollout-Plan
Wählen Sie eine Pipeline und einen Engpass. Fügen Sie einen Cache nur für diesen Schritt hinzu. Führen Sie denselben Commit zweimal aus und vergleichen Sie die Logs. Führen Sie dann nach Änderung der Abhängigkeits-Lockfile aus und bestätigen Sie, dass der Cache korrekt invalidiert wird. Fügen Sie schließlich die Bereinigung hinzu.
Für persistente Agenten kann die Bereinigung ein Cron-Job oder ein Jenkins-Wartungsjob sein:
find /var/cache/jenkins/npm -mindepth 1 -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
find /var/cache/jenkins/m2 -type f -name '*.lastUpdated' -delete
docker system prune -af --filter 'until=168h'
Passen Sie diese Befehle an Ihre Umgebung an. Führen Sie keine breiten Prune-Befehle auf gemeinsam genutzten Hosts aus, ohne zu überprüfen, was sonst noch denselben Docker-Daemon oder dasselbe Dateisystem verwendet.
Die beste Jenkins-Build-Caching-Strategie ist langweilig: Cachen Sie die teure wiederholte Arbeit, lassen Sie das Build-Tool die Korrektheit validieren, halten Sie Cache-Pfade explizit und löschen Sie alte Daten, bevor die Agentenfestplatte zum nächsten Engpass wird.
Passen Sie den Cache an das Agentenmodell an
Persistente VM-Agenten und wegwerfbare Cloud-Agenten benötigen unterschiedliche Cache-Designs. Auf einer persistenten VM sind lokale Verzeichnisse billig und schnell. Ein Maven-Cache in /var/cache/jenkins/m2 oder ein Docker-Layer-Cache auf dem Daemon können wochenlang überleben. Das Risiko ist Festplattenwachstum und veralteter Zustand.
Auf wegwerfbaren Agenten verschwinden lokale Caches nach jedem Build. Sie können trotzdem cachen, aber der Cache muss woanders leben: Objektspeicher, ein Paket-Proxy, eine Container-Registry oder ein persistentes Volume. Persistente Volumes in Kubernetes können funktionieren, aber gemeinsam genutzte beschreibbare Caches erfordern Sorgfalt. Zwei Builds, die gleichzeitig auf denselben Cache schreiben, können Sperrkonflikte erzeugen oder teilweise Downloads beschädigen, je nach Tool.
Für viele Teams ist die beste erste Investition kein Jenkins-Plugin. Es ist ein nahegelegener Abhängigkeits-Proxy:
- Maven oder Gradle über Nexus oder Artifactory.
- npm über eine private Registry oder einen Proxy.
- Docker über einen Registry-Mirror.
- Python über einen Paket-Mirror oder internen Index.
Das verbessert jeden Agenten, ohne zu Beginn jedes Jobs riesige Tarballs wiederherzustellen.
Wissen, wann man nicht cachen sollte
Cachen Sie keine Geheimnisse, generierten Anmeldeinformationen, Bereitstellungsmanifeste mit Token oder Verzeichnisse, die Build-Ausgabe mit umgebungsspezifischer Konfiguration mischen. Cachen Sie keine Testdatenbanken, es sei denn, die Testsuite ist explizit für Snapshot-Wiederherstellung ausgelegt. Teilen Sie keine veränderlichen Caches zwischen vertrauenswürdigen und nicht vertrauenswürdigen Jobs.
Seien Sie auch skeptisch beim Caching, wenn der Wiederherstellungsschritt größer ist als die Arbeit, die er spart. Ein 2-GB-Archiv, das 90 Sekunden zum Herunterladen und Entpacken benötigt, ist nicht hilfreich, wenn der Paketmanager sauber in 45 Sekunden von einem lokalen Proxy installieren kann.
Messen Sie kalte und warme Builds:
kalter Build: kein lokaler Cache
warmer Build: gleicher Commit, gleicher Cache-Schlüssel
geänderter Abhängigkeits-Build: Lockfile geändert
geänderter Quellcode-Build: nur Quellcode geändert
Diese vier Läufe zeigen Ihnen, ob der Cache dem tatsächlichen Workflow hilft oder nur einen künstlichen Wiederaufbau gut aussehen lässt.
Machen Sie die Cache-Bereinigung zum Teil des Designs
Jeder Cache benötigt eine Ablaufgeschichte, bevor er live geht. Auf statischen Agenten planen Sie die Bereinigung außerhalb der Haupt-Build-Zeiten. Auf gemeinsam genutzten Registries oder Objektspeichern verwenden Sie Lebenszyklusrichtlinien. In Jenkins verfolgen Sie die Cache-Größe als operative Metrik, nicht als nachträglichen Einfall.
Es ist normal, Caches während eines Vorfalls zu löschen. Eine gute Pipeline sollte nach dem Löschen des Caches langsamer werden, nicht kaputt gehen. Wenn das Löschen eines Caches den Build zerstört, verbirgt der Cache eine fehlende Abhängigkeitsdeklaration oder eine Umgebungsannahme.