Remarque :
- Ce tutoriel est disponible dans un environnement de laboratoire gratuit fourni par Oracle.
- Il utilise des exemples de valeur pour les informations d'identification, la location et les compartiments Oracle Cloud Infrastructure. A la fin de l'exercice, remplacez ces valeurs par celles propres à votre environnement cloud.
GraalVM Native Image, Spring et Containerisation
Introduction
Cet atelier est destiné aux développeurs qui souhaitent en savoir plus sur la mise en conteneur d'applications GraalVM Native Image.
La technologie GraalVM Native Image compile le code Java à l'avance dans un fichier exécutable natif. Seul le code requis lors de l'exécution par l'application est inclus dans le fichier exécutable.
Un fichier exécutable produit par Native Image présente plusieurs avantages importants, en ce sens qu'il :
- Utilise une fraction des ressources requises par la JVM. L'exécution est donc moins coûteuse
- Commence en millisecondes
- Performances de pointe immédiatement, sans réchauffement
- Peut être packagé dans une image de conteneur légère pour un déploiement plus rapide et plus efficace
- Présente une surface d'attaque réduite (plus importante dans les exercices à venir)
Nombre des principaux cadres de travail de microservices prennent en charge la compilation anticipée avec GraalVM Native Image, y compris Micronaut, Spring, Helidon et Quarkus.
En outre, il existe des modules d'extension Maven et Gradle pour Native Image afin que vous puissiez facilement créer, tester et exécuter des applications Java en tant que fichiers exécutables.
Remarque : Oracle Cloud Infrastructure (OCI) fournit GraalVM Enterprise sans frais supplémentaires.
Temps de laboratoire estimé : 90 minutes
Objectifs du laboratoire
Dans cet exercice, vous allez :
- Ajouter une application Spring Boot de base à une image Docker et l'exécuter
- Créer un exécutable natif à partir de cette application à l'aide de GraalVM Native Image
- Ajouter l'exécutable natif à une image Docker
- Réduire la taille de l'image de quai d'application grâce aux conteneurs GraalVM Native Image et Distroless
- Découvrez comment utiliser les outils de build natif GraalVM, le module d'extension Maven
REMARQUE : Si l'icône de l'ordinateur portable apparaît dans l'exercice, cela signifie que vous devez exécuter une commande, par exemple. Gardez un oeil pour cela.
# This is where you will need to do something
Etape 1 : connexion à un hôte virtuel et vérification de l'environnement de développement
Votre environnement de développement est fourni par un hôte distant : une instance de calcul OCI avec Oracle Linux 8, 4 coeurs et 32 Go de mémoire. L'environnement de bureau Luna Labs s'affiche avant que l'hôte distant soit prêt, ce qui peut prendre jusqu'à deux minutes.
Vous vous connectez à votre hôte distant en exécutant un script de configuration dans votre environnement Luna Desktop. Ce script est disponible via l'onglet Ressources.
-
Cliquez deux fois sur l'icône Luna Lab sur le bureau pour ouvrir le navigateur.
-
L'onglet Ressources s'affiche. Le graphique affiché en regard du titre Ressources tourne tandis que l'instance de calcul est provisionnée dans le cloud.
-
Lorsque l'instance est provisionnée (cette opération peut prendre jusqu'à 2 minutes), les éléments suivants apparaissent dans l'onglet Ressources.
-
Copiez le script de configuration qui configure votre environnement VS Code à partir de l'onglet Ressources. Cliquez sur le lien Visualiser les détails pour afficher la configuration. Copiez ceci comme indiqué dans la capture d'écran ci-dessous :
-
Ouvrez un terminal, comme indiqué dans la capture d'écran ci-dessous :
-
Collez le code de configuration dans le terminal, qui va ouvrir VS Code pour vous.
-
Une fenêtre de code VS s'ouvre et se connecte automatiquement à l'instance de machine virtuelle qui a été provisionnée pour vous. Cliquez sur Continuer pour accepter l'empreinte de la machine.
Vous avez terminé ! Félicitations. Vous êtes maintenant connecté à un hôte distant dans Oracle Cloud !
Le script ci-dessus ouvre le code VS, connecté à votre instance de calcul distante avec le code source de l'atelier ouvert.
Vous devrez ensuite ouvrir un terminal dans le code VS. Ce terminal vous permettra d'interagir avec l'hôte distant. Un terminal peut être ouvert dans le code VS via le menu : Terminal > New Terminal.
Nous utiliserons ce terminal dans le reste du laboratoire.
Remarque sur l'environnement de développement
Nous utiliserons GraalVM Enterprise 22, en tant qu'environnement Java pour cet exercice. GraalVM est une distribution JDK hautes performances d'Oracle basée sur Oracle Java SE fiable et sécurisé.
Votre environnement de développement est préconfiguré avec GraalVM et les outils Native Image requis pour cet atelier.
Vous pouvez facilement vérifier qu'en exécutant ces commandes dans le terminal :
java -version
native-image --version
Etape 2 : satisfaire notre exemple d'application Java
Au cours de cet atelier, vous allez créer une application simple comportant une API REST très minimale. Vous allez ensuite mettre en conteneur cette application à l'aide de Docker. Commencez par examiner rapidement votre application simple.
Nous avons fourni le code source et les scripts de création pour cette application et le dossier contenant le code source sera ouvert dans le code VS.
L'application est construite sur la structure Spring Boot et utilise le projet natif de printemps (un incubateur de printemps pour générer des exécutables natifs à l'aide de GraalVM Native Image).
L'application possède deux classes, qui se trouvent dans src/main/java
:
com.example.demo.DemoApplication
: classe d'initialisation Spring principale qui définit également l'adresse HTTP,/jibber
com.example.demo.Jabberwocky
: classe d'utilitaire qui implémente la logique de l'application.
Que fait l'application ? Si vous appelez le point de terminaison REST /jibber
, défini dans l'application, il renverra un verset non dense généré dans le style du poème jabberwocky par Lewis Carroll. Pour ce faire, le programme utilise une chaîne de marketing pour modéliser le poème d'origine (il s'agit essentiellement d'un modèle statistique). Ce modèle génère un nouveau texte.
Dans l'exemple d'application, nous fournissons à l'application le texte du poème, puis générons un modèle du texte que l'application utilise ensuite pour générer un nouveau texte similaire au texte d'origine. Nous utilisons la bibliothèque RiTa pour nous faire le gros du travail - elle prend en charge la construction et l'utilisation de Markov Chains.
Voici deux fragments de code de la classe d'utilitaire com.example.demo.Jabberwocky
qui créent le modèle. La variable text
contient le texte du poème d'origine. Ce fragment de code montre comment créer le modèle, puis comment le remplir avec text
. Cela est appelé par le constructeur de classe et nous définissons la classe comme un Singleton (afin qu'une seule instance de la classe soit créée).
this.r = new RiMarkov(3);
this.r.addText(text);
Ici, vous pouvez voir la méthode de génération de nouvelles lignes de verset à partir du modèle, en fonction du texte d'origine.
public String generate() {
String[] lines = this.r.generate(10);
StringBuffer b = new StringBuffer();
for (int i=0; i< lines.length; i++) {
b.append(lines[i]);
b.append("<br/>\n");
}
return b.toString();
}
Prenez un peu de temps à visualiser le code et à vous familiariser avec lui.
Pour créer l'application, vous allez utiliser Maven. Le fichier pom.xml
a été généré à l'aide de Spring Initializr et contient la prise en charge de l'utilisation de l'outil natif Spring. Il s'agit d'une dépendance que vous avez ajoutée à vos projets Spring Boot si vous prévoyez de cibler GraalVM Native Image. Si vous utilisez Maven, l'ajout de la prise en charge de Spring Native insère le module d'extension suivant dans votre configuration de build par défaut.
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Maintenant, créez votre application. A partir du répertoire racine du référentiel, exécutez les commandes suivantes dans votre shell :
mvn clean package
Cette opération génère un fichier JAR exécutable, qui contient toutes les dépendances de l'application, ainsi qu'un fichier MANIFEST
correctement configuré. Vous pouvez exécuter ce fichier JAR, puis envoyer une commande ping à l'adresse de l'application pour voir ce que vous obtenez. Placez la commande en arrière-plan à l'aide de &
afin d'obtenir l'invite de retour.
java -jar ./target/jibber-0.0.1-SNAPSHOT-exec.jar &
Appelez le point de fin à l'aide de la commande curl
à partir de la ligne de commande.
Lorsque vous publiez la commande dans votre terminal, VS Code peut vous inviter à ouvrir l'URL dans un navigateur, il suffit de fermer la boîte de dialogue, comme illustré ci-dessous.
Exécutez la commande suivante pour tester l'adresse HTTP :
curl http://localhost:8080/jibber
Tu as eu le verset des absurdes ? Maintenant que vous avez créé une application de travail, mettez-la fin et passez à la mise en conteneur. Amenez l'application au premier plan afin de pouvoir l'arrêter.
fg
Entrez <ctrl-c>
pour mettre fin à l'application.
<ctrl-c>
Etape 3 : mise en conteneur de votre application Java avec Docker
La mise en conteneur de votre application Java en tant que conteneur Docker est, heureusement, relativement simple. Vous pouvez créer une image Docker basée sur une image contenant une distribution JDK. Dans cet atelier, vous allez utiliser un conteneur qui contient déjà un JDK, container-registry.oracle.com/java/openjdk:17-oraclelinux8
, il s'agit d'une image Oracle Linux 8 avec OpenJDK.
Voici une répartition du fichier Dockerfile qui explique comment créer l'image Docker. Reportez-vous aux commentaires pour expliquer le contenu.
FROM container-registry.oracle.com/java/openjdk:17-oraclelinux8 # Base Image
ARG JAR_FILE # Pass in the JAR file as an argument to the image build
EXPOSE 8080 # This image will need to expose TCP port 8080, as this is the port on which your app will listen
COPY ${JAR_FILE} app.jar # Copy the JAR file from the `target` directory into the root of the image
ENTRYPOINT ["java"] # Run Java when starting the container
CMD ["-jar","app.jar"] # Pass in the parameters to the Java command that make it load and run your executable JAR file
Le fichier Dockerfile permettant de mettre en conteneur l'application Java se trouve dans le répertoire 00-containerise
.
Pour créer une image Docker contenant votre application, exécutez les commandes suivantes à partir de votre terminal :
docker build -f ./00-containerise/Dockerfile \
--build-arg JAR_FILE=./target/jibber-0.0.1-SNAPSHOT-exec.jar \
-t localhost/jibber:java.01 .
Interrogez Docker pour examiner la nouvelle image créée :
docker images | head -n2
Une nouvelle image doit apparaître dans la liste. Exécutez cette image comme suit :
docker run --rm -d --name "jibber-java" -p 8080:8080 localhost/jibber:java.01
Appelez ensuite l'adresse comme vous l'avez fait avant d'utiliser la commande curl
:
curl http://localhost:8080/jibber
Avez-vous vu le verset sans sens ? Vérifiez maintenant le temps nécessaire au démarrage de votre application. Vous pouvez l'extraire des journaux, à mesure que les applications Spring Boot écrivent l'heure de démarrage dans les journaux :
docker logs jibber-java
Par exemple, l'application a démarré dans 3.896s. Voici l'extraction des journaux :
2022-03-09 19:48:09.511 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 3.896 seconds (JVM running for 4.583)
OK, mettez fin au conteneur et continuez :
docker kill jibber-java
Vous pouvez également interroger Docker pour obtenir la taille de l'image. Nous avons fourni un script qui effectue cette opération pour vous. Exécutez la commande suivante dans votre terminal :
echo $((`docker inspect -f "" localhost/jibber:java.01`/1024/1024))
La taille de l'image est imprimée en Mo, soit 606 Mo.
Etape 4 : création d'un exécutable natif
Récupérez ce que vous avez jusqu'à présent :
- Vous avez créé une application Spring Boot avec une adresse HTTP,
/jibber
- Vous l'avez mise en conteneur
Vous allez maintenant découvrir comment créer un exécutable natif à partir de votre application à l'aide de GraalVM Native Image. Cet exécutable natif aura un certain nombre de caractéristiques intéressantes, à savoir :
- Cela va commencer très vite
- Elle utilisera moins de ressources que son application Java correspondante.
Vous pouvez utiliser les outils Native Image installés à l'aide de GraalVM afin de créer un exécutable natif d'une application à partir de la ligne de commande. Mais, comme vous utilisez déjà Maven, vous allez appliquer les outils de build natif GraalVM pour Maven, qui vous permettront de continuer à utiliser Maven pour créer.
Pour ajouter la prise en charge de la création d'un exécutable natif, vous pouvez utiliser un profil Maven, qui vous permet de déterminer si vous souhaitez simplement créer le fichier JAR ou un exécutable natif.
Dans le fichier Maven pom.xml
fourni, nous avons ajouté un profil qui crée un exécutable natif. Approche plus détaillée :
Vous devez d'abord déclarer le profil et lui attribuer un nom.
<profiles>
<profile>
<id>native</id>
<!-- Rest of profile hidden, to highlight relevant parts -->
</profile>
</profiles>
Ensuite, dans le profil, nous incluons le module d'extension d'outils de création GraalVM Native Image et l'attachons à la phase package
de Maven. Cela signifie qu'il sera exécuté dans le cadre de la phase package
. Vous pouvez transmettre les arguments de configuration à l'outil de création Native Image sous-jacent à l'aide de la section <buildArgs>
. Dans les balises buildArg
individuelles, vous pouvez transmettre les paramètres exactement de la même manière que pour l'outil native-image
. Vous pouvez donc utiliser tous les paramètres qui fonctionnent avec l'outil native-image
:
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-buildtools.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<imageName>jibber</imageName>
<buildArgs>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
<!-- Rest of profile hidden, to high-light relevant parts -->
</plugins>
</build>
Exécutez maintenant le build Maven à l'aide du profil, comme indiqué ci-dessous (notez que le nom de profil est indiqué avec l'indicateur -P
) :
mvn package -Pnative
Cette opération génère un exécutable natif pour la plate-forme dans le répertoire target
, appelé jibber
. Examinez la taille du fichier :
ls -lh target/jibber
Exécutez cet exécutable natif et testez-le. Exécutez la commande suivante dans votre terminal pour exécuter l'exécutable natif et le placer en arrière-plan à l'aide de &
:
./target/jibber &
Appelez l'adresse à l'aide de la commande curl
:
curl http://localhost:8080/jibber
Vous disposez désormais d'un exécutable natif de l'application qui démarre très rapidement !
Mettez fin à l'application avant de continuer. Mettez l'application au premier plan :
fg
Terminez-le avec <ctrl-c>
:
<ctrl-c>
Etape 5 : mise en conteneur de votre exécutable natif
Maintenant, étant donné que vous disposez d'une version exécutable native de votre application et que vous l'avez vue fonctionnelle, mettez-la en conteneur.
Nous avons fourni un fichier Dockerfile simple pour le packaging de cet exécutable natif : il se trouve dans le répertoire native-image/containerisation/lab/01-native-image/Dockerfile
. Le contenu est présenté ci-dessous, ainsi que des commentaires pour expliquer chaque ligne.
FROM container-registry.oracle.com/os/oraclelinux:8-slim
ARG APP_FILE # Pass in the native executable
EXPOSE 8080 # This image will need to expose TCP port 8080, as this is port your app will listen on
COPY ${APP_FILE} app # Copy the native executable into the root directory and call it "app"
ENTRYPOINT ["/app"] # Just run the native executable :)
Pour créer, exécutez la commande suivante à partir de votre terminal :
docker build -f ./01-native-image/Dockerfile \
--build-arg APP_FILE=./target/jibber \
-t localhost/jibber:native.01 .
Examinez la nouvelle image :
docker images | head -n2
Vous pouvez maintenant l'exécuter et le tester comme suit à partir du terminal :
docker run --rm -d --name "jibber-native" -p 8080:8080 localhost/jibber:native.01
Appelez l'adresse à partir du terminal à l'aide de curl
:
curl http://localhost:8080/jibber
Encore une fois, vous auriez dû voir plus de verset absurde dans le style du poème Jabberwocky. Vous pouvez examiner la durée du démarrage de l'application en consultant les journaux générés par l'application, comme vous l'avez fait précédemment. A partir de votre terminal, exécutez la commande suivante et recherchez l'heure de démarrage :
docker logs jibber-native
Nous avons vu ce qui suit qui montre que l'application a démarré dans 0.074s. C'est une nette amélioration par rapport à l'original de 3.896s !
2022-03-09 19:44:12.642 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.074 seconds (JVM running for 0.081)
Mettez fin au conteneur et passez à l'étape suivante :
docker kill jibber-native
Mais avant de passer à l'étape suivante, regardez la taille du conteneur produit :
echo $((`docker inspect -f "" localhost/jibber:native.01`/1024/1024))
La taille de l'image de conteneur affichée était de 199
Mo. Bien plus petit que notre conteneur Java d'origine.
Etape 6 : création d'un exécutable le plus statique et emballage d'une image sans distinction
Récupérez ce que vous avez fait jusqu'à présent :
- Vous avez créé une application Spring Boot avec une adresse HTTP,
/jibber
- Vous l'avez mise en conteneur
- Vous avez créé un exécutable natif de votre application à l'aide des outils de construction Native Image pour Maven.
- Vous avez mis en conteneur votre exécutable natif
Il serait intéressant de réduire encore davantage la taille de votre conteneur, car les conteneurs plus petits sont plus rapides à télécharger et à démarrer. Avec GraalVM Native Image, vous pouvez lier de manière statique les bibliothèques système à l'exécutable natif que vous générez. Si vous créez un exécutable natif lié de manière statique, vous pouvez packager l'exécutable natif directement dans une image Docker vide, également appelée conteneur scratch
.
Une autre option consiste à produire un exécutable natif lié principalement de façon statique. Dans ce cas, vous effectuez un lien statique dans toutes les bibliothèques système à l'exception de la bibliothèque C standard, glibc
. Avec un exécutable natif de ce type, vous pouvez utiliser un petit conteneur, tel que Google's Distroless, qui contient la bibliothèque glibc
, des fichiers standard et des certificats de sécurité SSL. La taille du conteneur Distroless standard est d'environ 20 Mo.
Vous allez créer un exécutable lié principalement de façon statique, puis le packager dans un conteneur sans distinction.
Nous avons ajouté un autre profil Maven pour créer cet exécutable natif lié principalement de façon statique. Ce profil est nommé distroless
. La seule différence entre ce profil et celui que vous avez utilisé auparavant, native
, est que nous transmettons un paramètre, -H:+StaticExecutableWithDynamicLibC
. Comme vous pouvez le penser, cela indique à native-image
de créer un exécutable natif lié principalement statiquement.
Vous pouvez créer votre exécutable natif principalement lié de manière statique comme suit :
mvn package -Pdistroless
C'est assez facile. L'exécutable natif généré se trouve dans le répertoire cible jibber-distroless
.
Collez-la maintenant dans un conteneur sans distinction. Le fichier Dockerfile à cette fin se trouve dans le répertoire native-image/containerisation/lab/02-smaller-containers/Dockerfile
. Examinez le contenu du fichier Dockerfile, qui contient des commentaires pour expliquer chaque ligne :
FROM gcr.io/distroless/base # Our base image, which is Distroless
ARG APP_FILE # Everything else is the same :)
EXPOSE 8080
COPY ${APP_FILE} app
ENTRYPOINT ["/app"]
Pour créer, exécutez la commande suivante à partir de votre terminal :
docker build -f ./02-smaller-containers/Dockerfile \
--build-arg APP_FILE=./target/jibber-distroless \
-t localhost/jibber:distroless.01 .
Examinez la nouvelle image Distroless :
docker images | head -n2
Maintenant, vous pouvez l'exécuter et le tester comme suit :
docker run --rm -d --name "jibber-distroless" -p 8080:8080 localhost/jibber:distroless.01
curl http://localhost:8080/jibber
Génial ! Ça a travaillé. Mais quelle est la taille de votre conteneur ? Utilisez le script pour vérifier la taille de l'image :
echo $((`docker inspect -f "" localhost/jibber:distroless.01`/1024/1024))
La taille est d'environ 107 Mo. Nous avons donc réduit le conteneur de 92 Mo. Par rapport à notre taille de départ, pour le conteneur Java, d'environ 600 Mo.
Conclusion
Nous espérons que vous avez apprécié ce laboratoire et que vous avez appris quelques choses en chemin. Nous avons vu comment mettre en conteneur une application Java. Ensuite, nous avons vu comment convertir cette application Java en exécutable natif, qui démarre beaucoup plus rapidement que l'application Java. Nous avons ensuite mis en conteneur l'exécutable natif et avons vu que la taille de l'image Docker, avec l'exécutable natif, est beaucoup plus petite que l'image Java Docker.
Enfin, nous avons vu comment construire des exécutables natifs liés statiquement principalement avec Native Image. Ces derniers peuvent être packagés dans des conteneurs plus petits, comme Distroless (indisponibilité), ce qui nous permet de réduire encore davantage la taille de l'image Docker.
En savoir plus
- Regardez une présentation de l'architecte Native Image Christian Wimmer GraalVM Native Image : analyse statique à grande échelle pour Java
- Documentation de référence sur GraalVM Native Image
Ressources de formation supplémentaires
Explorez d'autres exercices sur docs.oracle.com/learn ou accédez à davantage de contenu d'apprentissage gratuit sur le canal Oracle Learning YouTube. De plus, visitez le site education.oracle.com/learning-explorer pour devenir Oracle Learning Explorer.
Pour consulter la documentation du produit, consultez le centre d'aide Oracle.
GraalVM Native Image, Spring and Containerisation
F54860-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.