Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

I cuntenituri sò diventati u mezzu preferitu per imballà una applicazione cù tutte e so dipendenze di u so software è u sistema operatore è poi trasmettenu à diversi ambienti.

Questu articulu copre diverse manere di containerizà una applicazione Spring Boot:

  • custruì una maghjina docker usendu un dockerfile,
  • custruì una maghjina OCI da a fonte utilizendu Cloud-Native Buildpack,
  • è l'ottimisazione di l'imaghjini in runtime sepandu e parti JAR in diversi livelli utilizendu strumenti stratificati.

 Esempiu di codice

Questu articulu hè accumpagnatu da un esempiu di codice di travagliu nantu à GitHub .

Terminologia di u containeru

Cumincià cù a terminologia di u containeru utilizata in tuttu l'articulu:

  • Immagine di u containeru: un schedariu di un furmatu specificu. Cunvertemu a nostra applicazione in una maghjina di cuntainer eseguendu u strumentu di creazione.
  • Cuntinutu: Un esempiu eseguibile di l'imagine di u containeru.
  • Mutore di containeru: U prucessu di demoniu rispunsevuli di eseguisce u cuntinuu.
  • Host di u containeru: A macchina ospite nantu à quale u mutore di cuntainer hè in esecuzione.
  • Registru di u containeru: U locu generale utilizatu per publicà è distribuisce l'imaghjini di u containeru.
  • Standard OCIOpen Container Initiative (OCI) hè un framework di gestione ligeru, open-source furmatu da a Fundazione Linux. L'OCI Image Specification definisce i standard di l'industria per i formati di l'imaghjini di u containeru è u runtime per assicurà chì tutti i mutori di containeru ponu eseguisce l'imaghjini di u containeru creati da qualsiasi strumentu di creazione.

Per cuntene una applicazione, mettimu a nostra applicazione in una maghjina di cuntainer è publichemu quella maghjina à u registru publicu. U runtime di u containeru recupera sta maghjina da u registru, scompone, è corre l'applicazione in questu.

A versione 2.3 di Spring Boot furnisce plugins per custruisce l'imaghjini OCI.

Docker hè l'implementazione di u containeru più cumunimenti utilizata, è avemu aduprà Docker in i nostri esempi, cusì tutti i riferimenti successivi di u containeru in questu articulu si riferiranu à Docker.

Custruì una maghjina di cuntainer in u modu tradiziunale

Custruì l'imaghjini Docker per l'applicazioni Spring Boot hè assai faciule aghjunghjendu uni pochi di struzzioni à u vostru Dockerfile.

Prima criemu un JAR eseguibile è, cum'è parte di l'istruzzioni di Dockerfile, copià l'executable JAR in cima à l'imaghjini JRE di basa dopu avè applicà e persunalizazione necessarie.

Creemu a nostra applicazione Spring on Spring Initializr cù dipendenze weblombokи actuator. Avemu ancu aghjunghje un controller di restu per furnisce una API GETmetudu.

Crià un Dockerfile

Pudemu sta applicazione in un cuntinuu aghjunghjendu Dockerfile:

FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]

U nostru Dockerfile cuntene una maghjina di basa, da adoptopenjdk, sopra à quale copiemu u nostru schedariu JAR è dopu apre u portu, 8080chì ascolterà e dumande.

Assemblea di l'applicazione

Prima avete bisognu di creà una applicazione cù Maven o Gradle. Quì avemu aduprà Maven:

mvn clean package

Questu crea un schedariu JAR eseguibile per l'applicazione. Avemu bisognu di cunvertisce stu JAR eseguibile in una maghjina Docker per eseguisce nantu à u mutore Docker.

Crea una maghjina di cuntainer

Dopu mettemu questu eseguibile JAR in l'imaghjini di Docker eseguendu u cumandamentu docker buildda u cartulare radicale di u prughjettu chì cuntene u Dockerfile creatu prima:

docker build  -t usersignup:v1 .

Pudemu vede a nostra maghjina in a lista cù u cumandimu:

docker images 

L'output di u cumandamentu sopra include a nostra maghjina usersignupcù l'imaghjini di basa, adoptopenjdkspecificatu in u nostru Dockerfile.

REPOSITORY          TAG                 SIZE
usersignup          v1                  249MB
adoptopenjdk        11-jre-hotspot      229MB

Vede i strati in una maghjina di cuntainer

Fighjemu a pila di strati in l'imaghjini. Avemu aduprà strumentu  immersione, per vede sti strati:

dive usersignup:v1

Eccu una parte di l'output di u cumandamentu Dive: 

Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

Comu pudemu vede, a strata di l'applicazione custituisce una parte significativa di a dimensione di l'imaghjini. Vulemu riduce a dimensione di sta strata in e seguenti sezioni cum'è parte di a nostra ottimisazione.

Custruì una maghjina di cuntainer cù Buildpack

Pacchetti di assemblea (Buildpacks) hè un termu genericu utilizatu da diverse offerte Platform as a Service (PAAS) per creà una maghjina di cuntainer da u codice fonte. Hè stata lanciata da Heroku in 2011 è hè stata aduttata da Cloud Foundry, Google App Engine, Gitlab, Knative è uni pochi altri.

Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

Vantaggio di i pacchetti Cloud Build

Unu di i vantaghji principali di utilizà Buildpack per custruisce l'imaghjini hè questu I cambiamenti di cunfigurazione di l'imaghjini ponu esse gestiti cintrali (builder) è propagati à tutte l'applicazioni chì utilizanu u builder.

I pacchetti di custruzzione sò stati strettamente ligati à a piattaforma. Cloud-Native Buildpacks furnisce standardizazione in tutte e piattaforme supportendu u furmatu di l'imaghjini OCI, chì assicura chì l'imaghjini pò esse gestitu da u mutore Docker.

Utilizendu u plugin Spring Boot

U plugin Spring Boot crea l'imaghjini OCI da a fonte cù Buildpack. L'imaghjini sò creati usendu bootBuildImagecompiti (Gradle) o spring-boot:build-imagedestinazione (Maven) è installazione Docker locale.

Pudemu persunalizà u nome di l'imaghjini chì avemu bisognu di spinghje à u registru Docker specificendu u 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>

Utilizemu Maven per eseguisce build-imagescopi per creà una applicazione è creà una maghjina di cuntainer. Attualmente ùn usemu alcun Dockerfiles.

mvn spring-boot:build-image

U risultatu serà qualcosa cum'è questu:

[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Building image 'docker.io/pratikdas/usersignup:v1'
[INFO] 
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator]     Adding label 'org.springframework.boot.version'
.. [creator]     *** Images (c311fe74ec73):
.. [creator]           docker.io/pratikdas/usersignup:v1
[INFO] 
[INFO] Successfully built image 'docker.io/pratikdas/usersignup:v1'

Da l'output, vedemu chì paketo Cloud-Native buildpackutilizatu per creà una maghjina OCI di travagliu. Cum'è prima, pudemu vede l'imaghjina listata cum'è una maghjina Docker eseguendu u cumandamentu:

docker images 

Cunsigliu:

REPOSITORY                             SIZE
paketobuildpacks/run                  84.3MB
gcr.io/paketo-buildpacks/builder      652MB
pratikdas/usersignup                  257MB

Crià una Image Container cù Jib

Jib hè un plugin d'autore di l'imaghjini da Google chì furnisce un metudu alternativu di creà una maghjina di cuntainer da a fonte.

Cunfigurazione jib-maven-pluginin pom.xml:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.5.2</version>
      </plugin>

In seguitu, eseguimu u plugin Jib utilizendu u cumandamentu Maven per custruisce l'applicazione è creà l'imaghjini di u containeru. Cum'è prima, ùn usemu micca alcun Dockerfiles quì:

mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1

Dopu avè eseguitu u cumandamentu Maven sopra, avemu a seguente output:

[INFO] Containerizing application to pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO] 
[INFO] Built and pushed image as pratikdas/usersignup:v1
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete

L'output mostra chì l'imaghjini di u containeru hè stata creata è posta in u registru.

Motivazioni è metudi per creà imagine ottimizzati

Avemu dui motivi principali per ottimisimu:

  • Produttività: In un sistema di orchestrazione di cuntainer, una maghjina di cuntainer hè tirata da u registru di l'imaghjini à l'ospitu chì esegue u mutore di cuntainer. Stu prucessu hè chjamatu pianificazione. Tirà grandi immagini da u registru si traduce in tempi di pianificazione longu in sistemi di orchestrazione di cuntainer è tempi longu di custruzzione in pipeline CI.
  • Seguretat: l'imaghjini grandi anu ancu una grande zona per i vulnerabili.

Una maghjina Docker hè custituita da una pila di strati, ognunu rapprisenta una dichjarazione in u nostru Dockerfile. Ogni strata rapprisenta u delta di i cambiamenti in a capa sottostante. Quandu tiramu una maghjina Docker da u registru, hè tiratu in strati è cache in l'ospite.

Spring Boot usa "FAT JAR" in cum'è u formatu di imballaggio predeterminatu. Quandu guardemu un JAR grassu, vedemu chì l'applicazione hè una parte assai chjuca di tuttu u JAR. Questa hè a parte chì cambia più. U restu hè custituitu da dipendenze di Spring Framework.

A formula di ottimisazione hè centrata annantu à isolà l'applicazione à un livellu separatu da e dipendenze di Spring Framework.

A capa di dependenza chì forma a maiò parte di u fugliale JAR grossu hè telecaricatu solu una volta è cache in u sistema host.

Solu una fina capa di l'app hè tirata durante l'aghjurnamenti di l'app è a pianificazione di u containeru, cum'è mostra in stu schema:

Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

In e seguenti sezzioni, guardemu cumu creà queste imagine ottimizzate per una applicazione Spring Boot.

Custruì una maghjina di u containeru ottimizzata per una applicazione Spring Boot cù Buildpack

Spring Boot 2.3 supporta a stratificazione estrattendu parti di un schedariu JAR grossu in strati separati. A funzione di stratificazione hè disattivata per difettu è deve esse attivata esplicitamente cù u plugin Spring Boot Maven:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration> 
</plugin>

Adupremu sta cunfigurazione per custruisce a nostra maghjina di cuntainer prima cù Buildpack è dopu cù Docker in e sezioni seguenti.

Corrimu build-imageMaven mira à creà una maghjina di cuntainer:

mvn spring-boot:build-image

Se eseguimu Dive per vede i strati in l'imaghjini resultanti, pudemu vede chì a strata di l'applicazione (circulata in rossu) hè assai più chjuca in a gamma di kilobyte cumparatu cù ciò chì avemu avutu cù u formatu JAR grossu:

Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

Custruì una maghjina di u containeru ottimizzata per una applicazione Spring Boot cù Docker

Invece di utilizà un plugin Maven o Gradle, pudemu ancu creà una maghjina Docker JAR in strati cù un schedariu Docker.

Quandu usemu Docker, avemu bisognu di piglià dui passi extra per estrae i strati è copià in l'imaghjini finali.

U cuntenutu di u JAR risultante dopu a custruzzione cù Maven cù a stratificazione attivata sarà cusì:

META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx

L'output mostra un JAR addiziale chjamatu spring-boot-jarmode-layertoolsи layersfle.idxschedariu. Stu schedariu JAR addiziale furnisce capacità di stratificazione, cum'è descrittu in a sezione dopu.

Estrae dipendenze nantu à strati separati

Per vede è estrae strati da i nostri strati JAR, usemu a pruprietà di u sistema -Djarmode=layertoolsper principià spring-boot-jarmode-layertoolsJAR invece di l'applicazione:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar

Eseguisce stu cumandimu pruduce un output chì cuntene l'opzioni di cumanda dispunibuli:

Usage:
  java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

L'output mostra i cumandamenti listextractи helpс helpesse u predefinitu. Eseguimu u cumandamentu cun listopzione:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application

Avemu vistu una lista di dependenzii chì ponu esse aghjuntu cum'è strati.

Strati per difettu:

U nome di a capa

Cuntenuti

dependencies

ogni dependenza chì a versione ùn cuntene SNAPSHOT

spring-boot-loader

Classi di caricatore JAR

snapshot-dependencies

ogni dependenza chì a versione cuntene SNAPSHOT

application

classi d'applicazione è risorse

I strati sò definiti in layers.idxfile in l'ordine in quale deve esse aghjuntu à l'imaghjini Docker. Questi strati sò in cache nantu à l'ospitu dopu a prima fetch perchè ùn cambianu micca. Solu a strata di l'applicazione aghjurnata hè scaricata à l'ospite, chì hè più veloce per via di a dimensione ridutta .

Custruì una maghjina cù dipendenze estratte in strati separati

Custruiremu l'imaghjini finali in dui passi cù un metudu chjamatu assemblea multi-stadi . In u primu passu, estraremu e dipendenze è in u sicondu passu copiemu e dipendenze estratte in u finale .

Mudificàmu u nostru Dockerfile per una custruzione in più fasi:

# 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"]

Salvemu sta cunfigurazione in un schedariu separatu - Dockerfile2.

Custruemu l'imaghjini Docker usendu u cumandimu:

docker build -f Dockerfile2 -t usersignup:v1 .

Dopu avè eseguitu stu cumandamentu, avemu a seguente output:

Sending build context to Docker daemon  20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Successfully built a9ebf6970841
Successfully tagged userssignup:v1

Pudemu vede chì l'imaghjini di Docker hè creatu cù un ID d'imaghjini è dopu tagged.

Infine, eseguimu u cumandamentu Dive cum'è prima per verificà i strati in l'imagine Docker generata. Pudemu furnisce un ID d'imagine o tag cum'è input à u cumandamentu Dive:

dive userssignup:v1

Comu pudete vede da l'output, a capa chì cuntene l'applicazione hè avà solu 11 KB è e dipendenze sò in cache in strati separati. 

Creazione d'Immagini Docker ottimizzate per una Applicazione Spring Boot

Estrae dipendenze interne nantu à strati separati

Pudemu ancu riduce a dimensione di a strata di l'applicazione estrattendu qualsiasi di e nostre dipendenze persunalizate in una strata separata invece di imballà cù l'applicazione dichjarà in ymlfile simili chjamatu 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 stu schedariu layers.idxavemu aghjustatu una dipendenza persunalizata chjamata, io.myorgchì cuntene dipendenze di l'urganisazione recuperate da u repositoriu spartutu.

cunchiusioni

In questu articulu, avemu vistu l'usu di Cloud-Native Buildpacks per custruisce una maghjina di cuntainer direttamente da a fonte. Questa hè una alternativa à l'usu di Docker per creà una maghjina di cuntainer in u modu di solitu: prima, un schedariu JAR eseguibile grossu hè creatu è dopu imballatu in una maghjina di cuntainer specificendu l'istruzzioni in u Dockerfile.

Avemu ancu guardatu à ottimisà u nostru cuntainer includendu una funzione di stratificazione chì estrae dipendenze in strati separati chì sò in cache nantu à l'ospite è una capa fina di l'applicazione hè caricata à u tempu di pianificazione in i mutori di esecuzione di u container.

Pudete truvà tuttu u codice fonte usatu in l 'articulu à Github .

Riferimentu di cumandamentu

Eccu un riassuntu di i cumandamenti chì avemu usatu in questu articulu per una riferenza rapida.

Clearing di u cuntestu:

docker system prune -a

Custruì una maghjina di cuntainer cù un Dockerfile:

docker build -f <Docker file name> -t <tag> .

Custruite l'imagine di u containeru da a fonte (senza Dockerfile):

mvn spring-boot:build-image

Vede i strati di dependenza. Prima di custruisce u schedariu JAR di l'applicazione, assicuratevi chì a funzione di stratificazione hè attivata in u spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Estratti strati di dependenza. Prima di custruisce u schedariu JAR di l'applicazione, assicuratevi chì a funzione di stratificazione hè attivata in u spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Vede una Lista di Immagini di Container

docker images

Vede à a manca à l'internu di l'imaghjini di u containeru (assicuratevi chì l'uttellu di immersione hè stallatu):

dive <image ID or image tag>

Source: www.habr.com