Strategie Efficaci di Caching delle Build Jenkins per la Velocità CI/CD

Accelera le tue pipeline CI/CD Jenkins padroneggiando le strategie di caching delle build. Questa guida descrive metodi pratici per riutilizzare dipendenze, output del compilatore e layer Docker tra le build. Impara come sfruttare la conservazione dell'area di lavoro, le opzioni di build Docker e le tecniche di caching condiviso per minimizzare le attività ridondanti e velocizzare significativamente i tuoi processi di integrazione e distribuzione.

Strategie Efficaci di Caching delle Build Jenkins per la Velocità CI/CD

Il caching delle build Jenkins non è una singola funzionalità. È un insieme di decisioni su cosa può essere riutilizzato in modo sicuro tra le build. Una buona cache salva download di dipendenze, layer Docker, lavoro del compilatore e metadati del gestore di pacchetti. Una cattiva cache nasconde build danneggiate, riempie i dischi o fa sì che una pipeline funzioni su un agente e fallisca su un altro.

Inizia osservando il passaggio ripetuto più lento nel log del job. Se ogni build impiega due minuti per scaricare gli artefatti Maven, fai caching di Maven. Se Docker ricostruisce gli stessi layer di base ogni volta, correggi il caching di Docker. Se i test sono lenti perché la compilazione parte da zero ad ogni esecuzione, usa la cache del tool di build prima di inventare un archivio a livello Jenkins.

Mantieni gli spazi di lavoro quando aiutano, puliscili quando danneggiano

La cache più semplice è l'area di lavoro Jenkins esistente su un agente persistente. Se lo stesso job viene eseguito sullo stesso nodo, i file lasciati dalla build precedente possono essere riutilizzati.

Questo può aiutare con strumenti come Maven, Gradle, npm, pnpm, Cargo e Go. Può anche causare strani fallimenti quando file generati, vecchi report di test o output di build obsoleti rimangono nell'area di lavoro.

Un compromesso comune è pulire solo l'albero dei sorgenti e mantenere directory di cache dedicate al di fuori di esso:

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'
      }
    }
  }
}

Questo mantiene l'area di lavoro riproducibile mentre riutilizza le dipendenze scaricate. Assicurati che i permessi della directory della cache corrispondano all'utente che esegue l'agente.

Fai caching delle dipendenze con le regole dello strumento

Le cache delle dipendenze funzionano meglio quando il gestore di pacchetti le controlla. Non archiviare e ripristinare node_modules tra agenti non correlati a meno che non ci sia una forte ragione. Di solito è più sicuro fare caching del deposito di download del gestore di pacchetti.

Per npm:

environment {
  npm_config_cache = "${WORKSPACE}/.npm-cache"
}
steps {
  sh 'npm ci'
}

Per pnpm:

environment {
  PNPM_STORE_PATH = "${WORKSPACE}/.pnpm-store"
}
steps {
  sh 'pnpm install --frozen-lockfile'
}

Per Maven, usa un percorso di repository locale stabile:

sh 'mvn -B -Dmaven.repo.local=/var/cache/jenkins/m2 test'

Per Gradle, mantieni GRADLE_USER_HOME stabile e abilita la cache di build Gradle nel progetto quando appropriato:

environment {
  GRADLE_USER_HOME = '/var/cache/jenkins/gradle'
}
steps {
  sh './gradlew test --build-cache'
}

Il file di blocco è la tua guida di sicurezza. Se package-lock.json, pnpm-lock.yaml, pom.xml o build.gradle cambia, lo strumento dovrebbe recuperare le nuove dipendenze corrette. Se una cache continua a servire pacchetti vecchi o corrotti, cancellala e lascia che lo strumento la ricostruisca.

Caching dei layer Docker

Il caching Docker dipende da dove il demone Docker memorizza i layer. Su un agente VM di lunga durata, la normale docker build può riutilizzare automaticamente i layer. Su agenti Kubernetes effimeri, la build successiva spesso atterra su un pod nuovo senza cronologia dei layer.

Per agenti Docker persistenti, metti le righe del Dockerfile che cambiano lentamente per prime:

FROM node:22-bookworm
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm test

Se copi l'intero repository prima di npm ci, ogni modifica al codice sorgente invalida il layer delle dipendenze.

Per agenti effimeri, usa il caching del registro BuildKit:

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 .

Quella cache è condivisa attraverso il registro invece del demone locale. Di solito è una soluzione migliore per agenti Jenkins basati su Kubernetes rispetto al tentativo di montare una directory scrivibile di layer Docker in molti pod.

Archivia artefatti, non cache, a meno che non sia intenzionale

archiveArtifacts è utile per gli output di build che vuoi conservare: JAR, report di test, file di copertura, pacchetti generati. Non è un buon archivio di cache generico. Grandi archivi di dipendenze rallentano il controller, aumentano la pressione di archiviazione e rendono dolorosa la pulizia.

Se hai bisogno di una cache cross-agente, preferisci una vera posizione di cache esterna: un repository di artefatti, un bucket di object storage, un proxy di pacchetti o una cache di registro. Per build Java, un gestore di repository come Nexus o Artifactory spesso dà risultati migliori che copiare .m2 tra agenti Jenkins. Per Docker, una cache BuildKit basata su registro è più prevedibile che comprimere directory di layer in tarball.

Rendi visibili le chiavi della cache

Una cache dovrebbe avere una ragione per essere valida. Buone chiavi di cache includono il sistema operativo, l'architettura, la versione principale del linguaggio, la versione del gestore di pacchetti e l'hash del file di blocco.

Ad esempio, una chiave di cache Node potrebbe essere basata su:

linux-amd64-node22-npm10-sha256(package-lock.json)

Non hai bisogno di un plugin di cache sofisticato per applicare l'idea. Anche un nome di directory può portare la chiave:

sh '''
LOCK_HASH=$(sha256sum package-lock.json | awk '{print $1}')
export npm_config_cache="/var/cache/jenkins/npm/node22-${LOCK_HASH}"
npm ci
'''

Questo evita di riutilizzare una cache di dipendenze tra versioni di strumenti incompatibili. Rende anche la pulizia meno misteriosa perché le chiavi vecchie sono facili da identificare.

Osserva le modalità di fallimento

Il caching ha alcuni modelli di fallimento familiari:

  • Le build funzionano solo su un agente perché quell'agente ha un file nascosto nell'area di lavoro.
  • Il disco si riempie perché le cache vecchie non vengono mai eliminate.
  • Le immagini Docker vengono ricostruite da zero perché il Dockerfile copia file volatili troppo presto.
  • Una cache condivisa si corrompe quando più build vi scrivono contemporaneamente.
  • Il ripristino di una cache enorme richiede più tempo del download delle dipendenze da un proxy di pacchetti vicino.

I log di build dovrebbero mostrare se la cache viene utilizzata. Maven dice quando scarica le dipendenze. Docker mostra CACHED o cache miss con l'output di BuildKit. Gradle ha la scansione di build e l'output della console per il comportamento della cache. Se una cache è invisibile, aggiungi log attorno al suo percorso, dimensione e chiave.

Un piano di rollout pratico

Scegli una pipeline e un collo di bottiglia. Aggiungi una cache solo per quel passaggio. Esegui lo stesso commit due volte e confronta i log. Poi esegui dopo aver cambiato il file di blocco delle dipendenze e conferma che la cache si invalida correttamente. Infine, aggiungi la pulizia.

Per agenti persistenti, la pulizia può essere un cron job o un job di manutenzione Jenkins:

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'

Regola quei comandi per il tuo ambiente. Non eseguire comandi di pulizia ampi su host condivisi senza verificare cos'altro usa lo stesso demone Docker o filesystem.

La migliore strategia di caching delle build Jenkins è noiosa: fai caching del lavoro costoso ripetuto, lascia che lo strumento di build convalidi la correttezza, mantieni i percorsi della cache espliciti e cancella i dati vecchi prima che il disco dell'agente diventi il prossimo collo di bottiglia.

Abbina la cache al modello di agente

Gli agenti VM persistenti e gli agenti cloud usa-e-getta necessitano di diversi design di cache. Su una VM persistente, le directory locali sono economiche e veloci. Una cache Maven in /var/cache/jenkins/m2 o una cache di layer Docker sul demone possono sopravvivere per settimane. Il rischio è la crescita del disco e lo stato obsoleto.

Su agenti usa-e-getta, le cache locali svaniscono dopo ogni build. Puoi ancora fare caching, ma la cache deve risiedere altrove: object storage, un proxy di pacchetti, un registro contenitori o un volume persistente. I volumi persistenti in Kubernetes possono funzionare, ma le cache condivise scrivibili richiedono attenzione. Due build che scrivono sulla stessa cache contemporaneamente possono creare contesa di blocco o corrompere download parziali a seconda dello strumento.

Per molti team, il miglior primo investimento non è un plugin Jenkins. È un proxy di dipendenze vicino:

  • Maven o Gradle tramite Nexus o Artifactory.
  • npm tramite un registro privato o proxy.
  • Docker tramite un mirror di registro.
  • Python tramite un mirror di pacchetti o indice interno.

Questo migliora ogni agente senza ripristinare enormi tarball all'inizio di ogni job.

Sapere quando non fare caching

Non fare caching di segreti, credenziali generate, manifest di distribuzione contenenti token o directory che mescolano output di build con configurazione specifica dell'ambiente. Non fare caching di database di test a meno che la suite di test non sia esplicitamente progettata per il ripristino di snapshot. Non condividere cache mutabili tra job fidati e non fidati.

Sii anche scettico sul caching quando il passaggio di ripristino è più grande del lavoro che risparmia. Un archivio da 2 GB che impiega 90 secondi per essere scaricato e decompresso non è utile se il gestore di pacchetti può installare pulitamente in 45 secondi da un proxy locale.

Misura le build a freddo e a caldo:

build a freddo: nessuna cache locale
build a caldo: stesso commit, stessa chiave di cache
build con dipendenza cambiata: file di blocco cambiato
build con sorgente cambiata: solo sorgente cambiata

Queste quattro esecuzioni ti dicono se la cache aiuta il flusso di lavoro reale o fa solo sembrare buona una ricostruzione artificiale.

Rendi la pulizia della cache parte del design

Ogni cache ha bisogno di una storia di scadenza prima di essere attivata. Su agenti statici, programma la pulizia al di fuori delle ore di punta delle build. Su registri condivisi o object storage, usa policy di ciclo di vita. In Jenkins, traccia la dimensione della cache come metrica operativa, non come ripensamento.

È normale cancellare le cache durante un incidente. Una buona pipeline dovrebbe diventare più lenta dopo la cancellazione della cache, non rotta. Se la cancellazione di una cache rompe la build, la cache sta nascondendo una dichiarazione di dipendenza mancante o un presupposto ambientale.