Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon
Beholdere har blitt den foretrukne måten å pakke en applikasjon med alle dens programvare- og operativsystemavhengigheter og deretter levere dem til forskjellige miljøer.
Denne artikkelen dekker forskjellige måter å beholde en Spring Boot-applikasjon på:
lage et Docker-bilde ved hjelp av en Docker-fil,
lage et OCI-bilde fra kilden ved å bruke Cloud-Native Buildpack,
og kjøretidsbildeoptimalisering ved å separere deler av JAR i forskjellige lag ved å bruke flerlagsverktøy.
Kode eksempel
Denne artikkelen er ledsaget av et eksempel på arbeidskode på GitHub .
Beholderterminologi
Vi starter med beholderterminologien som brukes i artikkelen:
Beholderbilde: fil av et spesifikt format. Vi vil konvertere applikasjonen vår til et containerbilde ved å kjøre byggeverktøyet.
container: En kjørbar forekomst av beholderbildet.
Containermotor: Daemonprosessen som er ansvarlig for å kjøre beholderen.
Containervert: Vertsdatamaskinen som containermotoren kjører på.
Beholderregister: Den generelle plasseringen som brukes til å publisere og distribuere beholderbildet.
OCI standard: Open Container Initiative (OCI) er en lett, åpen styringsstruktur dannet innenfor Linux Foundation. OCI Image Specification definerer industristandarder for containerbilde- og kjøretidsformater for å sikre at alle containermotorer kan kjøre containerbilder laget av ethvert byggeverktøy.
For å beholde en applikasjon, pakker vi applikasjonen vår inn i et beholderbilde og publiserer det bildet til et delt register. Container runtime henter dette bildet fra registeret, pakker det ut og kjører programmet inne i det.
Versjon 2.3 av Spring Boot gir plugins for å lage OCI-bilder.
Docker er den mest brukte containerimplementeringen, og vi bruker Docker i eksemplene våre, så alle påfølgende containerreferanser i denne artikkelen vil referere til Docker.
Bygg et beholderbilde på tradisjonell måte
Å lage Docker-bilder for Spring Boot-applikasjoner er veldig enkelt ved å legge til noen få instruksjoner til Docker-filen.
Vi oppretter først en kjørbar JAR-fil, og som en del av Docker-filinstruksjonene kopierer vi den kjørbare JAR-filen på toppen av JRE-grunnbildet etter å ha brukt de nødvendige innstillingene.
La oss lage vårapplikasjonen vår på Våren Initializr med avhengigheter web, lombokи actuator. Vi legger også til en hvilekontroller for å gi et API med GETmetode.
Opprette en dockerfil
Vi containeriserer deretter denne applikasjonen ved å legge til Dockerfile:
Vår Docker-fil inneholder et basisbilde fra adoptopenjdk, på toppen av det kopierer vi JAR-filen vår og åpner porten, 8080som vil lytte etter forespørsler.
Bygger applikasjonen
Først må du lage en applikasjon ved å bruke Maven eller Gradle. Her bruker vi Maven:
mvn clean package
Dette oppretter en kjørbar JAR-fil for applikasjonen. Vi må konvertere denne kjørbare JAR til et Docker-bilde for å kjøre på Docker-motoren.
Opprette et beholderbilde
Vi legger deretter denne kjørbare JAR-filen inn i Docker-bildet ved å kjøre kommandoen docker buildfra prosjektets rotkatalog som inneholder Dockerfilen opprettet tidligere:
docker build -t usersignup:v1 .
Vi kan se bildet vårt i listen ved å bruke kommandoen:
docker images
Utdataene fra kommandoen ovenfor inkluderer bildet vårt usersignupsammen med basisbildet, adoptopenjdkspesifisert i vår Docker-fil.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MB
Se lag inne i et beholderbilde
La oss se på bunken med lag inne i bildet. Vi vil bruke инструмент stupe, for å se disse lagene:
dive usersignup:v1
Her er en del av utdataene fra Dive-kommandoen:
Som vi kan se, utgjør applikasjonslaget en betydelig del av bildestørrelsen. Vi ønsker å redusere størrelsen på dette laget i de følgende delene som en del av vår optimalisering.
Opprette et beholderbilde ved hjelp av Buildpack
Monteringspakker (Byggepakker) er et generelt begrep som brukes av ulike Platform as a Service-tilbud (PAAS) for å lage et beholderbilde fra kildekoden. Den ble lansert av Heroku i 2011 og har siden blitt adoptert av Cloud Foundry, Google App Engine, Gitlab, Knative og flere andre.
Fordelen med skybyggepakker
En av hovedfordelene med å bruke Buildpack til å lage bilder er det Bildekonfigurasjonsendringer kan administreres sentralt (bygger) og forplantes til alle applikasjoner som bruker builder.
Byggepakkene var tett koblet til plattformen. Cloud-Native Buildpacks gir standardisering på tvers av plattformer ved å støtte OCI-bildeformatet, som sikrer at bildet kan kjøres av Docker-motoren.
Bruker Spring Boot-plugin
Spring Boot-pluginen bygger OCI-bilder fra kilden ved å bruke Buildpack. Bilder lages ved hjelp av bootBuildImageoppgaver (Gradle) eller spring-boot:build-imagemål (Maven) og lokal Docker-installasjon.
Vi kan tilpasse navnet på bildet som trengs for å sende til Docker-registeret ved å spesifisere navnet i image tag:
Fra utgangen ser vi det paketo Cloud-Native buildpackbrukes til å lage et fungerende OCI-bilde. Som før kan vi se bildet oppført som et Docker-bilde ved å kjøre kommandoen:
Deretter kjører vi Jib-pluginen ved å bruke Maven-kommandoen for å bygge applikasjonen og lage et containerbilde. Som før bruker vi ingen Docker-filer her:
Etter å ha utført Maven-kommandoen ovenfor, får vi følgende utgang:
[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
Utdataene viser at containerbildet er opprettet og plassert i registeret.
Motivasjoner og teknikker for å lage optimaliserte bilder
Vi har to hovedgrunner for optimalisering:
Производительность: I et containerorkestreringssystem hentes et containerbilde fra bilderegisteret til verten som kjører containermotoren. Denne prosessen kalles planlegging. Å trekke store bilder fra registeret resulterer i lange planleggingstider i containerorkestreringssystemer og lange byggetider i CI-rørledninger.
Безопасность: Større bilder har også et større område for sårbarheter.
Et Docker-bilde består av en stabel med lag, som hver representerer en instruksjon i vår Dockerfile. Hvert lag representerer et delta av endringene i det underliggende laget. Når vi henter et Docker-bilde fra registeret, trekkes det i lag og bufres på verten.
Spring Boot bruker "fat JAR" i som standard emballasjeformat. Når vi ser på den tykke JAR, ser vi at applikasjonen utgjør en veldig liten del av hele JAR. Dette er den delen som endres oftest. Resten består av Spring Framework-avhengighetene.
Optimaliseringsformelen sentrerer seg om å isolere applikasjonen på et eget nivå fra Spring Framework-avhengigheter.
Avhengighetslaget, som utgjør hoveddelen av den tykke JAR-filen, lastes bare ned én gang og bufres på vertssystemet.
Bare et tynt lag av applikasjonen trekkes under applikasjonsoppdateringer og beholderplanlegging. som vist i dette diagrammet:
I de følgende delene skal vi se på hvordan du lager disse optimaliserte bildene for en Spring Boot-applikasjon.
Opprette et optimalisert containerbilde for en Spring Boot-applikasjon ved å bruke Buildpack
Spring Boot 2.3 støtter lagdeling ved å trekke ut deler av en tykk JAR-fil i separate lag. Lagdelingsfunksjonen er deaktivert som standard og må være eksplisitt aktivert ved hjelp av Spring Boot Maven-plugin:
Vi vil bruke denne konfigurasjonen til å bygge containerbildet vårt først med Buildpack og deretter med Docker i de følgende delene.
La oss lansere build-imageMaven-mål for å lage beholderbilde:
mvn spring-boot:build-image
Hvis vi kjører Dive for å se lagene i det resulterende bildet, kan vi se at applikasjonslaget (skissert i rødt) er mye mindre i kilobyteområdet sammenlignet med det vi fikk med fett JAR-formatet:
Opprette et optimalisert containerbilde for en Spring Boot-applikasjon ved hjelp av Docker
I stedet for å bruke en Maven- eller Gradle-plugin, kan vi også lage et lagdelt Docker JAR-bilde med en Docker-fil.
Når vi bruker Docker, må vi utføre to ekstra trinn for å trekke ut lagene og kopiere dem til det endelige bildet.
Innholdet i den resulterende JAR etter bygging med Maven med lagdeling aktivert vil se slik ut:
Utgangen viser en ekstra JAR kalt spring-boot-jarmode-layertoolsи layersfle.idxfil. Denne ekstra JAR-filen gir lagdelte behandlingsmuligheter, som beskrevet i neste avsnitt.
Utdrag av avhengigheter på individuelle lag
For å se og trekke ut lag fra vår lagdelte JAR bruker vi systemegenskapen -Djarmode=layertoolstil start spring-boot-jarmode-layertoolsJAR i stedet for applikasjon:
Å kjøre denne kommandoen produserer utdata som inneholder de tilgjengelige kommandoalternativene:
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
Utgangen viser kommandoene list, extractи helpс helpvære standard. La oss kjøre kommandoen med listalternativ:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
Vi ser en liste over avhengigheter som kan legges til som lag.
Standardlag:
Lagnavn
Innhold
dependencies
enhver avhengighet hvis versjon ikke inneholder SNAPSHOT
spring-boot-loader
JAR-lasterklasser
snapshot-dependencies
enhver avhengighet hvis versjon inneholder SNAPSHOT
application
applikasjonsklasser og ressurser
Lag er definert i layers.idxfilen i den rekkefølgen de skal legges til i Docker-bildet. Disse lagene bufres i verten etter første henting fordi de ikke endres. Bare det oppdaterte applikasjonslaget lastes ned til verten, noe som er raskere på grunn av den reduserte størrelsen .
Bygge et bilde med avhengigheter trukket ut i separate lag
Vi vil bygge det endelige bildet i to trinn ved å bruke en metode som kalles flertrinns montering . I det første trinnet vil vi trekke ut avhengighetene og i det andre trinnet vil vi kopiere de utpakkede avhengighetene inn i det endelige bildet.
La oss endre Dockerfilen vår for en flertrinnsbygging:
# 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"]
Vi lagrer denne konfigurasjonen i en egen fil - Dockerfile2.
Vi bygger Docker-bildet ved å bruke kommandoen:
docker build -f Dockerfile2 -t usersignup:v1 .
Etter å ha kjørt denne kommandoen får vi følgende utgang:
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
Vi kan se at et Docker-bilde lages med en bilde-ID og deretter merkes.
Til slutt kjører vi Dive-kommandoen som før for å inspisere lagene inne i det genererte Docker-bildet. Vi kan gi en bilde-ID eller -tag som input til Dive-kommandoen:
dive userssignup:v1
Som du kan se i utdataene, er laget som inneholder applikasjonen nå bare 11 KB, og avhengigheter er bufret i separate lag.
Utdrag av interne avhengigheter på individuelle lag
Vi kan redusere størrelsen på applikasjonsnivået ytterligere ved å trekke ut hvilke som helst av våre tilpassede avhengigheter til et eget nivå i stedet for å pakke dem sammen med applikasjonen ved å deklarere dem i ymllignende fil navngitt layers.idx:
I denne filen layers.idxvi har lagt til en egendefinert avhengighet kalt, io.myorgsom inneholder organisasjonsavhengigheter hentet fra et delt depot.
Utgang
I denne artikkelen så vi på bruk av Cloud-Native Buildpacks for å bygge et beholderbilde direkte fra kildekoden. Dette er et alternativ til å bruke Docker til å lage et beholderbilde på vanlig måte: først lage en tykk kjørbar JAR-fil og deretter pakke den inn i et beholderbilde ved å spesifisere instruksjoner i Docker-filen.
Vi har også sett på å optimalisere containeren vår ved å aktivere en lagdelingsfunksjon som trekker avhengigheter inn i separate lag som er bufret på verten og et tynt lag av applikasjonen lastes inn ved planleggingstid i containerens utførelsesmotorer.
Du finner all kildekoden som er brukt i artikkelen på Github .
Kommandoreferanse
Her er en rask oversikt over kommandoene vi brukte i denne artikkelen.
Kontekstsletting:
docker system prune -a
Opprette et beholderbilde ved hjelp av en Docker-fil:
docker build -f <Docker file name> -t <tag> .
Vi bygger containerbildet fra kildekoden (uten Dockerfile):
mvn spring-boot:build-image
Se avhengighetslag. Før du bygger applikasjonens JAR-fil, sørg for at lagdelingsfunksjonen er aktivert i spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar list
Trekker ut avhengighetslag. Før du bygger applikasjonens JAR-fil, sørg for at lagdelingsfunksjonen er aktivert i spring-boot-maven-plugin: