Erstellen von Docker-Images mit Spring Boot

1. Einleitung

Da immer mehr Unternehmen auf Container und virtuelle Server umsteigen, wird Docker zu einem wichtigeren Bestandteil der Softwareentwicklungs-Workflows. Zu diesem Zweck ist eine der großartigen neuen Funktionen in Spring Boot 2.3 die Möglichkeit, auf einfache Weise ein Docker-Image für Spring Boot-Anwendungen zu erstellen.

In diesem Tutorial erfahren Sie, wie Sie Docker-Images für eine Spring Boot-Anwendung erstellen.

2. Traditionelle Docker-Builds

Die traditionelle Methode zum Erstellen von Docker-Images mit Spring Boot ist die Verwendung einer Docker-Datei. Unten ist ein einfaches Beispiel:

FROM openjdk:8-jdk-alpine EXPOSE 8080 ARG JAR_FILE=target/demo-app-1.0.0.jar ADD ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]

Wir könnten dann den Docker-Build- Befehl verwenden, um ein Docker-Image zu erstellen. Dies funktioniert für die meisten Anwendungen gut, es gibt jedoch einige Nachteile.

Zuerst verwenden wir das von Spring Boot erstellte Fettglas. Dies kann sich auf die Startzeit auswirken, insbesondere in einer Containerumgebung . Wir können Startzeit sparen, indem wir stattdessen den explodierten Inhalt der JAR-Datei hinzufügen.

Zweitens werden Docker-Images in Ebenen erstellt. Aufgrund der Art der Spring Boot-Fat Jars werden der gesamte Anwendungscode und die Bibliotheken von Drittanbietern in einer einzigen Ebene zusammengefasst. Dies bedeutet, dass selbst wenn sich nur eine einzige Codezeile ändert, die gesamte Ebene neu erstellt werden muss .

Durch das Auflösen des JARs vor dem Erstellen erhalten Anwendungscode und Bibliotheken von Drittanbietern jeweils eine eigene Ebene. Dies ermöglicht es uns, den Caching-Mechanismus von Docker zu nutzen. Wenn nun eine Codezeile geändert wird, muss nur die entsprechende Ebene neu erstellt werden.

Schauen wir uns vor diesem Hintergrund an, wie Spring Boot den Prozess der Erstellung von Docker-Images verbessert hat.

3. Buildpacks

Buildpacks sind ein Tool, das Framework- und Anwendungsabhängigkeiten bereitstellt .

Zum Beispiel würde ein Buildpack bei einem Spring Boot Fat Jar die Java-Laufzeit für uns bereitstellen. Dadurch können wir die Docker-Datei überspringen und automatisch ein sinnvolles Docker-Image erhalten.

Spring Boot bietet sowohl Maven- als auch Gradle-Unterstützung für Buildpacks. Wenn wir beispielsweise mit Maven bauen, führen wir den folgenden Befehl aus:

./mvnw spring-boot:build-image

Schauen wir uns einige der relevanten Ausgaben an, um zu sehen, was passiert:

[INFO] Building jar: target/demo-0.0.1-SNAPSHOT.jar ... [INFO] Building image 'docker.io/library/demo:0.0.1-SNAPSHOT' ... [INFO] > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 100% ... [INFO] [creator] ===> DETECTING [INFO] [creator] 5 of 15 buildpacks participating [INFO] [creator] paketo-buildpacks/bellsoft-liberica 2.8.1 [INFO] [creator] paketo-buildpacks/executable-jar 1.2.8 [INFO] [creator] paketo-buildpacks/apache-tomcat 1.3.1 [INFO] [creator] paketo-buildpacks/dist-zip 1.3.6 [INFO] [creator] paketo-buildpacks/spring-boot 1.9.1 ... [INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT' [INFO] Total time: 44.796 s

Die erste Zeile zeigt, dass wir unser Standard-Fettglas gebaut haben, genau wie jedes typische Maven-Paket.

Die nächste Zeile beginnt mit der Erstellung des Docker-Images. Gleich danach sehen wir die Build-Pulls im Packeto-Builder.

Packeto ist eine Implementierung von Cloud-nativen Buildpacks. Es analysiert unser Projekt und ermittelt die erforderlichen Frameworks und Bibliotheken . In unserem Fall wird festgestellt, dass wir ein Spring Boot-Projekt haben, und die erforderlichen Buildpacks werden hinzugefügt.

Schließlich sehen wir das generierte Docker-Image und die gesamte Erstellungszeit. Beachten Sie, dass wir beim ersten Erstellen ziemlich viel Zeit damit verbringen, Buildpacks herunterzuladen und verschiedene Ebenen zu erstellen.

Eine der großartigen Funktionen von Buildpacks ist, dass das Docker-Image mehrere Ebenen umfasst. Wenn wir also nur unseren Anwendungscode ändern, sind nachfolgende Builds viel schneller:

... [INFO] [creator] Reusing layer 'paketo-buildpacks/executable-jar:class-path' [INFO] [creator] Reusing layer 'paketo-buildpacks/spring-boot:web-application-type' ... [INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT' ... [INFO] Total time: 10.591 s

4. Geschichtete Gläser

In einigen Fällen ziehen wir es möglicherweise vor, keine Buildpacks zu verwenden - möglicherweise ist unsere Infrastruktur bereits an ein anderes Tool gebunden, oder wir haben bereits benutzerdefinierte Docker-Dateien, die wir wiederverwenden möchten.

Aus diesen Gründen unterstützt Spring Boot auch das Erstellen von Docker-Images mithilfe von geschichteten Gläsern . Um zu verstehen, wie es funktioniert, schauen wir uns ein typisches Spring Boot-Fettglas-Layout an:

org/ springframework/ boot/ loader/ ... BOOT-INF/ classes/ ... lib/ ...

Das Fettglas besteht aus 3 Hauptbereichen:

  • Bootstrap-Klassen erforderlich, um die Spring-Anwendung zu starten
  • Anwendungscode
  • Bibliotheken von Drittanbietern

Bei geschichteten Gläsern sieht die Struktur ähnlich aus, aber wir erhalten eine neue Datei layer.idx , die jedes Verzeichnis im Fettglas einer Ebene zuordnet:

- "dependencies": - "BOOT-INF/lib/" - "spring-boot-loader": - "org/" - "snapshot-dependencies": - "application": - "BOOT-INF/classes/" - "BOOT-INF/classpath.idx" - "BOOT-INF/layers.idx" - "META-INF/"

Spring Boot ist sofort einsatzbereit und bietet vier Ebenen:

  • Abhängigkeiten : Typische Abhängigkeiten von Dritten
  • Snapshot-Abhängigkeiten : Snapshot-Abhängigkeiten von Drittanbietern
  • Ressourcen : statische Ressourcen
  • Anwendung : Anwendungscode und Ressourcen

Ziel ist es, Anwendungscode und Bibliotheken von Drittanbietern in Ebenen zu platzieren, die angeben, wie oft sie sich ändern .

Beispielsweise ändert sich der Anwendungscode wahrscheinlich am häufigsten, sodass er eine eigene Ebene erhält. Außerdem kann sich jede Ebene für sich entwickeln. Erst wenn sich eine Ebene geändert hat, wird sie für das Docker-Image neu erstellt.

Nachdem wir die neue Struktur der geschichteten Gläser verstanden haben, schauen wir uns an, wie wir sie verwenden können, um Docker-Bilder zu erstellen.

4.1. Erstellen von geschichteten Gläsern

Zuerst müssen wir unser Projekt einrichten, um ein Schichtglas zu erstellen. Bei Maven bedeutet dies, dass dem Spring Boot-Plugin-Bereich unseres POM eine neue Konfiguration hinzugefügt wird:

 org.springframework.boot spring-boot-maven-plugin   true   

Bei dieser Konfiguration generiert der Maven- Paketbefehl (zusammen mit einem seiner abhängigen Befehle) ein neues Layer-Jar unter Verwendung der vier zuvor genannten Standardebenen.

4.2. Viewing and Extracting Layers

Next, we need to extract the layers from the jar so that the Docker image will have the proper layers.

To examine the layers of any layered jar, we can run the command:

java -Djarmode=layertools -jar demo-0.0.1.jar list

Then to extract them, we would run:

java -Djarmode=layertools -jar demo-0.0.1.jar extract

4.3. Creating the Docker Image

The easiest way to incorporate these layers into a Docker image is by using a Dockerfile:

FROM adoptopenjdk:11-jre-hotspot as builder ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmode=layertools -jar application.jar extract FROM adoptopenjdk:11-jre-hotspot COPY --from=builder dependencies/ ./ COPY --from=builder snapshot-dependencies/ ./ COPY --from=builder spring-boot-loader/ ./ COPY --from=builder application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

This Dockerfile extracts the layers from our fat jar, then copies each layer into the Docker image. Each COPY directive results in a new layer in the final Docker image.

If we build this Dockerfile, we can see each layer from the layered jar get added to the Docker image as its own layer:

... Step 6/10 : COPY --from=builder dependencies/ ./ ---> 2c631b8f9993 Step 7/10 : COPY --from=builder snapshot-dependencies/ ./ ---> 26e8ceb86b7d Step 8/10 : COPY --from=builder spring-boot-loader/ ./ ---> 6dd9eaddad7f Step 9/10 : COPY --from=builder application/ ./ ---> dc80cc00a655 ...

5. Conclusion

In diesem Tutorial haben wir verschiedene Möglichkeiten gesehen, Docker-Images mit Spring Boot zu erstellen. Mit Buildpacks können wir geeignete Docker-Images ohne Boilerplate oder benutzerdefinierte Konfigurationen erhalten. Mit etwas mehr Aufwand können wir auch geschichtete Gläser verwenden, um ein maßgeschneidertes Docker-Bild zu erhalten.

Alle Beispiele in diesem Tutorial finden Sie auf GitHub.

Weitere Informationen zur Verwendung von Java und Docker finden Sie im Tutorial zu Jib.