Erstellung optimierter Docker-Images für Spring Boot-Anwendungen

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. auf GitHub .

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-StandardOpen Container Initiative (OCI) 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.

Docker 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 Spring Initializr mit Abhängigkeiten weblombokund 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 package

Dies 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      229MB

Betrachten der Schichten innerhalb des Containerimages

Lassen Sie uns den Stapel von Schichten im Image betrachten. Wir werden das Tool  dive, verwenden, um diese Schichten anzuzeigen:

dive usersignup:v1

Hier ist ein Teil der Ergebnisse der Ausführung des Dive-Befehls: 

Erstellung optimierter Docker-Images für Spring Boot-Anwendungen

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

Buildpacks (Buildpacks) – 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.

Erstellung optimierter Docker-Images für Spring Boot-Anwendungen

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}:v1

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

Das 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                  257MB

Erstellung 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.2

Anschließ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:v1

Nach 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% abgeschlossen

Die 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 "dicke JAR" als 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:

Erstellung optimierter Docker-Images für Spring Boot-Anwendungen

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
  
    
      true

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

Wenn 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 optimierter Docker-Images für Spring Boot-Anwendungen

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

In 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.jar

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

Die Ausgabe zeigt die Befehle Listeextractund helpmit helpals Standard. Lassen Sie uns den Befehl mit Listeder Option ausführen:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
Abhängigkeiten
spring-boot-loader
Snapshot-Abhängigkeiten
Anwendung

Wir 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 Multi-Stage-Build 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:v1

Wir 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:v1

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

Erstellung optimierter Docker-Images für Spring Boot-Anwendungen

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

Befehlsreferenz

Hier ist eine Zusammenfassung der Befehle, die wir in diesem Artikel verwendet haben, zur schnellen Einsicht.

Kontextbereinigung:

docker system prune -a

Container-Image mit einer Docker-Datei erstellen:

docker build -f  -t  .

Image des Containers aus Quellcode erstellen (ohne Dockerfile):

mvn spring-boot:build-image

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

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

Anzeigen der Liste von Container-Images

docker images

Anzeigen der Schichten im Container-Image (stellen Sie sicher, dass das Tauchwerkzeug installiert ist):

dive

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster