Containere er blevet det foretrukne middel til at pakke applikationen sammen med alle softwareafhængigheder og operativsystemet og derefter levere dem til forskellige miljøer.
Denne artikel diskuterer forskellige måder at containerisere en Spring Boot-applikation på:
- oprettelse af et Docker-image ved hjælp af en Docker-fil,
- oprettelse af et OCI-image fra kildekoden ved hjælp af Cloud-Native Buildpack,
- og optimering af billedet ved kørsel ved at opdele JAR-filer i forskellige lag ved hjælp af multi-stage værktøjer.
Code example
Denne artikel leveres med et eksempel på arbejdskode .
Containerterminologi
Vi starter med den containerterminologi, der anvendes i artiklen:
- Containerbillede (Container image): en fil af et bestemt format. Vi konverterer vores applikation til et containerbillede ved at køre et build-værktøj.
- Container: en udførelsesversion af containerbilledet.
- Containermotor (Container engine): en daemon-proces, der er ansvarlig for at køre containeren.
- Containervært (Container host): værtscomputer, hvor containermotoren kører.
- Containerregister (Container registry): et fælles sted, der bruges til at publicere og distribuere containerbilleder.
- OCI-standard: — en lettilgængelig åben administrationsstruktur skabt inden for Linux Foundation. OCI-specifikationen for billeder definerer branche-standarder for formater af containerbilleder og runtime-miljøer for at sikre, at alle containermotorer kan køre containerbilleder, der er oprettet af ethvert build-værktøj.
For at placere en applikation i en container indkapsler vi vores applikation i et containerbillede og offentliggør dette billede i et fælles register. Containerens runtime trækker dette billede fra registreret, udpakkes det og starter applikationen indeni.
Version 2.3 af Spring Boot tilbyder plugins til oprettelse af OCI-billeder.
— den mest anvendte implementering af containeren, og vi bruger Docker i vores eksempler, så alle efterfølgende henvisninger til container i denne artikel vil referere til Docker.
Oprettelse af containerbillede på traditionel måde
Det er meget nemt at oprette Docker-billeder til Spring Boot-applikationer ved at tilføje nogle få instruktioner til Docker-filen.
Først opretter vi en eksekverbar JAR-fil, og som en del af Docker-filens instruktioner kopierer vi den eksekverbare JAR-fil over grundlaget for JRE efter at have anvendt de nødvendige indstillinger.
Lad os oprette vores Spring-applikation på med afhængigheder web, lombokog actuator. Vi tilføjer også en REST-controller for at give API med GETmetode.
Oprettelse af Docker-fil
Derefter placerer vi denne applikation i en container ved at tilføje Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]Vores Docker-fil indeholder et basisbillede fra adoptopenjdk, over hvilket vi kopierer vores JAR-fil og derefter åbner porten, 8080der vil lytte efter anmodninger.
Bygning af applikationen
Først skal vi bygge applikationen ved hjælp af Maven eller Gradle. Her bruger vi Maven:
mvn clean packageDette opretter en eksekverbar JAR-fil til applikationen. Vi skal konvertere denne eksekverbare JAR til et Docker-billede for at køre i Docker-motoren.
Oprettelse af containerbillede
Derefter placerer vi denne eksekverbare JAR i et Docker-billede ved at køre kommandoen docker buildfra rodmappen af projektet, der indeholder Docker-filen, der blev oprettet tidligere:
docker build -t usersignup:v1 .Vi kan se vores billede i rækken med kommandoen:
docker images Resultatet af at køre ovenstående kommando inkluderer vores billede usersignupsammen med basisbilledet, adoptopenjdk, der er angivet i vores Docker-fil.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBVisning af lagene i containerbilledet
Lad os se på stakken af lag i billedet. Vi vil bruge for at se disse lag:
dive usersignup:v1Her er en del af resultaterne af at køre Dive-kommandoen:

Som vi kan se, udgør anvendelseslaget en betydelig del af billedets størrelse. Vi ønsker at reducere størrelsen af dette lag i de følgende sektioner som en del af vores optimering.
Oprettelse af containerbillede ved hjælp af Buildpack
() er et generelt begreb, der anvendes af forskellige "Platform as a Service" (PAAS) tilbud til at oprette et containerbillede fra kildekode. Det blev lanceret af Heroku i 2011 og er siden blevet accepteret af Cloud Foundry, Google App Engine, Gitlab, Knative og nogle andre.

Fordelen ved cloud-buildpacks
En af de primære fordele ved at bruge Buildpack til at oprette billeder er, at Ændringer af billedkonfigurationen kan administreres centralt (builder) og distribueres til alle applikationer, der bruger builder.
Build-pakkerne har været tæt knyttet til platformen. Cloud-Native Buildpacks sikrer ensartethed mellem platforme ved at understøtte OCI-billedformatet, som garanterer, at billedet kan køre på Docker-motoren.
Brug af Spring Boot-plugin
Spring Boot-pluginet opretter OCI-billeder fra kildekode ved hjælp af Buildpack. Billeder oprettes med bootBuildImageopgaver (Gradle) eller spring-boot:build-imagemål (Maven) og lokal Docker-installation.
Vi kan konfigurere navnet på det billede, der kræves for at sende til Docker-registeret, ved at angive navnet i image tag:
org.springframework.boot
spring-boot-maven-plugin
docker.io/pratikdas/${project.artifactId}:v1Lad os bruge Maven til at udføre build-imagemålet for at oprette applikationen og skabe containerbilledet. Lige nu bruger vi ikke nogen Docker-filer.
mvn spring-boot:build-imageResultatet vil se omtrent sådan ud:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Bygger billede 'docker.io/pratikdas/usersignup:v1'
[INFO]
[INFO] > Henter builder-billede 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator] Tilføjelse af label 'org.springframework.boot.version'
.. [creator] *** Billeder (c311fe74ec73):
.. [creator] docker.io/pratikdas/usersignup:v1
[INFO]
[INFO] Billede 'docker.io/pratikdas/usersignup:v1' bygget med succesFra output ser vi, at paketo Cloud-Native buildpackbruges til at skabe et fungerende OCI-billede. Som tidligere kan vi se billedet, der er angivet som Docker-billede, ved at køre kommandoen:
docker images Output:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBOprettelse af containerbilledet med Jib
Jib er et plugin til at skabe billeder fra Google, som tilbyder en alternativ metode til at oprette et containerbillede fra kildekoden.
Vi konfigurerer jib-maven-plugini pom.xml:
com.google.cloud.tools
jib-maven-plugin
2.5.2Derefter kører vi Jib-pluginet med Maven-kommandoen for at bygge applikationen og skabe containerbilledet. Som tidligere bruger vi ikke nogen Docker-filer her:
mvn compile jib:build -Dimage=/usersignup:v1Efter at have kørt ovenstående Maven-kommando får vi følgende 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% completeOutputtet viser, at containerbilledet er oprettet og gemt i registret.
Motivationer og metoder til at skabe optimerede billeder
Vi har to hovedårsager til optimering:
- Ydeevne: i containerorkestreringssystemet hentes containerbilledet fra billedregistret til den vært, hvor container-mekanismen kører. Denne proces kaldes planlægning. Hentning af store billeder fra registret resulterer i lang planlægningstid i containerorkestreringssystemer og lang byggetid i CI-pipelines.
- Sikkerhed: store billeder har også et større angrebsområde.
Et Docker-billede består af et lagstak, hvor hvert lag repræsenterer en instruktion i vores Dockerfile. Hvert lag repræsenterer en deltaændring fra det underliggende lag. Når vi henter et Docker-billede fra registret, hentes det lag-for-lag og caches på værten.
Spring Boot bruger standard pakkeformat. Når vi ser på den tykke JAR, kan vi se, at applikationen kun udgør en meget lille del af hele JAR’en. Det er den del, der ændrer sig oftest. Den resterende del består af afhængigheder fra Spring Framework.
Optimeringsformlen fokuserer på at isolere applikationen på et separat niveau fra afhængighederne i Spring Framework.
Afhængighedslaget, der danner hovedparten af den tykke JAR-fil, indlæses kun én gang og caches i værtsystemet.
Kun det tynde applikationslag trækkes under opdateringer af applikationen og containerplanlægning, som vist på dette diagram:

I de følgende sektioner vil vi se på, hvordan man skaber disse optimerede billeder til Spring Boot-applikationen.
Oprettelse af et optimeret containerbillede til en Spring Boot-applikation ved hjælp af Buildpack
Spring Boot 2.3 understøtter flerlagning ved at udtrække dele af den tykke JAR-fil til separate lag. Flerlagfunktionaliteten er som standard deaktiveret og skal aktiveres eksplicit ved hjælp af Spring Boot Maven-plugin:
org.springframework.boot
spring-boot-maven-plugin
trueVi vil bruge denne konfiguration til at oprette vores containerbillede først ved hjælp af Buildpack og derefter med Docker i de følgende afsnit.
Lad os køre build-imageMaven-målet for at oprette et containerbillede:
mvn spring-boot:build-imageHvis vi kører Dive for at se lagene i det resulterende billede, vil vi se, at applikationslaget (indrammet med rødt) er meget mindre, i kilobyteområdet, sammenlignet med hvad vi fik ved brug af det tykke JAR-format:

Oprettelse af et optimeret containerbillede til Spring Boot-applikationen ved hjælp af Docker
I stedet for at bruge Maven- eller Gradle-pluginet kan vi også oprette et lagdelt JAR Docker-billede med en Docker-fil.
Når vi bruger Docker, skal vi udføre to yderligere trin for at trække lagene ud og kopiere dem til det endelige billede.
Indholdet af det resulterende JAR efter opbygning med Maven med lagfunktion aktiveret vil se således ud:
META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idxUdgangen viser et ekstra JAR med navnet spring-boot-jarmode-layertoolsog layersfle.idxfil. Dette ekstra JAR-fil giver mulighed for lagdelt behandling, som beskrevet i det følgende afsnit.
Uddrag af afhængigheder i separate lag
For at se og uddrage lagene fra vores lagdelte JAR bruger vi systemegenskaben -Djarmode=layertoolsfor at køre spring-boot-jarmode-layertoolsJAR i stedet for applikationen:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarAt udføre denne kommando giver output, der indeholder tilgængelige kommandovalg:
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 commandUdgaven viser kommandoerne list, extractog helpmed helpvære som standard. Lad os køre kommandoen med listmuligheden:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
applicationVi ser en liste af afhængigheder, som kan tilføjes som lag.
Standardlagene:
Lagets navn
Indhold
dependencies
enhver afhængighed, hvis version ikke indeholder SNAPSHOT
spring-boot-loader
JAR-loaderklasser
snapshot-dependencies
enhver afhængighed, hvis version indeholder SNAPSHOT
application
applikationsklasser og ressourcer
Lag er defineret i layers.idxfilen i den rækkefølge, hvori de skal tilføjes til Docker-billedet. Disse lag caches på værten efter første hentning, da de ikke ændres. Kun den opdaterede applikationslag uploades til værten, hvilket sker hurtigere på grund af den reducerede størrelse. .
Bygning af et billede med afhængigheder, der er hentet i separate lag.
Vi vil bygge det endelige billede i to trin ved hjælp af en metode kaldet I det første trin vil vi hente afhængighederne, og i det andet trin vil vi kopiere de hentede afhængigheder til det endelige billede.
Lad os ændre vores Docker-fil til flere trin bygning:
# 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"]Gem denne konfiguration i en separat fil — Dockerfile2.
Byg Docker-billedet ved hjælp af kommandoen:
docker build -f Dockerfile2 -t usersignup:v1 .Efter at have udført denne kommando får vi følgende output:
Sender byggekontekst til Docker daemon 20.41MB
Trin 1/12 : FRA adoptopenjdk:14-jre-hotspot som builder
14-jre-hotspot: Henter fra library/adoptopenjdk
.
.
Succesfuldt bygget a9ebf6970841
Succesfuldt tagget userssignup:v1Vi ser, at Docker-billedet oprettes med et billed-id og derefter tagges.
Endelig kører vi Dive-kommandoen, som før, for at kontrollere lagene inden i det genererede Docker-billede. Vi kan angive billed-id'et eller tagget som input til Dive-kommandoen:
dive userssignup:v1Som det fremgår af outputtet, tager laget, der indeholder applikationen, nu kun 11 KB, og afhængighederne caches i separate lag.

Hentning af interne afhængigheder i separate lag.
Vi kan yderligere reducere størrelsen på applikationslaget ved at hente eventuelle af vores brugerdefinerede afhængigheder i et separat lag i stedet for at pakke dem sammen med applikationen ved at erklære dem i ymlen fil med navnet 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/"I denne fil layers.idxhar vi tilføjet en brugerdefineret afhængighed med navnet, io.myorgsom indeholder organisationsafhængigheder hentet fra et fælles lager.
Output
I denne artikel har vi set på brugen af Cloud-Native Buildpacks til at oprette en containerbilled direkte fra kildekoden. Dette er et alternativ til at bruge Docker til at oprette containerbillede på den sædvanlige måde: først oprettes en stor eksekverbar JAR-fil, og derefter pakkes den i et containerbillede ved at angive instruktioner i Docker-filen.
Vi har også set på optimering af vores container ved at inkludere lagringsfunktionen, der udtrækker afhængigheder i separate niveauer, som caches på værten, mens det tynde applikationslag indlæses under planlægningen i containerens kørselssystemer.
Du kan finde al kildekoden, der er brugt i artiklen på .
Kommandoguide
Her er et kort resumé af de kommandoer, vi brugte i denne artikel til en hurtig oversigt.
Rydning af konteksten:
docker system prune -aOprettelse af containerbillede ved hjælp af Docker-fil:
docker build -f -t .Bygger containerbillede fra kildekode (uden Dockerfile):
mvn spring-boot:build-imageVisning af lag af afhængigheder. Før du bygger JAR-filen for applikationen, skal du sikre dig, at lagringsfunktionen er aktiveret i spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar listUddragning af lag af afhængigheder. Før du bygger JAR-filen for applikationen, skal du sikre dig, at lagringsfunktionen er aktiveret i spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar extractVisning af listen over containerbilleder
docker imagesVisning af lag inden i containerbilledet (sørg for, at du har installeret dybdemoduleringsværktøjet):
diveKilde: habr.com
