Containers zijn de voorkeursmethode geworden voor het verpakken van een applicatie met al zijn software- en besturingssysteemafhankelijkheden, en voor het vervolgens leveren aan verschillende omgevingen.
In dit artikel worden verschillende manieren besproken om een Spring Boot-applicatie te containeriseren:
- het creëren van een Docker-image met behulp van een Docker-bestand,
- het creëren van een OCI-image uit de broncode met behulp van Cloud-Native Buildpack,
- en het optimaliseren van het image tijdens runtime door delen van de JAR te scheiden op verschillende niveaus met behulp van multistage tools.
Voorbeeldcode
Dit artikel wordt ondersteund door een voorbeeld van werkende code .
Container terminologie
We beginnen met de container terminologie die in dit artikel wordt gebruikt:
- Container image: een bestand van een specifiek formaat. We converteren onze applicatie naar een container image door een bouwtool uit te voeren.
- Container: een uitvoerende instantie van een container image.
- Container engine: een daemonproces dat verantwoordelijk is voor het uitvoeren van de container.
- Container host: de hostcomputer waarop de container engine draait.
- Container registry: een algemene locatie die wordt gebruikt voor het publiceren en distribueren van container images.
- OCI-standaard: is een lichtgewicht open governance-structuur die is opgericht binnen de Linux Foundation. De OCI-image-specificatie definieert industriestandaarden voor container image-formaten en runtimes, zodat alle container engines container images kunnen uitvoeren die door elke bouwtool zijn gemaakt.
Om een applicatie in een container te plaatsen, verpakken we onze applicatie in een container image en publiceren we deze image in een openbare registry. De container runtime haalt deze image uit de registry, pakt deze uit en voert de applicatie binnenin uit.
Versie 2.3 van Spring Boot biedt plugins voor het creëren van OCI-images.
de meest gebruikte containerimplementatie, en we gebruiken Docker in onze voorbeelden, dus alle verdere verwijzingen naar container in dit artikel zullen betrekking hebben op Docker.
Het bouwen van een container image op de traditionele manier
Het is heel eenvoudig om Docker-images voor Spring Boot-applicaties te maken door een paar instructies aan het Docker-bestand toe te voegen.
Eerst maken we een uitvoerbaar JAR-bestand en als onderdeel van de Docker-instructies kopiëren we het uitvoerbare JAR-bestand bovenop de basisafbeelding van JRE na het toepassen van de nodige configuraties.
Laten we onze Spring-applicatie maken op met afhankelijkheden web, lomboken actuator. We voegen ook een REST-controller toe om een API te bieden met GETde methode.
Het maken van een Docker-bestand
Vervolgens plaatsen we deze applicatie in een container, waarbij we toevoegen Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target\/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","\/application.jar"]Ons Docker-bestand bevat een basisafbeelding van adoptopenjdk, waarop we ons JAR-bestand kopieren en vervolgens de poort openen, 8080die verzoeken zal afluisteren.
De applicatie bouwen
Eerst moet de applicatie worden gemaakt met Maven of Gradle. Hier gebruiken we Maven:
mvn clean packageDit creëert een uitvoerbaar JAR-bestand van de applicatie. We moeten dit uitvoerbare JAR omzetten naar een Docker-afbeelding om te draaien in de Docker-engine.
Een containerafbeelding maken
Daarna plaatsen we dit uitvoerbare JAR-bestand in de Docker-afbeelding door de opdracht te uitvoeren docker builduit de hoofdmap van het project, dat eerder het Docker-bestand bevatte:
docker build -t usersignup:v1 .We kunnen onze afbeelding in de lijst zien met behulp van de opdracht:
docker images Het resultaat van het uitvoeren van de bovenstaande opdracht omvat onze afbeelding usersignupsamen met de basisafbeelding, adoptopenjdk, zoals opgegeven in onze Docker-bestand.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBDe lagen binnen de containerafbeelding bekijken
Laten we kijken naar de stapel lagen binnen de afbeelding. We zullen gebruiken om deze lagen te bekijken:
dive usersignup:v1Hier is een deel van de resultaten van het uitvoeren van de Dive-opdracht:

Zoals we zien, is het toepassingsniveau een aanzienlijk deel van de afbeeldingsgrootte. We willen de grootte van deze laag verminderen in de volgende secties in het kader van onze optimalisatie.
Een containerafbeelding maken met behulp van Buildpack
() is een algemene term die wordt gebruikt door verschillende "Platform as a Service" (PAAS) aanbiedingen om een containerafbeelding te maken uit de broncode. Het werd gelanceerd door Heroku in 2011 en is sindsdien aangenomen door Cloud Foundry, Google App Engine, Gitlab, Knative en sommige anderen.

Het voordeel van cloud-buildpacks
Een van de belangrijkste voordelen van het gebruik van Buildpack voor het maken van afbeeldingen is dat De wijzigingen in de image-configuratie kunnen centraal worden beheerd (builder) en verspreid worden naar alle applicaties die gebruikmaken van de builder.
Buildpacks waren sterk verbonden met het platform. Cloud-Native Buildpacks zorgen voor standaardisatie tussen platforms door het OCI-imageformaat te ondersteunen, dat garandeert dat het image kan worden uitgevoerd door de Docker-engine.
Het gebruik van de Spring Boot-plugin
De Spring Boot-plugin creëert OCI-images uit de source code met behulp van Buildpack. Images worden gemaakt met behulp van bootBuildImagetaken (Gradle) of spring-boot:build-imagedoelen (Maven) en een lokale Docker-installatie.
We kunnen de naam van de image instellen die naar de Docker registry moet worden gepusht door de naam op te geven in image tag:
org.springframework.boot
spring-boot-maven-plugin
docker.io/pratikdas/${project.artifactId}:v1Laten we Maven gebruiken om de build-imagedoel uit te voeren om de applicatie te bouwen en een containerimage te maken. We gebruiken momenteel geen Docker-bestanden.
mvn spring-boot:build-imageHet resultaat zal ongeveer als volgt zijn:
[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'Uit de uitvoer kunnen we zien dat paketo Cloud-Native buildpackwordt gebruikt om een werkend OCI-image te creëren. Zoals voorheen kunnen we het image dat als Docker-image is opgegeven, zien door de opdracht uit te voeren:
docker images Uitvoer:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBEen containerimage maken met Jib
Jib is een imagebouwplugin van Google die een alternatieve methode biedt om een containerimage uit de source code te maken.
We configureren jib-maven-pluginin pom.xml:
com.google.cloud.tools
jib-maven-plugin
2.5.2Vervolgens voeren we de Jib-plugin uit met de Maven-opdracht om de applicatie te bouwen en een containerimage te creëren. Zoals voorheen gebruiken we hier geen Docker-bestanden:
mvn compile jib:build -Dimage=/usersignup:v1Na het uitvoeren van de bovenstaande Maven-opdracht krijgen we de volgende uitvoer:
[INFO] Containerize de applicatie naar pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint ingesteld op [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Afbeelding gebouwd en gepusht als pratikdas/usersignup:v1
[INFO] Uitvoeren van taken:
[INFO] [==============================] 100.0% voltooidDe uitvoer toont aan dat de containerafbeelding is gemaakt en in de registry is geplaatst.
Motivaties en methoden voor het creëren van geoptimaliseerde afbeeldingen
We hebben twee belangrijke redenen voor optimalisatie:
- Prestaties: in het containersorceringssysteem wordt de containerafbeelding opgehaald uit de afbeeldingsregister op de host waar de containermechanisme wordt uitgevoerd. Dit proces wordt plannen genoemd. Het ophalen van grote afbeeldingsbestanden uit de registry leidt tot lange planningtijden in containersorceringssystemen en lange build-tijden in CI-pijplijnen.
- Beveiliging: grote afbeeldingen hebben ook een grotere oppervlakte voor kwetsbaarheden.
Een Docker-afbeelding bestaat uit een stapel lagen, waarvan elke laag een instructie in ons Dockerfile vertegenwoordigt. Elke laag is een delta van de wijzigingen in de onderliggende laag. Wanneer we een Docker-afbeelding uit de registry ophalen, worden deze in lagen opgehaald en op de host gecached.
Spring Boot gebruikt standaard verpakkingsformaat. Wanneer we naar de fat JAR kijken, zien we dat de applicatie slechts een klein deel van de hele JAR uitmaakt. Dit is het gedeelte dat het vaakst verandert. De rest bestaat uit afhankelijkheden van het Spring Framework.
De optimalisatieformule richt zich op het isoleren van de applicatie op een afzonderlijk niveau van de afhankelijkheden van het Spring Framework.
De laag met afhankelijkheden, die het grootste deel van het fat JAR-bestand vormt, wordt slechts één keer geladen en gecached in het host-systeem.
Alleen de dunne laag van de applicatie wordt opgehaald tijdens applicatie-updates en containerplanning, zoals weergegeven in dit diagram:

In de volgende secties zullen we bekijken hoe deze geoptimaliseerde afbeeldingen voor Spring Boot-applicaties te maken.
Het maken van een geoptimaliseerde containerafbeelding voor een Spring Boot-applicatie met behulp van Buildpack
Spring Boot 2.3 ondersteunt multilayering door delen van het fat JAR-bestand in afzonderlijke lagen te extraheren. De layering functie is standaard uitgeschakeld en moet expliciet worden ingeschakeld met de Spring Boot Maven-plugin:
org.springframework.boot
spring-boot-maven-plugin
trueWe zullen deze configuratie gebruiken om eerst ons containerbeeld te creëren met Buildpack, en daarna met Docker in de volgende secties.
Laten we de build-imageMaven-doelstelling voor het bouwen van een containerbeeld uitvoeren:
mvn spring-boot:build-imageAls we Dive uitvoeren om de lagen in het resulterende beeld te bekijken, zullen we zien dat de applicatielaag (omcirkeld in het rood) veel kleiner is in het kilobyte-bereik dan wat we hebben verkregen met het dikke JAR-formaat:

Een geoptimaliseerd containerbeeld voor een Spring Boot-applicatie creëren met Docker
In plaats van de Maven- of Gradle-plugin te gebruiken, kunnen we ook een gelaagd Docker JAR-beeld maken met een Docker-bestand.
Wanneer we Docker gebruiken, moeten we twee extra stappen uitvoeren om de lagen te extraheren en deze in het uiteindelijke beeld te kopiëren.
De inhoud van het resulterende JAR na het bouwen met Maven met de ingeschakelde gelaagdheid zal er als volgt uitzien:
META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idxIn de uitvoer wordt een extra JAR met de naam weergegeven spring-boot-jarmode-layertoolsen layersfle.idxbestand. Dit extra JAR-bestand biedt de mogelijkheid tot gelaagde verwerking, zoals in de volgende sectie wordt beschreven.
Afhankelijkheden extraheren op afzonderlijke lagen
Om de lagen van onze gelaagde JAR te bekijken en te extraheren, gebruiken we de systeemproperty -Djarmode=layertoolsom de spring-boot-jarmode-layertoolsJAR in plaats van de applicatie uit te voeren:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarHet uitvoeren van deze opdracht geeft output met de beschikbare opdrachtopties:
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 commandDe uitvoer toont de opdrachten lijst, extracten helpmet helpte zijn. Laten we de opdracht uitvoeren met lijstde optie:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
applicationWe zien een lijst met afhankelijkheden die als lagen kunnen worden toegevoegd.
Standaardlagen:
Laagnaam
Inhoud
dependencies
Elke afhankelijkheid waarvan de versie geen SNAPSHOT bevat
spring-boot-loader
JAR-laadclasses
snapshot-dependencies
Elke afhankelijkheid waarvan de versie een SNAPSHOT bevat
application
applicatieklassen en -bronnen
De lagen zijn gedefinieerd in layers.idxhet bestand in de volgorde waarin ze aan de Docker-image moeten worden toegevoegd. Deze lagen worden gecached op de host nadat ze voor het eerst zijn opgehaald, aangezien ze niet veranderen. Alleen de bijgewerkte laag van de applicatie wordt naar de host geüpload, wat sneller gebeurt door de verminderde grootte. .
Een image bouwen met afhankelijkheden, opgehaald in aparte lagen.
We zullen de uiteindelijke image in twee fasen bouwen, met een methode genaamd . In de eerste fase halen we de afhankelijkheden op, en in de tweede fase kopiëren we de opgehaalde afhankelijkheden naar de uiteindelijke image.
Laten we ons Docker-bestand aanpassen voor de multi-stage build:
# 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"]We bewaren deze configuratie in een apart bestand — Dockerfile2.
We bouwen de Docker-image met het commando:
docker build -f Dockerfile2 -t usersignup:v1 .Na het uitvoeren van dit commando krijgen we de volgende 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:v1We zien dat de Docker-image wordt gemaakt met een image-ID, en vervolgens wordt getagd.
Ten slotte voeren we het Dive-commando uit, zoals eerder, om de lagen binnen de gegenereerde Docker-image te bekijken. We kunnen ofwel de image-ID of de tag als invoer voor het Dive-commando opgeven:
dive userssignup:v1Zoals te zien is uit de output, neemt de laag die de applicatie bevat nu slechts 11 KB in beslag, en worden de afhankelijkheden gecached in aparte lagen.

Interne afhankelijkheden ophalen in aparte lagen.
We kunnen de grootte van de applicatielaag verder verminderen door enkele van onze aangepaste afhankelijkheden in een aparte laag op te halen, in plaats van ze samen met de applicatie in te pakken, door ze in te voeren in ymleen dergelijk bestand met de naam 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 dit bestand layers.idxhebben we een aanpasbare afhankelijkheid toegevoegd met de naam, io.myorgdie afhankelijkheden van de organisatie bevat, verkregen uit een openbare repository.
Uitslag
In dit artikel hebben we het gebruik van Cloud-Native Buildpacks besproken om een containerafbeelding direct vanuit de broncode te creëren. Dit is een alternatief voor het gebruik van Docker om een containerafbeelding op de gebruikelijke manier te maken: eerst wordt een zware uitvoerbare JAR-bestand gemaakt en vervolgens wordt deze verpakt in een containerafbeelding door instructies in het Docker-bestand op te geven.
We hebben ook de optimalisatie van onze container besproken door de laagfunctie op te nemen, die afhankelijkheden in aparte lagen extrahert, die op de host worden gecached, terwijl de dunne laag van de applicatie tijdens de planning wordt geladen in de uitvoeringsmechanismen van de container.
Je kunt de gehele broncode die in dit artikel is gebruikt vinden op .
Commando-gids
Hier is een samenvatting van de commando's die we in dit artikel hebben gebruikt voor een snel overzicht.
Context opschonen:
docker system prune -aEen containerafbeelding maken met een Docker-bestand:
docker build -f -t .Een containerafbeelding bouwen vanuit broncode (zonder Dockerfile):
mvn spring-boot:build-imageBekijk de lagen van afhankelijkheden. Zorg ervoor dat de laagfunctie is ingeschakeld in de spring-boot-maven-plugin voordat je het JAR-bestand van de applicatie bouwt:
java -Djarmode=layertools -jar application.jar listAfhankelijkheidslagen extraheren. Zorg ervoor dat de laagfunctie is ingeschakeld in de spring-boot-maven-plugin voordat je het JAR-bestand van de applicatie bouwt:
java -Djarmode=layertools -jar application.jar extractBekijk de lijst met containerafbeeldingen
docker imagesBekijk de lagen binnen de containerafbeelding (zorg ervoor dat de tool voor het duiken is geïnstalleerd):
diveBron: habr.com
