Container haben sich als bevorzugtes Mittel zur Verpackung von Anwendungen mit allen Software- und Betriebssystemabhängigkeiten etabliert und zur Bereitstellung in verschiedenen Umgebungen erwiesen.
In diesem Artikel werden verschiedene Methoden zur Containerisierung von Spring Boot-Anwendungen behandelt:
- Erstellung eines Docker-Images mit einer Docker-Datei,
- Erstellung eines OCI-Images aus dem Quellcode mit Cloud-Native Buildpack,
- und Optimierung des Images zur Laufzeit durch Aufteilung von JAR-Teilen auf verschiedene Ebenen mithilfe von mehrstufigen Werkzeugen.
Beispielcode
Dieser Artikel wird von einem Beispielcode begleitet. .
Containerterminologie
Wir beginnen mit der in diesem Artikel verwendeten Containerterminologie:
- Container-Image: eine Datei in einem bestimmten Format. Wir konvertieren unsere Anwendung in ein Container-Image, indem wir ein Build-Tool ausführen.
- Container: ein ausführbares Exemplar eines Container-Images.
- Container-Engine: ein Daemon-Prozess, der für das Starten von Containern verantwortlich ist.
- Container-Host: der Host-Computer, auf dem die Container-Engine läuft.
- Container-Registry: allgemeine Lage, die zur Veröffentlichung und Verbreitung von Container-Images verwendet wird.
- OCI-Standard: ist eine leichtgewichtige, offene Verwaltungsstruktur, die im Rahmen der Linux Foundation gebildet wurde. Die Spezifikation der OCI-Images definiert Branchenstandards für die Formate von Container-Images und Laufzeitumgebungen, um sicherzustellen, dass alle Container-Engines Container-Images, die mit jedem Build-Tool erstellt wurden, ausführen können.
Um eine Anwendung in einen Container zu bringen, verpacken wir unsere Anwendung in ein Container-Image und veröffentlichen dieses Image in einem gemeinsamen Repository. Die Container-Laufzeitumgebung holt sich dieses Image aus dem Repository, entpackt es und startet die Anwendung darin.
Version 2.3 von Spring Boot bietet Plugins zum Erstellen von OCI-Images.
ist die am häufigsten verwendete Implementierung von Containern, und wir verwenden Docker in unseren Beispielen, daher bezieht sich alles, was in diesem Artikel künftig auf Container erwähnt wird, auf Docker.
Container-Image auf traditionelle Weise erstellen
Es ist sehr einfach, Docker-Images für Spring Boot-Anwendungen zu erstellen, indem man einige Anweisungen in die Docker-Datei hinzufügt.
Zunächst erstellen wir eine ausführbare JAR-Datei und kopieren sie als Teil der Docker-Datei-Anweisungen über das Basis-Image von JRE, nachdem die erforderlichen Einstellungen vorgenommen wurden.
Lassen Sie uns unsere Spring-Anwendung auf mit Abhängigkeiten web, lombokund actuator. Wir fügen auch einen Rest-Controller hinzu, um eine API mit GETMethoden bereitzustellen.
Erstellen der Docker-Datei
Dann bringen wir diese Anwendung in einen Container, indem wir hinzufügen Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]Unsere Docker-Datei enthält das Basis-Image von adoptopenjdk, über das wir unsere JAR-Datei kopieren und dann den Port öffnen, 8080der eingehende Anfragen empfangen wird.
Anwendung bauen
Zunächst muss die Anwendung mit Maven oder Gradle erstellt werden. Hier verwenden wir Maven:
mvn clean packageDies erstellt eine ausführbare JAR-Datei der Anwendung. Wir müssen diese ausführbare JAR in ein Docker-Image umwandeln, um im Docker-Engine zu arbeiten.
Erstellen des Container-Images
Dann bringen wir diese ausführbare JAR in das Docker-Image, indem wir den Befehl ausführen docker buildaus dem Stammverzeichnis des Projekts, das die zuvor erstellte Docker-Datei enthält:
docker build -t usersignup:v1 .Wir können unser Image in der Liste mit dem Befehl sehen:
docker images Das Ergebnis der Ausführung des oben genannten Befehls umfasst unser Image usersignupneben dem Basisimage, adoptopenjdk, das in unserer Docker-Datei angegeben ist.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBBetrachten der Schichten innerhalb des Containerimages
Lassen Sie uns den Stapel von Schichten im Image betrachten. Wir werden das verwenden, um diese Schichten anzuzeigen:
dive usersignup:v1Hier ist ein Teil der Ergebnisse der Ausführung des Dive-Befehls:

Wie wir sehen, macht die Anwendungsebene einen erheblichen Teil der Bildgröße aus. Wir möchten die Größe dieser Schicht in den nächsten Abschnitten im Rahmen unserer Optimierung reduzieren.
Erstellen eines Containerimages mit Buildpack
() – ist ein allgemeiner Begriff, der von verschiedenen "Platform as a Service" (PaaS)-Angeboten verwendet wird, um ein Containerimage aus dem Quellcode zu erstellen. Es wurde 2011 von Heroku eingeführt und seitdem von Cloud Foundry, Google App Engine, Gitlab, Knative und einigen anderen übernommen.

Der Vorteil von Cloud-Buildpacks
Einer der Hauptvorteile der Verwendung von Buildpack zur Erstellung von Images ist, dass Konfigurationsänderungen zentral verwaltet (Builder) und auf alle Anwendungen verteilt werden können, die den Builder nutzen.
Buildpacks sind eng mit der Plattform verbunden. Cloud-Native Buildpacks gewährleisten die Standardisierung zwischen Plattformen, indem sie das OCI-Imageformat unterstützen, welches sicherstellt, dass das Image im Docker-Engine ausgeführt werden kann.
Die Nutzung des Spring Boot-Plugins
Das Spring Boot-Plugin erstellt OCI-Images aus dem Quellcode mithilfe von Buildpack. Die Images werden erstellt mit bootBuildImageAufgaben (Gradle) oder spring-boot:build-imageZielen (Maven) und einer lokalen Docker-Installation.
Wir können den Namen des Images, das in das Docker-Registry hochgeladen werden soll, anpassen, indem wir den Namen in image tag:
org.springframework.boot
spring-boot-maven-plugin
docker.io/pratikdas/${project.artifactId}:v1Lassen Sie uns Maven verwenden, um die build-imageZiele zur Erstellung der Anwendung und des Container-Images auszuführen. Momentan verwenden wir keine Docker-Dateien.
mvn spring-boot:build-imageDas Ergebnis wird ungefähr so aussehen:
[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'Aus den Ausgaben sehen wir, dass paketo Cloud-Native Buildpackverwendet wird, um ein funktionsfähiges OCI-Image zu erstellen. Wie zuvor können wir das Bild, das als Docker-Image angegeben ist, sehen, indem wir den folgenden Befehl ausführen:
docker images Fazit:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBErstellung eines Container-Images mit Jib
Jib ist ein von Google entwickeltes Plugin, das eine alternative Methode zur Erstellung eines Container-Images aus dem Quellcode bietet.
Wir konfigurieren jib-maven-pluginin pom.xml:
com.google.cloud.tools
jib-maven-plugin
2.5.2Anschließend starten wir das Jib-Plugin mit dem Maven-Befehl, um die Anwendung zu bauen und das Container-Image zu erstellen. Wie zuvor verwenden wir hier keine Docker-Dateien:
mvn compile jib:build -Dimage=/usersignup:v1Nach dem Ausführen des oben genannten Maven-Befehls erhalten wir die folgende Ausgabe:
[INFO] Anwendung in pratikdas/usersignup:v1 containerisieren...
.
.
[INFO] Container-Einstiegspunkt auf [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication] gesetzt
[INFO]
[INFO] Bild als pratikdas/usersignup:v1 erstellt und hochgeladen
[INFO] Aufgaben werden ausgeführt:
[INFO] [==============================] 100,0% abgeschlossenDie Ausgabe zeigt, dass das Containerbild erstellt und im Registry abgelegt wurde.
Motivation und Methoden zur Erstellung optimierter Bilder
Wir haben zwei Hauptgründe für die Optimierung:
- Leistung: Im Container-Orchestrierungssystem wird das Containerbild aus der Bild-Registry auf das Host-System extrahiert, auf dem der Container-Mechanismus läuft. Dieser Prozess wird als Scheduling bezeichnet. Das Abrufen großer Bilder aus der Registry führt zu langen Scheduling-Zeiten in Container-Orchestrierungssystemen und langen Build-Zeiten in CI-Pipelines.
- Sicherheit: Große Bilder haben zudem ein höheres Risiko für Sicherheitsanfälligkeiten.
Ein Docker-Bild besteht aus einem Stapel von Schichten, von denen jede eine Anweisung in unserem Dockerfile darstellt. Jede Schicht stellt eine Delta-Änderung der darunterliegenden Schicht dar. Wenn wir ein Docker-Image aus dem Registry abrufen, geschieht dies schichtweise und wird auf dem Host-Server zwischengespeichert.
Spring Boot verwendet Standard-Paketformat. Wenn wir das dicke JAR betrachten, sehen wir, dass die Anwendung nur einen sehr kleinen Teil des gesamten JARs ausmacht. Es ist der Teil, der sich am häufigsten ändert. Der Rest besteht aus Abhängigkeiten des Spring Framework.
Die Optimierungsformel konzentriert sich darauf, die Anwendung auf einer separaten Schicht von den Abhängigkeiten des Spring Framework zu isolieren.
Die Schicht der Abhängigkeiten, die den Hauptteil des dicken JARs bildet, wird nur einmal geladen und im Host-System zwischengespeichert.
Nur die dünne Schicht der Anwendung wird während der Anwendungsupdates und der Containerplanung geladen, wie in diesem Diagramm dargestellt:

In den folgenden Abschnitten werden wir erörtern, wie man diese optimierten Images für Spring Boot-Anwendungen erstellt.
Erstellung eines optimierten Container-Images für eine Spring Boot-Anwendung mit Buildpack
Spring Boot 2.3 unterstützt die Multi-Layer-Architektur, indem es Teile der umfangreichen JAR-Datei in separate Schichten extrahiert. Die Schichten-Funktion ist standardmäßig deaktiviert und muss ausdrücklich mit dem Spring Boot Maven Plugin aktiviert werden:
org.springframework.boot
spring-boot-maven-plugin
trueWir werden diese Konfiguration verwenden, um unser Container-Image zunächst mit Buildpack und dann mit Docker in den folgenden Abschnitten zu erstellen.
Lassen Sie uns build-imagedas Maven-Ziel zum Erstellen des Container-Images ausführen:
mvn spring-boot:build-imageWenn wir Dive starten, um die Schichten im resultierenden Bild zu sehen, werden wir feststellen, dass die Anwendungsschicht (rot umrandet) im Vergleich zu dem, was wir mit dem umfangreichen JAR-Format erhalten haben, wesentlich kleiner im Kilobyte-Bereich ist:

Erstellung eines optimierten Container-Images für Spring Boot-Anwendungen mit Docker
Anstatt das Maven- oder Gradle-Plugin zu verwenden, können wir auch ein mehrschichtiges Docker-JAR-Image mit einer Docker-Datei erstellen.
Wenn wir Docker verwenden, müssen wir zwei zusätzliche Schritte ausführen, um die Schichten zu extrahieren und sie im endgültigen Image zu kopieren.
Der Inhalt des erzeugten JAR nach dem Build mit Maven und aktivierter Schichtfunktion sieht folgendermaßen aus:
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 der Ausgabe wird ein zusätzlicher JAR mit dem Namen angezeigt, spring-boot-jarmode-layertoolsund layersfle.idxDatei. Diese zusätzliche JAR-Datei ermöglicht eine mehrschichtige Behandlung, wie im nächsten Abschnitt beschrieben.
Extraktion von Abhängigkeiten in separaten Schichten
Um die Schichten aus unserem mehrschichtigen JAR anzuzeigen und zu extrahieren, verwenden wir die System-Property -Djarmode=layertoolszum Ausführen spring-boot-jarmode-layertoolsdes JAR anstelle der Anwendung:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarDie Ausführung dieses Kommandos ergibt eine Ausgabe, die die verfügbaren Befehlsoptionen enthält:
Benutzung:
java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar
Verfügbare Befehle:
list Listet Schichten aus dem JAR auf, die extrahiert werden können
extract Extrahiert Schichten aus dem JAR zur Erstellung eines Images
help Hilfe zu jedem BefehlDie Ausgabe zeigt die Befehle Liste, extractund helpmit helpals Standard. Lassen Sie uns den Befehl mit Listeder Option ausführen:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listAbhängigkeiten
spring-boot-loader
Snapshot-Abhängigkeiten
AnwendungWir sehen eine Liste von Abhängigkeiten, die als Layer hinzugefügt werden können.
Standard-Layer:
Layer-Name
Inhalt
Abhängigkeiten
jede Abhängigkeit, deren Version kein SNAPSHOT enthält
spring-boot-loader
JAR-Loader-Klassen
snapshot-dependencies
jede Abhängigkeit, deren Version ein SNAPSHOT enthält
Anwendung
Anwendungs-Klassen und Ressourcen
Layer sind definiert in layers.idxDatei in der Reihenfolge, in der sie zum Docker-Image hinzugefügt werden sollen. Diese Layer werden nach dem ersten Abruf auf dem Host zwischengespeichert, da sie unverändert bleiben. Es wird nur das aktualisierte Anwendungs-Layer auf den Host geladen, was aufgrund der reduzierten Größe schneller erfolgt. .
Erstellung eines Images mit Abhängigkeiten, die in separaten Layern extrahiert werden.
Wir werden das finale Image in zwei Schritten erstellen, unter Verwendung einer Methode, die als bekannt ist. Im ersten Schritt extrahieren wir die Abhängigkeiten, und im zweiten Schritt kopieren wir die extrahierten Abhängigkeiten in das endgültige Image.
Lassen Sie uns unsere Docker-Datei für den Multi-Stage-Build modifizieren:
# 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"]Diese Konfiguration speichern wir in einer separaten Datei — Dockerfile2.
Docker-Image mit dem Befehl erstellen:
docker build -f Dockerfile2 -t usersignup:v1 .Nach Ausführung dieses Befehls erhalten wir folgende Ausgabe:
Sende den Build-Kontext an den Docker-Daemon 20,41 MB
Schritt 1/12 : FROM adoptopenjdk:14-jre-hotspot als Builder
14-jre-hotspot: Wird aus der Bibliothek/adoptopenjdk heruntergeladen
.
.
Erfolgreich erstellt a9ebf6970841
Erfolgreich getaggt userssignup:v1Wir sehen, dass das Docker-Image mit der ID erstellt und dann getaggt wird.
Schließlich führen wir den Dive-Befehl wie zuvor aus, um die Schichten im generierten Docker-Image zu überprüfen. Wir können die Bild-ID oder den Tag als Eingabe für den Dive-Befehl angeben:
dive userssignup:v1Wie aus den Ausgaben hervorgeht, nimmt die Schicht, die die Anwendung enthält, jetzt nur noch 11 KB in Anspruch, während die Abhängigkeiten in separaten Schichten zwischengespeichert werden.

Extrahieren von internen Abhängigkeiten in separaten Schichten
Wir können die Größe der Anwendungsschicht weiter reduzieren, indem wir alle benutzerdefinierten Abhängigkeiten in eine separate Schicht extrahieren, anstatt sie zusammen mit der Anwendung zu verpacken, indem wir sie in ymleiner Datei wie folgt deklarieren: 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 dieser Datei layers.idxhaben wir eine benutzerdefinierte Abhängigkeit mit dem Namen hinzugefügt, io.myorgmit Abhängigkeiten der Organisation, die aus einem gemeinsamen Repository stammen.
Fazit
In diesem Artikel haben wir die Verwendung von Cloud-Native Buildpacks zur Erstellung eines Container-Images direkt aus dem Quellcode untersucht. Dies ist eine Alternative zur Verwendung von Docker, um ein Container-Image auf die herkömmliche Weise zu erstellen: Zuerst wird eine ausführbare JAR-Datei erstellt und dann in ein Container-Image gepackt, indem Anweisungen in der Docker-Datei angegeben werden.
Wir haben auch die Optimierung unseres Containers betrachtet, indem wir die Schichtüberlagerungsfunktion aktiviert haben, die Abhängigkeiten in separate Ebenen extrahiert, die auf dem Host zwischengespeichert werden, während die dünne Anwendungsschicht bei der Planung in die Ausführungsmechanismen des Containers geladen wird.
Den gesamten Quellcode, der in diesem Artikel verwendet wurde, finden Sie unter .
Befehlsreferenz
Hier ist eine Zusammenfassung der Befehle, die wir in diesem Artikel verwendet haben, zur schnellen Einsicht.
Kontextbereinigung:
docker system prune -aContainer-Image mit einer Docker-Datei erstellen:
docker build -f -t .Image des Containers aus Quellcode erstellen (ohne Dockerfile):
mvn spring-boot:build-imageAnzeigen von Abhängigkeitsschichten. Stellen Sie sicher, dass die Schichtüberlagerungsfunktion im spring-boot-maven-plugin aktiviert ist, bevor Sie die JAR-Datei der Anwendung erstellen:
java -Djarmode=layertools -jar application.jar listExtrahieren von Abhängigkeitsschichten. Stellen Sie sicher, dass die Schichtüberlagerungsfunktion im spring-boot-maven-plugin aktiviert ist, bevor Sie die JAR-Datei der Anwendung erstellen:
java -Djarmode=layertools -jar application.jar extractAnzeigen der Liste von Container-Images
docker imagesAnzeigen der Schichten im Container-Image (stellen Sie sicher, dass das Tauchwerkzeug installiert ist):
diveQuelle: habr.com
