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 standardOpen 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 weblombokи 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:

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

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: 

Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon

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.

Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon

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:

<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>

La oss bruke Maven til å gjøre det build-imagemål for å lage en applikasjon og lage et beholderbilde. Vi bruker ingen Dockerfiler for øyeblikket.

mvn spring-boot:build-image

Resultatet blir noe slikt:

[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'

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:

docker images 

Konklusjon:

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

Opprette et beholderbilde med Jib

Jib er en plugin for bildeoppretting fra Google som gir en alternativ metode for å lage et beholderbilde fra kildekoden.

Konfigurerer jib-maven-plugini pom.xml:

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

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:

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

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:

Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon

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:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration> 
</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:

Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon

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:

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

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:

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

Å 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 listextractи helpс helpvære standard. La oss kjøre kommandoen med listalternativ:

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

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. 

Bygge optimaliserte Docker-bilder for en Spring Boot-applikasjon

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:

- "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 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:

 java -Djarmode=layertools -jar application.jar extract

Vis en liste over beholderbilder

docker images

Visning til venstre inne i beholderbildet (sørg for at dykkeverktøyet er installert):

dive <image ID or image tag>

Kilde: www.habr.com