Skip to main content

Utiliser JLink pour créer des images Docker plus légères pour votre application Java Spring Boot

Écrit par
feature kubernetes polp

24 août 2023

0 minutes de lecture

Les conteneurs apportent davantage de flexibilité et d’agilité au développement et au déploiement de logiciels. Toutefois, ils créent également une nouvelle surface d’attaque dont des acteurs malveillants peuvent tirer parti. Un conteneur compromis peut permettre à un attaquant d’accéder à d’autres conteneurs, voire au système hôte. Réduire la taille des images en limitant le nombre d’artefacts qu’elles contiennent contribue déjà grandement à réduire la surface d’attaque.

Dans cet article, nous vous proposons une analyse approfondie de l’utilisation de JLink pour optimiser la taille des images Docker et renforcer la sécurité et les performances des applications. Nous vous montrerons comment utiliser JLink et l’intégrer à Docker pour déployer efficacement vos applications Spring Boot ou Java en général.

Java est l’un des langages de programmation les plus utilisés au monde pour le développement d’applications d’entreprise. Pourtant, les développeurs rencontrent souvent des difficultés liées à la taille des images Docker lorsqu’ils déploient des applications Java dans des conteneurs Docker. JLink, un outil introduit dans JDK 9, permet notamment de résoudre ce problème.

JLink (Java Linker) est un outil en ligne de commande qui assemble et optimise un ensemble de modules et leurs dépendances pour créer une image d’exécution personnalisée. En pratique, il crée un environnement d’exécution Java minimal qui ne contient que les modules nécessaires à votre application.

$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules my.module --output myRunTime

Dans la commande ci-dessus, my.module correspond à votre module et myRuntime à l’image d’exécution personnalisée que JLink va créer.

Lors de la création d’images Docker pour des applications Java, leur taille est souvent préoccupante, en particulier pour les applications Spring Boot, qui s’accompagnent de nombreuses dépendances. Une image Docker volumineuse peut allonger les temps de démarrage, augmenter les coûts de stockage et ralentir les processus de déploiement.

Dans les anciennes versions de Java, le kit de développement Java (JDK) incluait un environnement d’exécution Java (JRE). Seul le JRE était nécessaire pour exécuter l’artefact Java créé. Il était donc courant d’utiliser le JRE dans une image Docker ou de choisir une image de base JRE pour ses conteneurs. Les versions récentes de Java n’incluent pas toujours de JRE, même si certains fournisseurs continuent d’en proposer un ainsi que des images de base correspondantes. Vous pouvez les utiliser ou opter pour un environnement d’exécution Java plus spécifique, adapté à votre application.

JLink vous permet de créer un environnement d’exécution Java minimal ne contenant que les modules nécessaires. Vous réduisez ainsi considérablement la taille de votre image Docker. À titre d’exemple, un environnement d’exécution Java standard peut dépasser 200 Mo, tandis qu’avec JLink, vous pouvez le ramener à moins de 50 Mo.

Spring Boot crée un fichier JAR volumineux contenant toutes les dépendances de vos applications. De plus, de nombreuses applications Spring Boot ne déclarent aucun module. Ce n’est pas nécessairement un problème, mais nous devons déterminer les modules dont l’application et l’ensemble de ses dépendances ont besoin.

Utiliser Jdeps pour trouver les modules


Jdeps est un outil Java qui affiche les dépendances au niveau des packages ou des classes. Introduit dans Java 8, il permet d’analyser les dépendances d’une application, puis de les utiliser pour créer une image d’exécution personnalisée avec JLink.

Lorsque toutes nos dépendances se trouvent dans un même répertoire, nous pouvons utiliser jdeps pour afficher un récapitulatif de ces dépendances.

jdeps -cp 'mydeps/lib/*' -recursive --multi-release 17 -s target/MyJar.jar

De même, nous pouvons utiliser jdeps pour afficher récursivement toutes les dépendances de modules de l’application Spring Boot et de ses dépendances.

jdeps --ignore-missing-deps -q  --recursive  --multi-release 17  --print-module-deps  --class-path 'mydeps/lib/*'  target/MyJar.jar

La sortie générée par jdeps permet à JLink de créer un environnement d’exécution Java contenant uniquement les modules nécessaires à cette application Spring Boot.

Exporter les dépendances Spring Boot dans un dossier

Comme indiqué, Spring Boot crée un fichier JAR volumineux qui inclut toutes les dépendances. Toutefois, celles-ci sont organisées d’une manière particulière dans le fichier JAR et ne sont donc pas faciles à analyser avec jdeps. Deux solutions simples permettent de rendre les dépendances accessibles à jdeps.

  • Décompresser le fichier JAR volumineux créé par Spring Boot.

    • Cette option est idéale si l’artefact de build a déjà été créé et que vous ne souhaitez pas ou ne pouvez pas reconstruire l’application. Les dépendances seront extraites dans /BOOT/libs/.

  • Utiliser un plug-in de votre outil de build pour copier les dépendances dans un dossier spécifique.

    • Avec Maven, vous pouvez par exemple utiliser le maven-dependency-plugin. Dans l’exemple ci-dessous, les dépendances sont copiées dans le dossier /target/dependency une fois la phase package de Maven terminée.

<project>
    <!-- ... other configurations ... -->

    <build>
        <plugins>
            <!-- Add the maven-dependency-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.2</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- Configure the output directory for the dependencies -->
                            <outputDirectory>${project.build.directory}/dependency</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <!-- ... other configurations ... -->
</project>

Créer une image Docker avec un environnement d’exécution Java personnalisé

Nous allons maintenant combiner jdeps et JLink pour créer un environnement d’exécution Java personnalisé. Nous pourrons ainsi créer une image Docker minimale, parfaitement adaptée à une application Spring Boot.

FROM maven:3-eclipse-temurin-17 as build
RUN mkdir /usr/src/project
COPY . /usr/src/project
WORKDIR /usr/src/project
RUN mvn package -DskipTests
RUN jar xf target/JavaCoffeeShop.jar
RUN jdeps --ignore-missing-deps -q  \
    --recursive  \
    --multi-release 17  \
    --print-module-deps  \
    --class-path 'BOOT-INF/lib/*'  \
    target/JavaCoffeeShop.jar > deps.info
RUN jlink \
    --add-modules $(cat deps.info) \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --output /myjre
FROM debian:bookworm-slim
ENV JAVA_HOME /user/java/jdk17
ENV PATH $JAVA_HOME/bin:$PATH
COPY --from=build /myjre $JAVA_HOME
RUN mkdir /project
COPY --from=build /usr/src/project/target/JavaCoffeeShop.jar /project/
WORKDIR /project
ENTRYPOINT java -jar JavaCoffeeShop.jar

Dans l’exemple ci-dessus, j’ai utilisé un build Docker en plusieurs étapes. La première étape de build repose sur une image eclipse-temurin JDK 17 qui inclut Maven. Elle sert à :

  • Créer l’artefact Java. Avec Maven, je crée le fichier JAR exécutable volumineux contenant l’application complète.

  • Décompresser le fichier JAR pour récupérer toutes les dépendances. Cette étape est nécessaire uniquement si vous n’utilisez pas le maven-dependency-plugin décrit précédemment. Si vous l’avez inclus, vous pouvez passer cette étape.

  • Utiliser jdeps pour récupérer les modules nécessaires. Indiquez le fichier contenant tous les fichiers JAR des dépendances ainsi que l’artefact final, puis enregistrez la liste dans deps.info.

  • Exécuter JLink pour créer un environnement d’exécution Java personnalisé. Utilisez deps.info comme entrée et stockez le résultat dans /myjre. Nous ajoutons uniquement les modules nécessaires à JLink et supprimons les informations de débogage, les pages de manuel et les fichiers d’en-tête.

La deuxième et dernière étape crée l’image de production à partir d’une image debian:stable-slim.

  • Définir les variables d’environnement. Définissez JAVA_HOME sur le chemin où je vais copier myjre, puis ajoutez JAVA_HOME à PATH.

  • Copier l’environnement d’exécution Java créé par JLink. Faites référence à la première étape et copiez l’environnement d’exécution Java personnalisé à l’emplacement défini par JAVA_HOME.

  • Copier l’artefact Java créé. Le fichier JAR exécutable volumineux créé par Spring Boot est copié dans le répertoire dédié au projet.

  • Définir l’Entrypoint

JLink présente plusieurs avantages pour la création d’images Docker destinées aux applications Java Spring Boot :

1. Réduction de la taille des images : comme indiqué précédemment, JLink peut contribuer à réduire la taille de votre image Docker, accélérant ainsi le déploiement et diminuant les coûts de stockage.

2. Démarrage plus rapide : une image Docker plus légère permet à votre application de démarrer plus vite, ce qui est essentiel pour les applications qui doivent évoluer rapidement.

3. Sécurité : en incluant uniquement les modules nécessaires, vous réduisez la surface d’attaque de votre application. Moins de modules, c’est moins de vulnérabilités potentielles.

En matière de sécurité, il est essentiel de souligner le rôle de Snyk dans la protection de vos applications. Snyk est un outil de sécurité destiné aux développeurs qui analyse votre code source, vos packages open source, vos images de conteneurs et vos configurations cloud à la recherche de vulnérabilités. Avec Snyk Container et Snyk Open Source, vous pouvez détecter et corriger les problèmes de sécurité dans votre application et ses dépendances, y compris ceux présents dans vos images Docker.

$ snyk container test your-repo/your-image:tag

Dans la commande ci-dessus, your-repo/your-image:tag désigne votre image Docker. Snyk l’analysera et signalera les vulnérabilités détectées, ainsi que des suggestions pour les corriger.

Créer des images Docker plus légères et plus sécurisées pour les applications Java

Notez que les exemples de cet article ont pour but de montrer comment utiliser JLink pour créer une image Docker plus légère pour vos projets Java. Ils ne respectent pas toutes les bonnes pratiques pour sécuriser les images Docker. Pour en savoir plus, consultez notre article « 10 bonnes pratiques pour créer un conteneur Java avec Docker ».

En conclusion, JLink est un outil puissant qui peut vous aider à créer des images Docker plus légères et plus sécurisées pour vos applications Java Spring Boot. Associé à des outils de sécurité comme Snyk, il vous permet de garantir les performances et la sécurité de vos applications. Alors, pourquoi attendre ? Inscrivez-vous à Snyk dès aujourd’hui et commencez à sécuriser vos applications.

La sécurité des conteneurs, pensée pour les développeurs

Snyk détecte et corrige automatiquement les vulnérabilités dans les images de conteneurs et les workloads Kubernetes.