Compiler une application Spring en natif avec GraalVM
Avec la sortie cette semaine de Spring Native Beta en version 0.9.0, il est intéressant de faire un état des lieux de la compilation d’applications Spring
en exécutables natifs à l’aide de GraalVM
et de son mode native-image
.
L’exécution d’une application en code natif a, en effet, de nombreux intérêts comparée à celle en Bytecode dans une JVM :
- Le démarrage est instantané
- La performance est optimale dès le démarrage
- La consommation de la mémoire est fortement réduite
La version de Spring Native est, toutefois, en Beta ce qui signifie que tous les composants de Spring ne sont pas encore fonctionnels en mode natif. Voyons en détails son fonctionnement.
- Configuration requise de base
- Génération du squelette d’application
- Ajout d’un Controller Web
- Compilation en code natif
- Conclusion
Configuration requise de base
Tout d’abord, vous devrez installer GraalVM et ensuite son compilateur en code natif native-image
:
Pour MacOS et Linux, il est recommandé d’installer ses JDKs avec SDKMAN. Rien de compliqué : référez-vous à la doc. officielle ou bien retrouvez un précédent article sur cet outil Installer Java avec SDKMAN
Pour nos amis sur Windows, reportez-vous directement Ă la page Installation on Windows Platforms
Génération du squelette d’application
L’arrivée de la version Beta implique que Spring Native est désormais supporté par Spring Initializr, une interface web qui permet de composer son application Spring puis de générer son squelette.
Utilisons-la pour définir notre application démo :
- Renseignez les métadonnées du projet
- Sélectionnez la dépendance
Spring Native [Experimental]
pour bénéficier de la compilation native - Ajoutez la dépendance
Spring Web
dans le cadre de cette démo - Téléchargez le code généré en cliquant sur le bouton
Generate
Modules Spring Native
Vous trouverez, dans le POM, la liste de modules Spring configurés en tant que dépendances Maven :
- La dépendance
Spring Native
et sa version :
- Le plugin
Spring Boot Maven
et sa configuration pour exécuter le build d’une image native dans un conteneur Buildpacks :
- Le plugin
AOT Maven
qui sert Ă configurer Spring pour sa compilationAhead-Of-Time
ainsi qu’à générer du code pour la configuration et le classpath de l’application :
Remarques
Dépendances non supportées
Au cas où vous sélectionneriez une dépendance Spring non encore supportée dans le mode natif, le fichier HELP.md
contiendra un avertissement :
Dépendances supportées
- Dans le cas des dépendances supportées par Spring, l’initializr va configurer tous les plugins nécessaires pour que le build et l’exécution de l’application Spring fonctionnent out-of-the-box !
Dans l’exemple de Spring Data JPA
, Maven sera configuré pour que les classes Hibernate soient compilées au moment du build de l’application et non pas lors de son runtime comme c’est le cas pour une JVM :
Tout cela est très rassurant ! J’avais testé auparavant la version 0.7.1
de Spring Native (nommé spring-graalvm-native à l’époque) et il y avait alors beaucoup de modifications manuelles à apporter.
But affiché de l'équipe en charge de Spring Native
- Fournir une configuration automatiquement afin qu’il n’y ait pas besoin de modifier le code Java, que l’application soit exécutée en mode natif ou dans une JVM.
- Faire en sorte que les tests unitaires s’exécutent de la même façon dans une image native ou dans une JVM.
- Réduire encore plus la taille de l’image native générée dans la prochaine version 0.10 de Spring Native.
Ajout d’un Controller Web
Dézippez le fichier généré par
Spring Initializr
et ouvrez le répertoire avec votre IDE préféré.Créez un nouveau Controller à la racine du package de votre projet avec le code ci-dessous :
Compilation en code natif
Il existe deux façons de compiler une application Spring en code natif :
- En utilisant le Buildpack Spring Boot intégré à Spring et qui va produire un conteneur léger contenant le code natif de l’application
- En utilisant le plugin Maven native-image-maven-plugin qui va produire un exécutable natif
Remarque
La configuration Maven générée par Spring Initializr fait le choix de Buildpacks :
- Nous n’aborderons par conséquent que cet aspect dans cet article.
- Nous verrons le build natif à l’aide du plugin Maven native-image qui nécessite des modifications importantes du POM, dans un prochain article.
Utilisation du Buildpack Spring Boot
Cette procédure permet d’obtenir un conteneur Docker qui contient l’application compilée en code natif. Il est léger et peut être déployé directement dans un orchestrateur de conteneurs.
Pré-requis
Docker doit être installé afin de pouvoir lancer le Buildpack Spring Boot. C’est un conteneur qui contient tout le nécessaire pour builder une application Spring en code natif.
- Vous pouvez installer Docker Ă partir de Docker Installation
- Pour MacOS, il est recommandé d’allouer au moins 8Go de mémoire à Docker
- Pour Windows, il faut activer Docker WSL 2 Backend pour avoir de meilleures performances
Compilation en mode natif avec Buildpacks
- L’application native peut être compilée en lançant la commande suivante :
Cette commande va créer, en local, un conteneur Linux pour compiler l’application native à partir du compilateur native-image
de GraalVM.
- Regardons les images présentes, dans le registre Docker local et qui viennent d’être mises en oeuvre dans ce build :
On constate que ce processus produit 3 images Docker :
- paketobuildpacks/run:tiny-cnb : Le
runner
basé surdistroless
bionic + glibc + openssl + CA certs pour exécuter une application en code natif. C’est le conteneur de base servant à encapsuler une application en code natif. - paketobuildpacks/builder:tiny : Le
builder
basé sur une stackdistroless
ubuntu:bionic + openssl + CA certs + compilers + shell utilities. C’est un Buildpack servant à compiler la plupart des applications en Go et les applications Java en code natif avec GraalVM. - demo_spring_native:0.0.1-SNAPSHOT : L’application, en code natif, encapsulée dans un runner de base
distroless
.
Pour aller plus loin
- Les images issues du Buildpack datent de 1980, du 1er janvier 1980 exactement ! C’est tout à fait voulu et l’explication se trouve là : Time Travel with Pack
- Les stacks Distroless sont des images minimalistes, développées par Google et qui améliorent la sécurité et la taille des conteneurs en diminuant la surface des attaques et le nombre de composants qu’elles intègrent.
- La notion de Runner et Builder dans les Buildpacks.
Exécution de l’application
- Pour démarrer l’application issue du Buildpack, tapez la commande suivante:
- Testez son fonctionnement avec :
Ca marche ! Magnifique !!
Caractéristiques du Buildpacks
- La compilation dure 3 min (avec les images Docker et les artefacts Maven en local)
- L’application démarre en 0.06 s
- L’image Docker contenant l’application Spring et l’OS, fait une taille de 82 Mo
Conclusion
- La version Spring Native 0.9.0 nous a permis de compiler facilement une application Spring en mode natif.
- Comme attendu, les bénéfices du mode natif sont un démarrage instantané et une taille de conteneur fortement réduite.
Points intéressants, cela engendre de nouvelles utilisations :
- la gestion du High Availability peut se faire avec une seule instance, le démarrage d’une seconde étant instantanée.
- le démarrage instantané permet aussi à une application web d’être serverless, sans avoir besoin d’être redéveloppée.
- Avec Knative (un redesign de Kubernetes qui démarre des conteneurs serverless), GraalVM Native est une solution très bien adaptée.
Spring Native sera, à terme, intégré dans Spring Boot 3 et Spring Framework 6, le but étant de spécifier uniquement dans le build Maven ou Graddle, la cible attendue (native ou autre). Le travail restant consiste à optimiser la taille du code natif générée, prendre en compte plus d’APIs Spring et améliorer l’exécution des tests dans l’image native (JUnit 5,…)
A suivre de près donc !
Cheers…