I contenitori sono diventati il mezzo preferito per imballare le applicazioni con tutte le dipendenze del software e del sistema operativo, e poi per consegnarle a diversi ambienti.
In questo articolo vengono considerati vari metodi di containerizzazione dell'applicazione Spring Boot:
- creazione di un'immagine Docker utilizzando un file Docker,
- creazione di un'immagine OCI dal codice sorgente con Cloud-Native Buildpack,
- e ottimizzazione dell'immagine durante l'esecuzione suddividendo parti del JAR in diversi livelli usando strumenti multistrato.
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 certo formato. Convertiamo la nostra applicazione in un'immagine del contenitore eseguendo uno strumento di compilazione.
- Contenitore: un'istanza eseguibile dell'immagine del contenitore.
- Motore del contenitore (Container engine): un processo demone responsabile dell'esecuzione del contenitore.
- Host del contenitore (Container host): il computer host su cui opera il motore del contenitore.
- Registro dei contenitori (Container registry): un'ubicazione comune utilizzata per pubblicare e distribuire l'immagine del contenitore.
- Standard OCI: è una struttura di governance aperta e leggera, formata all'interno della Linux Foundation. La specifica dell'immagine OCI definisce standard settoriali per i formati delle immagini dei contenitori e gli ambienti di esecuzione, garantendo che tutti i motori dei contenitori possano eseguire immagini dei contenitori generate da qualsiasi strumento di compilazione.
Per inserire l'applicazione in un contenitore, racchiudiamo la nostra applicazione in un'immagine del contenitore e pubblichiamo quest'immagine in un registro comune. L'ambiente di esecuzione del contenitore estrae quest'immagine dal registro, la decomprime e avvia l'applicazione al suo interno.
La versione 2.3 di Spring Boot offre plugin per creare immagini OCI.
è l'implementazione di contenitore più comunemente utilizzata, e noi utilizziamo Docker nei nostri esempi, quindi tutti i seguenti riferimenti al contenitore in questo articolo si riferiranno a Docker.
Creazione dell'immagine del contenitore nel modo tradizionale
Creare immagini Docker per 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 sopra l'immagine di base JRE dopo aver applicato le impostazioni necessarie.
Creiamo la nostra applicazione Spring su con dipendenze web, lomboke actuator. Aggiungiamo anche un controller rest per fornire API con e username/password: admin/admin.metodo.
Creazione del file Docker
Poi mettiamo questa applicazione nel contenitore, 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 copriamo il nostro file JAR e poi apriamo la porta, 8080che ascolterà le richieste.
Costruzione dell'applicazione
Per prima cosa, dobbiamo creare l'applicazione utilizzando Maven o Gradle. Qui utilizziamo Maven:
mvn clean packageQuesto genera un file JAR eseguibile dell'applicazione. Dobbiamo convertire questo file JAR eseguibile in un'immagine Docker per l'esecuzione nel motore Docker.
Creazione dell'immagine del contenitore
Dopo mettiamo questo file JAR eseguibile nell'immagine Docker eseguendo il comando docker builddalla radice del progetto contenente il file Docker creato in precedenza:
docker build -t usersignup:v1 .Possiamo vedere la nostra immagine nell'elenco utilizzando il comando:
docker images Il risultato dell'esecuzione del comando sopra 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 229MBVisualizzazione degli strati all'interno dell'immagine del contenitore
Diamo un'occhiata alla pila di strati all'interno dell'immagine. Useremo per visualizzare questi strati:
dive usersignup:v1Ecco parte dei risultati dell'esecuzione del comando Dive:

Come possiamo vedere, il livello dell'applicazione rappresenta una parte significativa della dimensione dell'immagine. Vogliamo ridurre la dimensione di questo strato nelle sezioni successive nell'ambito della nostra ottimizzazione.
Creazione dell'immagine del contenitore utilizzando Buildpack
() sono un termine generico utilizzato da varie offerte di "Piattaforma come servizio" (PAAS) per creare un'immagine del contenitore 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 buildpack nel cloud
Uno dei principali vantaggi dell'utilizzare i Buildpack per creare immagini è che Le modifiche alla configurazione dell'immagine possono essere gestite centralmente (builder) e distribuite a tutte le applicazioni che utilizza il builder.
I pacchetti di build erano strettamente legati alla piattaforma. I Cloud-Native Buildpacks offrono standardizzazione tra le piattaforme, supportando il formato immagine OCI, che garantisce 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 vengono create utilizzando bootBuildImagetask (Gradle) oppure spring-boot:build-imagegoal (Maven) e l'installazione locale di Docker.
Possiamo configurare il nome dell'immagine necessaria per l'invio al registro Docker specificando il nome in image tag:
<plugin>
<groupId>org.springframework.boot<\/groupId>
<artifactId>spring-boot-maven-plugin<\/artifactId>
<configuration>
<image>
<name>docker.io\/pratikdas\/${project.artifactId}:v1<\/name>
<\/image>
<\/configuration>
<\/plugin>Utilizziamo Maven per eseguire la 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à più o meno il seguente:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Costruzione dell'immagine 'docker.io\/pratikdas\/usersignup:v1'
[INFO]
[INFO] > Estrazione dell'immagine builder 'gcr.io\/paketo-buildpacks\/builder:base-platform-api-0.3' 0%
.
.
.. [creatore] Aggiunta dell'etichetta 'org.springframework.boot.version'
.. [creatore] *** Immagini (c311fe74ec73):
.. [creatore] docker.io\/pratikdas\/usersignup:v1
[INFO]
[INFO] Immagine 'docker.io\/pratikdas\/usersignup:v1' costruita con successoDai risultati, vediamo che il paketo Cloud-Native buildpackviene utilizzato per costruire un'immagine OCI funzionante. Come prima, possiamo vedere l'immagine, indicata come immagine Docker, eseguendo il comando:
docker images Conclusione:
REPOSITORY SIZE
paketobuildpacks\/run 84.3MB
gcr.io\/paketo-buildpacks\/builder 652MB
pratikdas\/usersignup 257MBCreazione dell'immagine del contenitore con Jib
Jib è un plugin per la creazione di immagini di Google che fornisce un metodo alternativo per costruire un'immagine del contenitore dal codice sorgente.
Configuriamo jib-maven-pluginnel pom.xml:
<plugin>
<groupId>com.google.cloud.tools<\/groupId>
<artifactId>jib-maven-plugin<\/artifactId>
<version>2.5.2<\/version>
<\/plugin>Successivamente, eseguiamo il plugin Jib utilizzando il comando Maven per costruire l'applicazione e creare l'immagine del contenitore. Come prima, qui non stiamo utilizzando alcun file Docker:
mvn compile jib:build -Dimage=<docker registry name>\/usersignup:v1Dopo aver eseguito il comando Maven sopra indicato, otteniamo il seguente output:
[INFO] Contenitore dell'applicazione in fase di creazione per pratikdas/usersignup:v1...
.
.
[INFO] Punto di ingresso del contenitore impostato su [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Immagine costruita e inviata come pratikdas/usersignup:v1
[INFO] Esecuzione delle attività:
[INFO] [==============================] 100,0% completatoI dati di output mostrano che l'immagine del contenitore è stata creata e posizionata nel registro.
Motivazioni e metodi per la creazione di immagini ottimizzate
Abbiamo due principali motivi per ottimizzare:
- Prestazioni: nel sistema di orchestrazione dei contenitori, l'immagine del contenitore viene estratta dal registro delle immagini sull'host in cui è in esecuzione il motore del contenitore. Questo processo è chiamato pianificazione. L'estrazione di immagini di grandi dimensioni dal registro porta a tempi di pianificazione prolungati nei sistemi di orchestrazione dei contenitori e a tempi di build lunghi nei pipeline CI.
- Sicurezza: immagini di grandi dimensioni hanno anche una superficie maggiore per vulnerabilità.
Un'immagine Docker è composta da uno stack di livelli, ognuno dei quali rappresenta un'istruzione nel nostro Dockerfile. Ogni livello rappresenta una delta delle modifiche rispetto al livello sottostante. Quando estraiamo un'immagine Docker dal registro, essa viene estratta a livelli e messa in 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.
Il livello delle dipendenze, che forma la parte principale del fat JAR, viene caricato solo una volta e messo in cache nel sistema host.
Solo il sottile livello 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 del contenitore ottimizzata per un'applicazione Spring Boot utilizzando Buildpack
Spring Boot 2.3 supporta la multi-layering estraendo parti del fat JAR in livelli separati. La funzione di layering è disabilitata per impostazione predefinita e deve essere esplicitamente abilitata tramite il plugin Spring Boot Maven:
Spring Boot 2.3 supporta la multilivello estraendo parti di un file JAR pesante in strati separati. La funzione di overlay è disabilitata per impostazione predefinita e deve essere attivata esplicitamente utilizzando il plugin Spring Boot Maven:
org.springframework.boot
spring-boot-maven-plugin
trueUtilizzeremo questa configurazione per creare la nostra immagine del contenitore prima con Buildpack e poi con Docker nelle sezioni successive.
Avviamo build-imagel'obiettivo di Maven per creare l'immagine del contenitore:
mvn spring-boot:build-imageSe avviamo Dive per vedere i layer nell'immagine risultante, noteremo che il layer dell'applicazione (evidenziato in rosso) è molto più piccolo, nell'ordine di kilobyte, rispetto a quello che abbiamo ottenuto utilizzando il formato JAR pesante:

Creazione di un'immagine del contenitore ottimizzata per un'applicazione Spring Boot con Docker
Invece di utilizzare il plugin Maven o Gradle, possiamo anche creare un'immagine JAR Docker multi-layer con un file Docker.
Quando utilizziamo Docker, dobbiamo eseguire due passaggi aggiuntivi per estrarre i layer e copiarli nell'immagine finale.
Il contenuto del JAR risultante dopo la creazione con Maven con la funzionalità di layering abilitata sarà simile al 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 di nome spring-boot-jarmode-layertoolse layersfle.idxfile. Questo file JAR aggiuntivo fornisce la funzionalità di gestione multi-layer, come descritto nella sezione successiva.
Estrazione delle dipendenze su layer separati
Per visualizzare ed estrarre i layer dal nostro JAR multi-layer, utilizziamo la proprietà di sistema -Djarmode=layertoolsper eseguire spring-boot-jarmode-layertoolsJAR anziché l'applicazione:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarEseguendo questo comando otteniamo un output contenente i parametri di comando disponibili:
Usage:
java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar
Available commands:
list Elenca i layer dal jar che possono essere estratti
extract Estrae i layer dal jar per la creazione dell'immagine
help Aiuto su qualsiasi comandoL'output mostra i comandi list, extracte helpcon helpessere per impostazione predefinita. Eseguiamo il comando con listl'opzione:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
applicationVediamo un elenco delle dipendenze che possono essere aggiunte come layer.
Layer di default:
Nome del layer
Contenuto
dependencies
qualsiasi dipendenza la cui versione non contiene SNAPSHOT
spring-boot-loader
Classi del caricatore JAR
snapshot-dependencies
qualsiasi dipendenza la cui versione contiene SNAPSHOT
application
classi delle applicazioni e risorse
I livelli sono definiti in layers.idxun file nell'ordine in cui devono essere aggiunti all'immagine Docker. Questi livelli vengono memorizzati nella cache sull'host dopo il primo download, poiché non vengono modificati. Solo il livello aggiornato dell'applicazione viene caricato sull'host, il che avviene più rapidamente a causa delle dimensioni ridotte. .
Costruendo l'immagine con le dipendenze estratte in livelli separati.
Costruiremo l'immagine finale in due fasi, utilizzando un metodo chiamato . Nella prima fase estrarremo le dipendenze, e nella seconda fase copieremo le dipendenze estratte nell'immagine finale.
Modifichiamo il nostro file Docker per la compilazione 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 l'output seguente:
Inviando il contesto di build al demone Docker 20.41MB
Passo 1/12 : DA adoptopenjdk:14-jre-hotspot come builder
14-jre-hotspot: Downloading from library/adoptopenjdk
.
.
Costruito con successo a9ebf6970841
Etichettato con successo userssignup:v1Vediamo che l'immagine Docker viene creata con un identificatore dell'immagine e poi viene etichettata.
Infine, eseguiamo il comando Dive, come prima, per controllare i livelli all'interno dell'immagine Docker generata. Possiamo specificare l'identificativo dell'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 nella cache in livelli separati.

Estrazione delle dipendenze interne in livelli separati.
Possiamo ulteriormente ridurre le dimensioni del livello dell'applicazione estraendo qualsiasi dipendenza personalizzata in un livello separato, anziché imballarle insieme all'applicazione, dichiarandole in ymlun file simile chiamato 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 di nome, io.myorgcontenente le dipendenze dell'organizzazione, ottenute da un repository comune.
Conclusione
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 in modo tradizionale: prima si crea un file JAR eseguibile e poi lo si imballa in un'immagine del contenitore specificando le istruzioni nel file Docker.
Abbiamo anche visto come ottimizzare il nostro contenitore includendo la funzionalità di sovrapposizione, che estrae le dipendenze in singoli livelli memorizzati nella cache sull'host, mentre lo strato sottile dell'applicazione viene caricato durante il scheduling 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 utilizzato in questo articolo per una rapida consultazione.
Pulizia del contesto:
docker system prune -aCreazione dell'immagine del contenitore utilizzando il file Docker:
docker build -f -t .Costruiamo un'immagine del contenitore dal codice sorgente (senza Dockerfile):
mvn spring-boot:build-imageVisualizzazione dei livelli delle dipendenze. Prima di costruire il file JAR dell'applicazione, assicurati che la funzionalità di sovrapposizione sia abilitata nel spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar listEstrazione dei livelli delle dipendenze. Prima di costruire il file JAR dell'applicazione, assicurati che la funzionalità di sovrapposizione sia abilitata nel spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar extractVisualizzazione dell'elenco delle immagini del contenitore
docker imagesVisualizzazione dei layer all'interno dell'immagine del contenitore (assicurati che l'outil dive sia installato):
diveFonte: habr.com
