Creazione di immagini Docker con Spring Boot

1. Introduzione

Man mano che sempre più organizzazioni si spostano verso container e server virtuali, Docker sta diventando una parte più significativa dei flussi di lavoro di sviluppo software. A tal fine, una delle grandi nuove funzionalità di Spring Boot 2.3 è la possibilità di creare facilmente un'immagine Docker per le applicazioni Spring Boot.

In questo tutorial vedremo come creare immagini Docker per un'applicazione Spring Boot.

2. Build Docker tradizionali

Il modo tradizionale di creare immagini Docker con Spring Boot consiste nell'usare un Dockerfile. Di seguito è riportato un semplice esempio:

FROM openjdk:8-jdk-alpine EXPOSE 8080 ARG JAR_FILE=target/demo-app-1.0.0.jar ADD ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]

Potremmo quindi utilizzare il comando di compilazione docker per creare un'immagine Docker. Funziona bene per la maggior parte delle applicazioni, ma ci sono un paio di svantaggi.

Innanzitutto, stiamo usando il fat jar creato da Spring Boot. Ciò può influire sul tempo di avvio, soprattutto in un ambiente containerizzato . Possiamo risparmiare tempo di avvio aggiungendo invece i contenuti esplosi del file jar.

In secondo luogo, le immagini Docker sono costruite in livelli. La natura dei fat jars di Spring Boot fa sì che tutto il codice dell'applicazione e le librerie di terze parti vengano inserite in un unico livello. Ciò significa che anche quando cambia solo una singola riga di codice, l'intero livello deve essere ricostruito .

Esplodendo il jar prima della creazione, il codice dell'applicazione e le librerie di terze parti ottengono ciascuno il proprio livello. Questo ci consente di sfruttare il meccanismo di memorizzazione nella cache di Docker. Ora, quando viene modificata una riga di codice, è necessario ricostruire solo il livello corrispondente.

Con questo in mente, diamo un'occhiata a come Spring Boot ha migliorato il processo di creazione di immagini Docker.

3. Buildpacks

I buildpack sono uno strumento che fornisce framework e dipendenze dalle applicazioni .

Ad esempio, dato un fat jar di Spring Boot, un buildpack ci fornirebbe il runtime Java. Questo ci consente di saltare il Dockerfile e ottenere automaticamente un'immagine Docker ragionevole.

Spring Boot include sia il supporto Maven che Gradle per i buildpack. Ad esempio, compilando con Maven, avremmo eseguito il comando:

./mvnw spring-boot:build-image

Diamo un'occhiata ad alcuni degli output pertinenti per vedere cosa sta succedendo:

[INFO] Building jar: target/demo-0.0.1-SNAPSHOT.jar ... [INFO] Building image 'docker.io/library/demo:0.0.1-SNAPSHOT' ... [INFO] > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 100% ... [INFO] [creator] ===> DETECTING [INFO] [creator] 5 of 15 buildpacks participating [INFO] [creator] paketo-buildpacks/bellsoft-liberica 2.8.1 [INFO] [creator] paketo-buildpacks/executable-jar 1.2.8 [INFO] [creator] paketo-buildpacks/apache-tomcat 1.3.1 [INFO] [creator] paketo-buildpacks/dist-zip 1.3.6 [INFO] [creator] paketo-buildpacks/spring-boot 1.9.1 ... [INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT' [INFO] Total time: 44.796 s

La prima riga mostra che abbiamo costruito il nostro barattolo grasso standard, proprio come qualsiasi tipico pacchetto Maven.

La riga successiva inizia la compilazione dell'immagine Docker. Subito dopo, vediamo la build pull nel builder Packeto.

Packeto è un'implementazione di buildpack cloud-native. Svolge il lavoro di analisi del nostro progetto e determinazione dei framework e delle librerie richiesti . Nel nostro caso, determina che abbiamo un progetto Spring Boot e aggiunge i buildpack richiesti.

Infine, vediamo l'immagine Docker generata e il tempo totale di compilazione. Nota come la prima volta che costruiamo, dedichiamo una discreta quantità di tempo a scaricare i buildpack e creare diversi livelli.

Una delle grandi caratteristiche dei buildpacks è che l'immagine Docker è composta da più livelli. Quindi, se modifichiamo solo il codice dell'applicazione, le build successive saranno molto più veloci:

... [INFO] [creator] Reusing layer 'paketo-buildpacks/executable-jar:class-path' [INFO] [creator] Reusing layer 'paketo-buildpacks/spring-boot:web-application-type' ... [INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT' ... [INFO] Total time: 10.591 s

4. Barattoli a strati

In alcuni casi, potremmo preferire non utilizzare i buildpack: forse la nostra infrastruttura è già collegata a un altro strumento o abbiamo già Dockerfile personalizzati che vogliamo riutilizzare.

Per questi motivi, Spring Boot supporta anche la creazione di immagini Docker utilizzando vasi a più livelli . Per capire come funziona, diamo un'occhiata a un tipico layout fat jar di Spring Boot:

org/ springframework/ boot/ loader/ ... BOOT-INF/ classes/ ... lib/ ...

Il barattolo grasso è composto da 3 aree principali:

  • Classi bootstrap richieste per avviare l'applicazione Spring
  • Codice dell'applicazione
  • Librerie di terze parti

Con i jar a strati, la struttura sembra simile, ma otteniamo un nuovo file layers.idx che mappa ogni directory nel fat jar su un livello:

- "dependencies": - "BOOT-INF/lib/" - "spring-boot-loader": - "org/" - "snapshot-dependencies": - "application": - "BOOT-INF/classes/" - "BOOT-INF/classpath.idx" - "BOOT-INF/layers.idx" - "META-INF/"

Immediatamente, Spring Boot fornisce quattro livelli:

  • dipendenze : dipendenze tipiche da terze parti
  • snapshot-dependencies : dipendenze snapshot di terze parti
  • risorse : risorse statiche
  • applicazione : codice dell'applicazione e risorse

L'obiettivo è posizionare il codice dell'applicazione e le librerie di terze parti in livelli che riflettono la frequenza con cui cambiano .

Ad esempio, il codice dell'applicazione è probabilmente ciò che cambia più frequentemente, quindi ottiene il proprio livello. Inoltre, ogni livello può evolversi da solo e solo quando un livello è cambiato verrà ricostruito per l'immagine Docker.

Ora che comprendiamo la nuova struttura jar a strati, diamo un'occhiata a come possiamo utilizzarla per creare immagini Docker.

4.1. Creazione di vasi a strati

Per prima cosa, dobbiamo impostare il nostro progetto per creare un vaso a strati. Con Maven, questo significa aggiungere una nuova configurazione alla sezione del plugin Spring Boot del nostro POM:

 org.springframework.boot spring-boot-maven-plugin   true   

Con questa configurazione, il comando del pacchetto Maven (insieme a tutti i suoi comandi dipendenti) genererà un nuovo jar a strati usando i quattro livelli predefiniti menzionati in precedenza.

4.2. Viewing and Extracting Layers

Next, we need to extract the layers from the jar so that the Docker image will have the proper layers.

To examine the layers of any layered jar, we can run the command:

java -Djarmode=layertools -jar demo-0.0.1.jar list

Then to extract them, we would run:

java -Djarmode=layertools -jar demo-0.0.1.jar extract

4.3. Creating the Docker Image

The easiest way to incorporate these layers into a Docker image is by using a Dockerfile:

FROM adoptopenjdk:11-jre-hotspot as builder ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmode=layertools -jar application.jar extract FROM adoptopenjdk:11-jre-hotspot COPY --from=builder dependencies/ ./ COPY --from=builder snapshot-dependencies/ ./ COPY --from=builder spring-boot-loader/ ./ COPY --from=builder application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

This Dockerfile extracts the layers from our fat jar, then copies each layer into the Docker image. Each COPY directive results in a new layer in the final Docker image.

If we build this Dockerfile, we can see each layer from the layered jar get added to the Docker image as its own layer:

... Step 6/10 : COPY --from=builder dependencies/ ./ ---> 2c631b8f9993 Step 7/10 : COPY --from=builder snapshot-dependencies/ ./ ---> 26e8ceb86b7d Step 8/10 : COPY --from=builder spring-boot-loader/ ./ ---> 6dd9eaddad7f Step 9/10 : COPY --from=builder application/ ./ ---> dc80cc00a655 ...

5. Conclusion

In questo tutorial, abbiamo visto vari modi per creare immagini Docker con Spring Boot. Utilizzando i buildpack, possiamo ottenere immagini Docker adatte senza configurazioni boilerplate o personalizzate. Oppure, con un po 'più di sforzo, possiamo utilizzare vasi a più livelli per ottenere un'immagine Docker più personalizzata.

Tutti gli esempi in questo tutorial possono essere trovati su GitHub.

Per ulteriori informazioni sull'utilizzo di Java e Docker, guarda il tutorial su jib.