I contenitori sono diventati il mezzo preferito per imballare un'applicazione con tutte le dipendenze software e di sistema operativo, e poi consegnarli in ambienti diversi.
In questo articolo si esplorano diversi metodi di containerizzazione di un'applicazione Spring Boot:
- creazione di un'immagine Docker utilizzando un file Docker,
- creazione di un'immagine OCI dal codice sorgente utilizzando Cloud-Native Buildpack,
- e ottimizzazione dell'immagine in fase di esecuzione, suddividendo le parti JAR su diversi livelli utilizzando strumenti multi-livello.
Esempio di codice
Questo articolo è accompagnato da un esempio di codice funzionante. .
Terminologia dei contenitori
Iniziamo con la terminologia dei contenitori utilizzata nell'articolo:
- Immagine del contenitore (Container image): un file di un formato specifico. Convertiamo la nostra applicazione in un'immagine del contenitore eseguendo uno strumento di build.
- Container: un'istanza eseguibile dell'immagine del contenitore.
- Motore del contenitore (Container engine): un processo daemon responsabile dell'esecuzione del contenitore.
- Host del contenitore (Container host): il computer host su cui opera il meccanismo del contenitore.
- Registro dei contenitori (Container registry): posizione generale utilizzata per la pubblicazione e distribuzione dell'immagine del contenitore.
- Standard OCI: è una struttura di gestione aperta e leggera, formata nell'ambito della Linux Foundation. La specifica delle immagini OCI definisce standard industriali per i formati delle immagini dei contenitori e per l'ambiente di esecuzione, per garantire che tutti i meccanismi dei contenitori possano eseguire immagini dei contenitori create da qualsiasi strumento di build.
Per inserire un'applicazione in un contenitore, racchiudiamo la nostra applicazione in un'immagine del contenitore e pubblichiamo questa immagine in un registro pubblico. L'ambiente di esecuzione del contenitore estrae questa immagine dal registro, la decomprime e avvia l'applicazione al suo interno.
La versione 2.3 di Spring Boot fornisce plugin per la creazione di immagini OCI.
è l'implementazione del contenitore più comunemente utilizzata, e noi utilizziamo Docker nei nostri esempi, quindi tutti i collegamenti successivi al contenitore in questo articolo si riferiranno a Docker.
Costruire un'immagine del contenitore nel modo tradizionale
Creare immagini Docker per le applicazioni Spring Boot è molto semplice, aggiungendo alcune istruzioni al file Docker.
Iniziamo creando un file eseguibile JAR e, come parte delle istruzioni del file Docker, copiamo il file JAR eseguibile sopra l'immagine di base JRE dopo aver applicato le necessarie configurazioni.
Creiamo la nostra applicazione Spring su con le dipendenze web, lomboke actuator. Aggiungiamo anche un controller REST per fornire un'API con GETil metodo.
Creazione del file Docker
Successivamente mettiamo questa applicazione in un container, aggiungendo Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]Il nostro file Docker contiene un'immagine di base da adoptopenjdk, sopra la quale copiamo il nostro file JAR e poi apriamo la porta, 8080che ascolterà le richieste.
Build dell'applicazione
Iniziamo creando l'applicazione con Maven o Gradle. Qui utilizziamo Maven:
mvn clean packageQuesto crea un file JAR eseguibile dell'applicazione. Dobbiamo convertire questo JAR eseguibile in un'immagine Docker per funzionare nel motore Docker.
Creazione dell'immagine del container
Poi mettiamo questo file JAR eseguibile nell'immagine Docker eseguendo il comando docker builddalla cartella principale del progetto che contiene il file Docker creato in precedenza:
docker build -t usersignup:v1 .Possiamo visualizzare la nostra immagine nell'elenco utilizzando il comando:
docker images Il risultato dell'esecuzione del comando sopra menzionato include la nostra immagine usersignupinsieme all'immagine di base, adoptopenjdk, specificata nel nostro file Docker.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBVisualizzare i layer all'interno dell'immagine del container
Esaminiamo la pila dei layer all'interno dell'immagine. Useremo per visualizzare questi layer:
dive usersignup:v1Ecco parte dei risultati dell'esecuzione del comando Dive:

Come possiamo vedere, il livello applicativo costituisce una parte significativa delle dimensioni dell'immagine. Vogliamo ridurre le dimensioni di questo layer nelle sezioni successive durante la nostra ottimizzazione.
Creazione dell'immagine del container utilizzando Buildpack
() è un termine generico utilizzato da varie offerte di "Piattaforma come Servizio" (PaaS) per creare un'immagine del container dal codice sorgente. È stato lanciato da Heroku nel 2011 e da allora è stato adottato da Cloud Foundry, Google App Engine, Gitlab, Knative e altri.

Il vantaggio dei pacchetti di costruzione cloud
Uno dei principali vantaggi dell'utilizzo di Buildpack per la creazione delle immagini è che le modifiche alla configurazione dell'immagine possono essere gestite centralmente (builder) e distribuite a tutte le applicazioni che utilizzano il builder.
I Buildpacks erano strettamente legati alla piattaforma. I Cloud-Native Buildpacks garantiscono standardizzazione tra le piattaforme, supportando il formato immagine OCI, che assicura che l'immagine possa essere eseguita dal motore Docker.
Utilizzo del plugin Spring Boot
Il plugin Spring Boot crea immagini OCI dal codice sorgente utilizzando Buildpack. Le immagini sono create utilizzando bootBuildImagetask (Gradle) o spring-boot:build-imagegoal (Maven) e installazione locale di Docker.
Possiamo configurare il nome dell'immagine da inviare al registro Docker specificando il nome in image tag:
org.springframework.boot
spring-boot-maven-plugin
docker.io/pratikdas/${project.artifactId}:v1Utilizziamo Maven per eseguire build-imagegoal per creare l'applicazione e l'immagine del contenitore. Attualmente non stiamo utilizzando alcun file Docker.
mvn spring-boot:build-imageIl risultato sarà simile a questo:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Costruendo l'immagine 'docker.io/pratikdas/usersignup:v1'
[INFO]
[INFO] > Scaricando l'immagine builder 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator] Aggiunta l'etichetta 'org.springframework.boot.version'
.. [creator] *** Immagini (c311fe74ec73):
.. [creator] docker.io/pratikdas/usersignup:v1
[INFO]
[INFO] Immagine 'docker.io/pratikdas/usersignup:v1' costruita con successoDai dati di uscita vediamo che paketo Cloud-Native buildpackviene utilizzato per creare un'immagine OCI funzionante. Come prima, possiamo vedere l'immagine specificata come immagine Docker eseguendo il comando:
docker images Risultato:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBCreazione dell'immagine del contenitore utilizzando Jib
Jib è un plugin per creare immagini di Google che fornisce un metodo alternativo per creare un'immagine del contenitore dal codice sorgente.
Configurare jib-maven-pluginin pom.xml:
com.google.cloud.tools
jib-maven-plugin
2.5.2Successivamente, eseguiamo il plugin Jib utilizzando il comando Maven per costruire l'applicazione e creare l'immagine del contenitore. Come prima, qui non utilizziamo alcun file Docker:
mvn compile jib:build -Dimage=/usersignup:v1Dopo aver eseguito il comando Maven sopra menzionato, otteniamo il seguente output:
[INFO] Containerizzando l'applicazione in pratikdas/usersignup:v1...
.
.
[INFO] Punto di ingresso del container impostato su [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Immagine costruita e pubblicata come pratikdas/usersignup:v1
[INFO] Esecuzione dei task:
[INFO] [==============================] 100,0% completatoL'output mostra che l'immagine del container è stata creata e inserita nel registro.
Motivazioni e metodi per la creazione di immagini ottimizzate
Abbiamo due motivi principali per l'ottimizzazione:
- Prestazioni: nel sistema di orchestrazione dei container, l'immagine del container viene estratta dal registro delle immagini sull'host in cui è in esecuzione il meccanismo del container. Questo processo è chiamato pianificazione. L'estrazione di immagini di grandi dimensioni dal registro porta a tempi di pianificazione prolungati nei sistemi di orchestrazione dei container e a tempi di costruzione prolungati nei pipeline CI.
- Sicurezza: le immagini di grandi dimensioni presentano anche una maggiore superficie di vulnerabilità.
Un'immagine Docker è composta da uno stack di layer, ognuno dei quali rappresenta un'istruzione nel nostro Dockerfile. Ogni strato rappresenta una delta delle modifiche dello strato sottostante. Quando estraiamo un'immagine Docker da un registro, essa viene scaricata a strati e mantenuta nella cache sull'host.
Spring Boot utilizza La formula di ottimizzazione si concentra sull'isolamento dell'applicazione su un livello separato dalle dipendenze del Spring Framework.
Lo strato di dipendenze, che costituisce la maggior parte del file JAR pesante, viene caricato solo una volta e memorizzato nella cache nel sistema host.
Solo lo strato sottile dell'applicazione viene estratto durante gli aggiornamenti dell'applicazione e la pianificazione dei contenitori,
come mostrato in questo diagramma: Nelle sezioni seguenti vedremo come creare queste immagini ottimizzate per un'applicazione Spring Boot.

Creazione di un'immagine di contenitore ottimizzata per un'applicazione Spring Boot utilizzando Buildpack
Creazione di un'immagine del contenitore ottimizzata per l'applicazione Spring Boot utilizzando Buildpack
Spring Boot 2.3 supporta la multi-livello estraendo parti di un file JAR spesso in strati separati. La funzionalità di layering è disabilitata per default e deve essere attivata esplicitamente con il plugin Spring Boot Maven:
org.springframework.boot
spring-boot-maven-plugin
trueUtilizzeremo questa configurazione per creare la nostra immagine del container prima con Buildpack e poi con Docker nei prossimi paragrafi.
Eseguiamo build-imagel'obiettivo Maven per costruire l'immagine del container:
mvn spring-boot:build-imageSe eseguiamo Dive per vedere i livelli nell'immagine risultante, noteremo che il livello dell'applicazione (evidenziato in rosso) è di gran lunga più piccolo in kilobyte rispetto a quello ottenuto utilizzando il formato JAR spesso:

Creazione di un'immagine del container ottimizzata per un'applicazione Spring Boot con Docker
Invece di utilizzare il plugin Maven o Gradle, possiamo anche creare un'immagine JAR multi-livello Docker con un file Docker.
Quando utilizziamo Docker, dobbiamo eseguire due passaggi aggiuntivi per estrarre gli strati e copiarli nell'immagine finale.
Il contenuto del JAR ottenuto dopo la costruzione con Maven con la funzione di stratificazione attivata sarà il seguente:
META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idxNell'output viene visualizzato un JAR aggiuntivo con il nome spring-boot-jarmode-layertoolse layersfle.idxfile. Questo file JAR aggiuntivo offre la possibilità di elaborazione multilivello, come descritto nella sezione successiva.
Estrazione delle dipendenze su strati separati
Per visualizzare ed estrarre gli strati dal nostro JAR multilivello, utilizziamo la proprietà di sistema -Djarmode=layertoolsper avviare spring-boot-jarmode-layertoolsJAR invece dell'applicazione:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarEseguendo questo comando si ottiene un output contenente le opzioni disponibili:
Usage:
java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar
Available commands:
list Elenca gli strati dal JAR che possono essere estratti
extract Estrae gli strati dal JAR per la creazione dell'immagine
help Aiuto su qualsiasi comandoL'output mostra i comandi list, extracte helpcon helpcome predefinito. Eseguiamo il comando con listl'opzione:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdipendenze
spring-boot-loader
dipendenze-snapshot
applicazioneVediamo l'elenco delle dipendenze che possono essere aggiunte come layer.
Layer predefiniti:
Nome del layer
Contenuto
dependencies
qualsiasi dipendenza la cui versione non contiene SNAPSHOT
spring-boot-loader
Classi del caricatore JAR
dipendenze-snapshot
qualsiasi dipendenza la cui versione contiene SNAPSHOT
applicazione
classi delle applicazioni e risorse
I layer sono definiti in layers.idxun file nell'ordine in cui devono essere aggiunti all'immagine Docker. Questi layer vengono memorizzati nella cache sull'host dopo il primo recupero, poiché non cambiano. Solo il layer dell'app aggiornata viene caricato sull'host, il che avviene più rapidamente grazie alla dimensione ridotta. .
Costruzione dell'immagine con dipendenze recuperate in layer separati.
Costruiremo l'immagine finale in due fasi, utilizzando un metodo chiamato . Nella prima fase recupereremo le dipendenze e nella seconda fase copieremo le dipendenze recuperate nell'immagine finale.
Modifichiamo il nostro file Docker per la build multi-stage:
# the first stage of our build will extract the layers
FROM adoptopenjdk:14-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
# the second stage of our build will copy the extracted layers
FROM adoptopenjdk:14-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]Salviamo questa configurazione in un file separato — Dockerfile2.
Costruiamo l'immagine Docker con il comando:
docker build -f Dockerfile2 -t usersignup:v1 .Dopo aver eseguito questo comando, otteniamo il seguente output:
Invio del contesto di build al demone Docker 20,41MB
Passo 1/12: FROM adoptopenjdk:14-jre-hotspot come builder
14-jre-hotspot: Scaricamento da library/adoptopenjdk
.
.
Costruito con successo a9ebf6970841
Etichettato con successo userssignup:v1Possiamo vedere che l'immagine Docker viene creata con un ID immagine e poi etichettata.
Infine, eseguiamo il comando Dive, come prima, per controllare i livelli all'interno dell'immagine Docker generata. Possiamo specificare l'ID immagine o il tag come input per il comando Dive:
dive userssignup:v1Come si può vedere dall'output, il livello contenente l'applicazione ora occupa solo 11 KB, mentre le dipendenze sono memorizzate in livelli separati.

Estrazione delle dipendenze interne in livelli separati
Possiamo ulteriormente ridurre le dimensioni del livello dell'applicazione estraendo alcune delle nostre dipendenze personalizzate in un livello separato invece di includerle insieme all'applicazione, dichiarandole nel ymlun file come layers.idx:
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "custom-dependencies":
- "io/myorg/"
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"In questo file layers.idxabbiamo aggiunto una dipendenza personalizzata con il nome, io.myorgcon dipendenze organizzative ottenute da un repository condiviso.
Risultato
In questo articolo, abbiamo esaminato l'uso di Cloud-Native Buildpacks per creare un'immagine del contenitore direttamente dal codice sorgente. Questa è un'alternativa all'uso di Docker per creare un'immagine del contenitore nel modo tradizionale: prima si crea un file eseguibile JAR spesso, e poi si impacchetta in un'immagine del contenitore fornendo istruzioni nel file Docker.
Abbiamo anche discusso l'ottimizzazione del nostro contenitore, abilitando la funzione di sovrapposizione, che estrae le dipendenze in livelli separati, che vengono memorizzati nella cache sull'host, mentre uno strato sottile dell'applicazione viene caricato durante la pianificazione nei meccanismi di esecuzione del contenitore.
Puoi trovare tutto il codice sorgente utilizzato nell'articolo su .
Guida ai comandi
Ecco un riepilogo dei comandi che abbiamo usato in questo articolo per una rapida consultazione.
Pulizia del contesto:
docker system prune -aCreazione dell'immagine del contenitore utilizzando un file Docker:
docker build -f -t .Compilazione dell'immagine del contenitore dal codice sorgente (senza Dockerfile):
mvn spring-boot:build-imageVisualizzazione degli strati di dipendenza. Prima di compilare il file JAR dell'applicazione, assicurati che la funzione di stratificazione sia attivata in spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar listEstrazione degli strati di dipendenza. Prima di compilare il file JAR dell'applicazione, assicurati che la funzione di stratificazione sia attivata in spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar extractVisualizzazione dell'elenco delle immagini dei contenitori
docker imagesVisualizzazione degli strati all'interno dell'immagine del contenitore (assicurati che sia installato lo strumento di immersione):
diveFonte: habr.com
