Guten Tag.
Vor kurzem stand ich vor der Aufgabe, Spring Boot 2-Anwendungen in einem Kubernetes-Cluster mithilfe eines Docker-Images zu starten. Dieses Problem ist nicht neu, schnell genug habe ich Beispiele in Google gefunden und meine Anwendung gepackt. Ich war sehr überrascht, das alpine Bild für jdk11 nicht zu finden und hoffte, dass schlank klein genug sein würde, aber als ich das Bild an die Docker-Registrierung schickte, bemerkte ich, dass es fast 422 Megabyte groß war. Unter der Katze finden Sie eine Beschreibung, wie ich das Docker-Image mit meinem Spring Boot und Java 11 auf 144 Megabyte reduziert habe.

App
Wie bereits erwähnt, wird meine Anwendung mit Spring Boot 2 erstellt und ist ein REST-API-Wrapper über eine relationale Datenbank (mithilfe von @RepositoryRestResource). Meine Abhängigkeiten umfassen:
org.springframework.boot:spring-boot-starter-data-rest org.springframework.boot:spring-boot-starter-data-jpa org.flywaydb:flyway-core org.postgresql:postgresql
Die gesammelte JAR-Datei hat eine Größe von 37,6 Megabyte.
Dockerfile:
FROM openjdk:11-jdk-slim WORKDIR /home/demo ARG REVISION COPY target/spring-boot-app-${REVISION}.jar app.jar ENTRYPOINT ["java","-jar","app.jar"]
Als Ergebnis der Assembly erhalte ich ein Bild mit einer Größe von 422 MB gemäß der Ausgabe des Befehls docker images. Interessanterweise wird bei Verwendung des veralteten 8-JDK-Slim-Bilds die Größe auf 306 MB reduziert.
Versuch 1: Ein weiteres Grundbild
Der erste logische Schritt war der Versuch, ein leichteres Bild zu finden, das vorzugsweise auf Alpen basiert. Ich habe die beliebtesten Java-Repositorys gescannt:
(11 als aktuelle LTS-Version und 8 als noch genügend Anwendungen, die nicht auf modernere Versionen migriert werden konnten)
Eine Tabelle mit Bildern und Tags (~ 2700), deren Größe zum Zeitpunkt des Schreibens angegeben ist, finden Sie hier
Hier sind einige davon:
openjdk 8 488MB openjdk 8-slim 269MB openjdk 8-alpine 105MB openjdk 8-jdk-slim 269MB openjdk 8-jdk-alpine 105MB openjdk 8-jre 246MB openjdk 8-jre-slim 168MB openjdk 8-jre-alpine 84.9MB openjdk 11 604MB openjdk 11-slim 384MB openjdk 11-jdk 604MB openjdk 11-jdk-slim 384MB openjdk 11-jre 479MB openjdk 11-jre-slim 273MB adoptopenjdk/openjdk8 alpine 221MB adoptopenjdk/openjdk8 alpine-slim 89.7MB adoptopenjdk/openjdk8 jre 200MB adoptopenjdk/openjdk8 alpine-jre 121MB adoptopenjdk/openjdk11 alpine 337MB adoptopenjdk/openjdk11 alpine-slim 246MB adoptopenjdk/openjdk11 jre 218MB adoptopenjdk/openjdk11 alpine-jre 140MB
Wenn Sie also das Basis-Image in adoptopenjdk / openjdk11: alpine-jre ändern, können Sie das Image mit der Anwendung auf 177 MB reduzieren.
Versuch 2: Benutzerdefinierte Laufzeit
Seit der Veröffentlichung von jdk9 und der Modularisierung war es möglich, eine eigene Laufzeit zu erstellen, die nur die Module enthält, die für Ihre Anwendung erforderlich sind. Weitere Informationen zu dieser Funktionalität finden Sie hier .
Versuchen wir, die erforderlichen Module für die Testfeder-Boot-Anwendung zu ermitteln:
~/app ᐅ jdeps -s target/app-1.0.0.jar app-1.0.0.jar -> java.base app-1.0.0.jar -> java.logging app-1.0.0.jar -> not found
Ok, es scheint, dass jdeps nicht mit dem mit Spring Boot erstellten Fat-Jar umgehen kann, aber wir können das Archiv entpacken und den Klassenpfad schreiben:
~/app ᐅ jdeps -s -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original Error: byte-buddy-1.9.12.jar is a multi-release jar file but --multi-release option is not set ~/app ᐅ jdeps -s --multi-release 11 -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original Error: aspectjweaver-1.9.2.jar is not a multi-release jar file but --multi-release option is set
Bei dieser Gelegenheit ist derzeit ein Fehler offen: https://bugs.openjdk.java.net/browse/JDK-8207162
Ich habe versucht, jdk12 herunterzuladen, um diese Informationen zu erhalten, bin jedoch auf den folgenden Fehler gestoßen:
Exception in thread "main" com.sun.tools.classfile.Dependencies$ClassFileError ... Caused by: com.sun.tools.classfile.ConstantPool$InvalidEntry: unexpected tag at #1: 53
Durch Versuch, Irrtum und Modulsuche mit ClassNotFoundException habe ich festgestellt, dass meine Anwendung die folgenden Module benötigt:
- java.base
- java.logging
- java.sql
- java.naming
- java.management
- java.instrument
- java.desktop
- java.security.jgss
Rantime für sie kann gesammelt werden mit:
jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime
Versuchen wir, mit diesen Modulen ein einfaches Docker-Image zu erstellen:
FROM openjdk:11-jdk-slim RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime FROM debian:stretch-slim COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime RUN ln -s /usr/lib/jvm/spring-boot-runtime/bin/java /usr/bin/java
und sammle es:
docker build . -t spring-boot-runtime:openjdk-11-slim
Infolgedessen betrug die Größe 106 Megabyte, was erheblich kleiner ist als bei den meisten mit openjdk gefundenen Basisimages. Wenn Sie es für meine Anwendung verwenden, beträgt die resultierende Größe 144 Megabyte.
Außerdem können wir spring-boot-runtime:openjdk-11-slim
als Basis-Image für alle Spring-Boot-Anwendungen, wenn sie ähnliche Abhängigkeiten aufweisen. Bei verschiedenen Abhängigkeiten kann für jede Anwendung eine mehrstufige Image-Assembly verwendet werden, bei der in der ersten Phase die Java-Laufzeit erfasst und in der zweiten Phase das Archiv mit der Anwendung hinzugefügt wird.
FROM openjdk:11-jdk-slim RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,YOUR_MODULES --output /usr/lib/jvm/spring-boot-runtime FROM debian:stretch-slim COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime WORKDIR /home/demo ARG REVISION COPY target/app-${REVISION}.jar app.jar ENTRYPOINT ["/usr/lib/jvm/spring-boot-runtime/bin/java","-jar","app.jar"]
Fazit
Derzeit haben die meisten Docker-Images für Java ein ausreichend großes Volumen, was sich negativ auf die Startzeit der Anwendung auswirken kann, insbesondere wenn sich die erforderlichen Ebenen noch nicht auf dem Server befinden. Mithilfe von Tags mit jre oder mithilfe der Java-Modularisierung können Sie Ihre eigene Laufzeit erstellen, wodurch die Größe des Anwendungsabbilds erheblich reduziert wird.