javaxdocker
< < Articles

Réduire la taille de ses images Docker avec jlink

04/10/2019

Pour de multiples raisons (sécurité, coûts, performances,...) on souhaite réduire au maximum la taille de nos images Docker.

Dans cette article nous parlerons uniquement d’image Docker destinée à des applications serveurs fonctionnant sur une JVM 11 (Java, Scala, Kotlin,...).

Dans un premier temps nous verrons la pratique courante puis ce qu’il est possible de faire avec jlink pour l’améliorer.

Ce que l’on fait déjà depuis longtemps

Il n’est pas nécessaire d’avoir un JDK complet dans la majorité des cas. Une image de base contenant uniquement un JRE est largement suffisante et bien plus petite. Par exemple pour une JVM 11 : openjdk:11-jre-slim : 204 Mo openjdk:11-jdk-slim : 401 Mo

On passe du simple au double ! Ici pas de débat on choisit l’image avec un JRE.

Utiliser un JRE nous permet de ne pas avoir tous les outils de développement du JDK que nous n'utilisons de toute façon jamais dans nos images de production. Mais on peut aller plus loin.

Faire mieux

Rappel : depuis Java 9, le JDK est modulaire (JPMS). L’un des avantages de cette modularité est de pouvoir construire son propre exécutable java contenant uniquement les modules qu’on utilise vraiment. Ceci se fait via l’outil jlink.

La construction de notre image allégée va se faire en 2 temps (merci Docker multi-stage).

Première étape : créer un exécutable java réduit

FROM adoptopenjdk/openjdk11:jdk-11.0.4_11 as builder
RUN jlink \
    --add-modules java.security.jgss,java.rmi,java.sql,java.desktop,jdk.crypto.ec,jdk.unsupported \
    --verbose \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --output /opt/jre-minimal

On part d’une image debian contenant un JDK 11. La difficulté est de déterminer de quels modules on a besoin pour notre application. Dans cet exemple il s’agit d’une application web Play 2.6 en Scala 2.12.

Je n’ai pas trouvé de solution miracle. L’outil jdeps pourrait potentiellement aider mais l’outil est, selon moi, assez cryptique et pas franchement user friendly. Plus d’infos ici si vous voulez vous y essayer.

Dans cet exemple, on doit ajouter jdk.unsupported car akka utilise la classe Unsafe. Idem pour java.sql, akka utilise sql.Date… alors que cette application ne fait pas du tout de sql. Mais pourquoi donc java.desktop me diriez vous ? Et bien tout simplement parce que le package java.beans est dedans ! Celui-ci est par exemple utilisé par Google Guice. Pour plus de détails : https://stackoverflow.com/a/49802687.

Deuxième étape : créer l’image de run

FROM debian:stable-slim
MAINTAINER ekino
COPY --from=builder /opt/jre-minimal /opt/jre-minimal
ENV PATH=${PATH}:/opt/jre-minimal/bin
ADD build/distributions/ekino-tools-version.tar /play/
EXPOSE 8080
ARG JAVA_OPTS
ENV JAVA_OPTS=${JAVA_OPTS}
CMD java ${JAVA_OPTS} \
        -Dhttp.port=8080 \
        -classpath /play/ekino-tools-version/lib/ekino-tools-version.jar play.core.server.ProdServerStart

On part cette fois d’une image debian-slim qui fait 69 Mo. Il y a sans doute moyen de faire plus petit mais c’est déjà une bonne base. On copie ensuite notre exécutable java construit dans le stage précédent. Enfin, on ajoute les instructions habituelles.

Bilan

Le gain est très intéressant : on passe d’une image finale de 263 Mo (avec une base JRE) à une image de 180 Mo ! Ceci sans rien changer à notre CI ou configuration de run.

Le repo du projet d’exemple : https://github.com/ekino/ekino-tools-version