ProHoster > blog > administratie > Geoptimaliseerde Docker-images bouwen voor een Spring Boot-applicatie
Geoptimaliseerde Docker-images bouwen voor een Spring Boot-applicatie
Containers zijn het voorkeursmiddel geworden om een applicatie met al zijn software- en besturingssysteemafhankelijkheden te verpakken en deze vervolgens aan verschillende omgevingen te leveren.
In dit artikel worden verschillende manieren besproken om een Spring Boot-applicatie in containers te plaatsen:
een docker-image bouwen met behulp van een dockerfile,
een OCI-image bouwen vanaf de bron met behulp van Cloud-Native Buildpack,
en runtime-beeldoptimalisatie door delen van de JAR in verschillende lagen te scheiden met behulp van tools met meerdere niveaus.
Codevoorbeeld
Dit artikel gaat vergezeld van een werkend codevoorbeeld op GitHub .
Containerterminologie
We beginnen met de containerterminologie die in het hele artikel wordt gebruikt:
Containerafbeelding: een bestand met een specifiek formaat. We converteren onze applicatie naar een containerimage door de build-tool uit te voeren.
Bak: een uitvoerbaar exemplaar van de containerimage.
Containermotor: Het daemonproces dat verantwoordelijk is voor het uitvoeren van de container.
Containerhost: De hostmachine waarop de containermotor draait.
Containerregister: De algemene locatie die wordt gebruikt om de containerimage te publiceren en te distribueren.
OCI-standaard: Open Container-initiatief (OCI) is een lichtgewicht, open-source managementframework gevormd door de Linux Foundation. De OCI Image Specification definieert industriestandaarden voor containerimageformaten en de runtime om ervoor te zorgen dat alle containerengines containerimages kunnen uitvoeren die door elke buildtool zijn gemaakt.
Om een applicatie in een container te plaatsen, verpakken we onze applicatie in een containerimage en publiceren we die image naar het openbare register. De containerruntime haalt deze afbeelding op uit het register, pakt deze uit en voert de toepassing erin uit.
Versie 2.3 van Spring Boot biedt plug-ins voor het bouwen van OCI-images.
havenarbeider is de meest gebruikte containerimplementatie en we gebruiken Docker in onze voorbeelden, dus alle volgende containerverwijzingen in dit artikel verwijzen naar Docker.
Op traditionele wijze een containerimage bouwen
Het maken van Docker-images voor Spring Boot-applicaties is heel eenvoudig door een paar instructies aan het Docker-bestand toe te voegen.
We maken eerst een uitvoerbare JAR en kopiëren, als onderdeel van de Dockerfile-instructies, de uitvoerbare JAR bovenop de basis-JRE-image nadat we de nodige aanpassingen hebben aangebracht.
Laten we onze Spring-applicatie maken Lente Initializr met afhankelijkheden web, lombokи actuator. We voegen ook een rustcontroller toe om een API van te voorzien GETmethode.
Een Dockerbestand maken
Vervolgens plaatsen we deze applicatie in een container door het toe te voegen Dockerfile:
Onze Dockerfile bevat een basisimage, van adoptopenjdk, waarop we ons JAR-bestand kopiëren en vervolgens de poort openen, 8080die naar verzoeken luistert.
Applicatie montage
Eerst moet u een applicatie maken met Maven of Gradle. Hier gebruiken we Maven:
mvn clean package
Hiermee wordt een uitvoerbaar JAR-bestand voor de toepassing gemaakt. We moeten deze uitvoerbare JAR converteren naar een Docker-image om op de Docker-engine te draaien.
Maak een containerimage
Vervolgens plaatsen we dit uitvoerbare JAR-bestand in de Docker-image door de opdracht uit te voeren docker buildvanuit de hoofdmap van het project met daarin het Dockerfile dat eerder is gemaakt:
docker build -t usersignup:v1 .
We kunnen onze afbeelding in de lijst zien met het commando:
docker images
De uitvoer van de bovenstaande opdracht bevat onze afbeelding usersignupsamen met de basisafbeelding, adoptopenjdkgespecificeerd in ons Dockerbestand.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MB
Bekijk lagen in een containerimage
Laten we eens kijken naar de stapel lagen in de afbeelding. We zullen gebruiken инструмент duiken, om deze lagen te bekijken:
dive usersignup:v1
Hier is een deel van de uitvoer van het duikcommando:
Zoals we kunnen zien, vormt de applicatielaag een aanzienlijk deel van de afbeeldingsgrootte. We willen de grootte van deze laag in de volgende secties verkleinen als onderdeel van onze optimalisatie.
Een containerimage bouwen met Buildpack
Montage pakketten (bouwpakketten) is een algemene term die door verschillende Platform as a Service (PAAS)-aanbiedingen wordt gebruikt om een containerimage van de broncode te maken. Het werd gelanceerd door Heroku in 2011 en is sindsdien overgenomen door Cloud Foundry, Google App Engine, Gitlab, Knative en een paar anderen.
Voordeel van Cloud Build-pakketten
Een van de belangrijkste voordelen van het gebruik van Buildpack om afbeeldingen te bouwen is dat Wijzigingen in de afbeeldingsconfiguratie kunnen centraal worden beheerd (builder) en met behulp van de builder naar alle applicaties worden doorgegeven.
De bouwpakketten waren nauw verbonden met het platform. Cloud-Native Buildpacks bieden standaardisatie op verschillende platforms door het OCI-imageformaat te ondersteunen, wat ervoor zorgt dat de image door de Docker-engine kan worden uitgevoerd.
De Spring Boot-plug-in gebruiken
De Spring Boot-plug-in bouwt OCI-afbeeldingen vanaf de broncode met behulp van Buildpack. Afbeeldingen worden gemaakt met behulp van bootBuildImagetaken (Gradle) of spring-boot:build-imagetarget (Maven) en lokale Docker-installatie.
We kunnen de naam aanpassen van de afbeelding die we naar het Docker-register moeten pushen door de naam op te geven image tag:
Laten we Maven gebruiken om uit te voeren build-imagedoelen voor het maken van een applicatie en het maken van een containerimage. We gebruiken momenteel geen Dockerfiles.
Uit de output zien we dat paketo Cloud-Native buildpackgebruikt om een werkende OCI-image te maken. Net als voorheen kunnen we de afbeelding zien als een Docker-afbeelding door de opdracht uit te voeren:
Jib is een plug-in voor het maken van afbeeldingen van Google die een alternatieve methode biedt voor het maken van een containerafbeelding vanaf de bron.
Vervolgens voeren we de Jib-plug-in uit met behulp van de Maven-opdracht om de applicatie te bouwen en de containerimage te maken. Net als voorheen gebruiken we hier geen Dockerfiles:
Na het uitvoeren van de bovenstaande Maven-opdracht krijgen we de volgende uitvoer:
[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
Uit de uitvoer blijkt dat de containerimage is gemaakt en in het register is geplaatst.
Motivaties en technieken voor het maken van geoptimaliseerde afbeeldingen
We hebben twee belangrijke redenen om te optimaliseren:
Производительность: In een containerorkestratiesysteem wordt een containerimage uit het imageregister gehaald naar de host waarop de containerengine draait. Dit proces heet planning. Het ophalen van grote afbeeldingen uit het register resulteert in lange planningstijden in containerorkestratiesystemen en lange bouwtijden in CI-pijplijnen.
veiligheid: grote afbeeldingen hebben ook een groot gebied voor kwetsbaarheden.
Een Docker-afbeelding bestaat uit een stapel lagen, die elk een statement in ons Dockerbestand vertegenwoordigen. Elke laag vertegenwoordigt de delta van veranderingen in de onderliggende laag. Wanneer we een Docker-image uit het register halen, wordt deze in lagen opgehaald en op de host in de cache opgeslagen.
Spring Boot-gebruik "dikke JAR" in als het standaardverpakkingsformaat. Als we naar een dikke JAR kijken, zien we dat de applicatie een heel klein onderdeel is van de hele JAR. Dit is het onderdeel dat het meest verandert. De rest bestaat uit Spring Framework-afhankelijkheden.
De optimalisatieformule is erop gericht de applicatie op een afzonderlijk niveau te isoleren van de Spring Framework-afhankelijkheden.
De afhankelijkheidslaag die het grootste deel van het dikke JAR-bestand vormt, wordt slechts één keer gedownload en in de cache op het hostsysteem opgeslagen.
Tijdens app-updates en containerplanning wordt slechts een dunne laag van de app getrokken, zoals weergegeven in dit diagram:
In de volgende secties bekijken we hoe u deze geoptimaliseerde afbeeldingen kunt maken voor een Spring Boot-toepassing.
Een geoptimaliseerde containerimage bouwen voor een Spring Boot-applicatie met Buildpack
Spring Boot 2.3 ondersteunt gelaagdheid door delen van een dik JAR-bestand in afzonderlijke lagen te extraheren. De gelaagdheidsfunctie is standaard uitgeschakeld en moet expliciet worden ingeschakeld met behulp van de Spring Boot Maven-plug-in:
We zullen deze configuratie gebruiken om onze containerimage eerst met Buildpack en vervolgens met Docker te bouwen in de volgende secties.
Laten we rennen build-imageMaven-doel om een containerimage te maken:
mvn spring-boot:build-image
Als we Dive uitvoeren om de lagen in de resulterende afbeelding te bekijken, kunnen we zien dat de applicatielaag (rood omcirkeld) veel kleiner is in kilobytes vergeleken met wat we kregen met het dikke JAR-formaat:
Een geoptimaliseerde containerimage bouwen voor een Spring Boot-applicatie met Docker
In plaats van een Maven- of Gradle-plug-in te gebruiken, kunnen we ook een gelaagde Docker JAR-afbeelding maken met een Docker-bestand.
Wanneer we Docker gebruiken, moeten we twee extra stappen ondernemen om de lagen te extraheren en naar de uiteindelijke afbeelding te kopiëren.
De inhoud van de resulterende JAR na het bouwen met Maven met ingeschakelde lagen zal er als volgt uitzien:
De uitvoer toont een extra JAR met de naam spring-boot-jarmode-layertoolsи layersfle.idxbestand. Dit extra JAR-bestand biedt gelaagde verwerkingsmogelijkheden, zoals beschreven in de volgende sectie.
Afhankelijkheden op afzonderlijke lagen extraheren
Om lagen uit onze gelaagde JAR te bekijken en te extraheren, gebruiken we de systeemeigenschap -Djarmode=layertoolsom te beginnen spring-boot-jarmode-layertoolsJAR in plaats van toepassing:
Het uitvoeren van deze opdracht levert een uitvoer op 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 command
De uitvoer toont de opdrachten list, extractи helpс helpde standaard zijn. Laten we de opdracht uitvoeren met listkeuze:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
We zien een lijst met afhankelijkheden die als lagen kunnen worden toegevoegd.
Standaard lagen:
Naam van laag
Inhoud
dependencies
elke afhankelijkheid waarvan de versie geen SNAPSHOT bevat
spring-boot-loader
JAR-laderklassen
snapshot-dependencies
elke afhankelijkheid waarvan de versie SNAPSHOT bevat
application
toepassingsklassen en bronnen
Lagen worden gedefinieerd in layers.idxbestand in de volgorde waarin ze aan de Docker-image moeten worden toegevoegd. Deze lagen worden na de eerste keer ophalen op de host in de cache opgeslagen, omdat ze niet veranderen. Alleen de bijgewerkte applicatielaag wordt gedownload naar de host, wat sneller is vanwege de kleinere omvang .
Een afbeelding bouwen met afhankelijkheden die in afzonderlijke lagen zijn geëxtraheerd
We zullen het uiteindelijke beeld in twee stappen bouwen met behulp van een methode genaamd meertrapsmontage . In de eerste stap extraheren we de afhankelijkheden en in de tweede stap kopiëren we de geëxtraheerde afhankelijkheden naar het uiteindelijke .
Laten we ons Dockerbestand aanpassen voor een build in meerdere fasen:
# 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 slaan deze configuratie op in een apart bestand - Dockerfile2.
We bouwen de Docker-image met behulp van de opdracht:
docker build -f Dockerfile2 -t usersignup:v1 .
Na het uitvoeren van deze opdracht krijgen we de volgende uitvoer:
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
We kunnen zien dat de Docker-afbeelding is gemaakt met een afbeeldings-ID en vervolgens is getagd.
Ten slotte voeren we de opdracht Dive uit zoals voorheen om de lagen in de gegenereerde Docker-afbeelding te bekijken. We kunnen een afbeeldings-ID of tag opgeven als invoer voor de duikopdracht:
dive userssignup:v1
Zoals u in de uitvoer kunt zien, is de laag die de applicatie bevat nu slechts 11 KB groot en worden afhankelijkheden in afzonderlijke lagen in de cache opgeslagen.
Extraheer interne afhankelijkheden op afzonderlijke lagen
We kunnen de grootte van de applicatielaag verder verkleinen door al onze aangepaste afhankelijkheden naar een aparte laag te extraheren in plaats van ze in de applicatie te verpakken door ze in te declareren ymlvergelijkbaar bestand met de naam layers.idx:
In dit bestand layers.idxwe hebben een aangepaste afhankelijkheid toegevoegd met de naam, io.myorgmet organisatie-afhankelijkheden die zijn opgehaald uit de gedeelde repository.
Uitgang
In dit artikel hebben we gekeken naar het gebruik van Cloud-Native Buildpacks om rechtstreeks vanuit de bron een containerimage te bouwen. Dit is een alternatief voor het gebruik van Docker om op de gebruikelijke manier een containerimage te maken: eerst wordt een dik uitvoerbaar JAR-bestand gemaakt en vervolgens verpakt in een containerimage door de instructies in het Dockerbestand op te geven.
We hebben ook gekeken naar het optimaliseren van onze container door een gelaagdheidsfunctie op te nemen die afhankelijkheden extraheert in afzonderlijke lagen die in de cache op de host worden opgeslagen en een dunne applicatielaag wordt geladen op het geplande tijdstip in de uitvoeringsengines van de container.
Je kunt alle broncode die in het artikel wordt gebruikt vinden op GitHub .
Commandoreferentie
Hier volgt een samenvatting van de opdrachten die we in dit artikel hebben gebruikt, zodat u deze snel kunt raadplegen.
Context opheldering:
docker system prune -a
Een containerimage bouwen met een Dockerfile:
docker build -f <Docker file name> -t <tag> .
Bouw een containerimage vanaf de bron (zonder Dockerfile):
mvn spring-boot:build-image
Bekijk afhankelijkheidslagen. Voordat u het JAR-bestand van de toepassing bouwt, moet u ervoor zorgen dat de gelaagdheidsfunctie is ingeschakeld in de spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar list
Afhankelijkheidslagen extraheren. Voordat u het jar-bestand van de toepassing bouwt, moet u ervoor zorgen dat de gelaagdheidsfunctie is ingeschakeld in de spring-boot-maven-plug-in: