Bon après-midi
Récemment, j'ai eu la tâche de lancer des applications Spring Boot 2 dans un cluster Kubernetes à l'aide d'une image Docker. Ce problème n'est pas nouveau, assez rapidement j'ai trouvé des exemples dans Google et emballé mon application. J'ai été très surpris de ne pas trouver l'image alpine pour jdk11 et j'espérais que slim serait assez petit, mais quand j'ai envoyé l'image au registre docker, j'ai remarqué que sa taille était de près de 422 mégaoctets. Sous le chat est une description de la façon dont j'ai réduit l'image docker avec ma botte de printemps et java 11 à 144 mégaoctets.

App
Comme je l'ai mentionné précédemment, mon application est construite à l'aide de Spring Boot 2 et est un wrapper d'API REST sur une base de données relationnelle (à l'aide de @RepositoryRestResource). Mes dépendances incluent:
org.springframework.boot:spring-boot-starter-data-rest org.springframework.boot:spring-boot-starter-data-jpa org.flywaydb:flyway-core org.postgresql:postgresql
Le fichier jar collecté a une taille de 37,6 mégaoctets.
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"]
À la suite de l'assemblage, j'obtiens une image de taille: 422 Mo selon la sortie de la commande docker images. Fait intéressant, lorsque vous utilisez l'image 8-jdk slim obsolète, la taille est réduite à 306 Mo.
Tentative 1: une autre image de base
La première étape logique a été une tentative de trouver une image plus légère, de préférence basée sur l'alpin. J'ai scanné les référentiels Java les plus populaires:
(11 comme la version LTS actuelle et 8 car il y a encore un nombre suffisant d'applications qui ne pouvaient pas migrer vers des versions plus modernes)
Un tableau avec des images et des tags (~ 2700), leurs tailles au moment de la rédaction est disponible ici
En voici quelques uns:
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
Ainsi, si vous changez l'image de base en adoptopenjdk / openjdk11: alpine-jre, vous pouvez réduire l'image avec l'application à 177 Mo.
Tentative 2: exécution personnalisée
Depuis la sortie de jdk9 et de la modularisation, il est devenu possible de créer votre propre runtime qui ne contient que les modules nécessaires à votre application. Vous pouvez en savoir plus sur cette fonctionnalité ici .
Essayons de déterminer les modules nécessaires pour l'application de démarrage à ressort de test:
~/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, il semble que jdeps ne puisse pas gérer le fat-jar créé avec Spring Boot, mais nous pouvons décompresser l'archive et écrire le chemin de classe:
~/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
A cette occasion, un bug est actuellement ouvert: https://bugs.openjdk.java.net/browse/JDK-8207162
J'ai essayé de télécharger jdk12 pour obtenir ces informations, mais j'ai rencontré l'erreur suivante:
Exception in thread "main" com.sun.tools.classfile.Dependencies$ClassFileError ... Caused by: com.sun.tools.classfile.ConstantPool$InvalidEntry: unexpected tag at #1: 53
Par essai, erreur et recherche de module par ClassNotFoundException, j'ai déterminé que mon application avait besoin des modules suivants:
- java.base
- java.logging
- java.sql
- java.naming
- java.management
- java.instrument
- java.desktop
- java.security.jgss
La durée de vie pour eux peut être collectée en utilisant:
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
Essayons de construire une image docker de base à l'aide de ces modules:
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
et récupérez-le:
docker build . -t spring-boot-runtime:openjdk-11-slim
En conséquence, la taille était de 106 mégaoctets, ce qui est nettement plus petit que la plupart des images de base trouvées avec openjdk. Si vous l'utilisez pour mon application, la taille résultante sera de 144 mégaoctets.
De plus, nous pouvons utiliser spring-boot-runtime:openjdk-11-slim
comme image de base pour toutes les applications Spring Boot si elles ont des dépendances similaires. Dans le cas de diverses dépendances, il est possible d'utiliser un assemblage d'images à plusieurs étapes pour chacune des applications où le runtime java sera collecté à la première étape, et l'archive avec l'application sera ajoutée à la seconde.
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"]
Conclusion
Actuellement, la plupart des images docker pour java ont un volume suffisamment important, ce qui peut affecter négativement l'heure de démarrage de l'application, surtout si les couches nécessaires ne sont pas encore sur le serveur. En utilisant des balises avec jre ou en utilisant la modularisation java, vous pouvez créer votre propre runtime, ce qui réduira considérablement la taille de l'image de l'application.