Développer une application Cloud Ready avec Quarkus

Il y a quelques mois déjà, je vous avais parlé de Quarkus, le nouveau framework de développement de microservices en Java, pensé pour le cloud et les containers. Si vous n’aviez pas lu l’article à cet époque, et voulez découvrir ce framework, c’est ici : Zoom sur Quarkus.

Depuis, Quarkus est sorti en version 1.0 et j’ai eu la chance d’en parler au Devfest Nantes et plus particulièrement de comment Quarkus répond aux problématiques du Cloud. 

C’est le sujet de cet article : le développement d’application Cloud Ready avec Quarkus.

Cloud Ready

Difficile de donner une définition à ce qu’est une application Cloud Ready.

Beaucoup de gens citent les principes des 12 Factors applications https://12factor.net/

Ils définissent certains principes qui dépassent le cadre des applications Cloud Ready mais on peut noter les principes suivants :

  • Exécuter les applications comme des processus stateless (sans état).
  • Exporter nos services via un port binding.
  • Scale out en ajoutant un process.
  • Démarrage rapide et arrêt gracieux (sans perdre de requête par exemple).
  • Configuration via variables d’environnement.
  • Dev, staging et prod aussi similaires que possible.
  • Séparation des étapes de build et de run.

Pour définir ce qu’est une application, on peut aussi citer les principes définis dans le livre blanc Container native applications de RedHat : https://www.redhat.com/en/resources/cloud-native-container-design-whitepaper

Ce livre blanc se concentre sur les applications à déployer dans des containers, et c’est généralement ce que l’on déploie dans le Cloud.

Ce livre blance reprend certains principes des 12 factors applications et insiste sur certains points essentiels pour le déploiement via conteneurs :

  • La même image doit être déployée sur tous les environnements.
  • Observabilité : health, métriques, tracing, logs, …
  • Processus jetable (ce qui induit aussi un démarrage et arrêt rapide).
  • Confinement : une image doit contenir tout ce qui est nécessaire à l’application.

La réponse de Quarkus

Container native

Quarkus est un framework qu’on pourrait qualifier de Container Native. Il a été pensé dès le début pour être déployé dans un conteneur.

À l’initialisation d’un projet Quarkus, des Dockerfile par défaut sont fournis, et la documentation explique comment les utiliser pour déployer dans Kubernetes (qui est un des standards d’orchestration de conteneur) ou OpenShift.

Par défaut, le packaging d’une application Quarkus produit un JAR avec les classes de vos applications, et un répertoire lib avec toutes vos librairies. En copiant les librairies dans votre conteneur Docker via un layer séparé du JAR de votre application, vous évitez de changer ce layer à chaque packaging de votre application, ce qui améliore nettement le temps de création de votre conteneur ainsi que sa taille.

Un framework d’extension

Quarkus est un framework d’extension, le coeur de Quarkus est très petit. Quarkus apporte principalement ses fonctionnalités via l’intégration de librairies existantes via une extension.

Une extension permet d’intégrer une librairie dans Quarkus pour qu’elle :

  • Se configure via le système de configuration globale de Quarkus.
  • S’intègre avec son moteur d’injection de dépendance (CDI).
  • S’intègre dans le système de build de Quarkus.

Chaque extension va importer uniquement les librairies nécessaires à son fonctionnement. 

Quarkus sépare le déploiement (build) du runtime : si une librairie n’est nécessaire qu’au build (par exemple une librairie pour lire un fichier de configuration XML), elle ne sera pas chargée au runtime (ni packagée), ce qui économise de la mémoire et de l’espace disque.

Chaque extension va pouvoir exécuter des étapes de build (build steps) qui seront exécutées via le plugin Maven/Gradle et permettre une optimisation du temps de démarrage et de l’empreinte mémoire, en déplaçant des étapes de démarrage de la librairie au build au lieu du runtime (par exemple : l’analyse d’annotations).

Quarkus minimise l’utilisation de la réflexion à l’exécution en utilisant de la génération de bytecode au build time. Ça permet encore un démarrage plus rapide de votre application.

Tout ça est un peu compliqué, mais c’est une des forces de Quarkus. 

Pour plus d’information : https://quarkus.io/guides/writing-extensions

GraalVM

Pour faire simple, GraalVM est un projet mené par Oracle Labs dans le but d’implémenter une machine virtuelle Java en Java, et de pouvoir exécuter via celle-ci n’importe quel langage de programmation de manière interopérable (on peut appeler une méthode d’un langage depuis un autre).

GraalVM se base sur la machine virtuelle HotSpot (celle d’OpenJDK) et contient un ensemble de composants que voici.

Quarkus s’intègre avec deux composants spécifiques :

  • Le compilateur Graal, et plus spécifiquement la capacité de l’utiliser comme compilateur AOT (Ahead Of Time) via l’outil native-image pour packager une application Quakus comme un exécutable natif.
  • SubstrateVM qui est la machine virtuelle minimale intégrée aux exécutables natifs.

SubstrateVM vient avec quelques limitations :

  • Closed-world assumption via analyse statique du code : lors de l’utilisation de l’outil native-image, le code de l’application va être analysé, et seul celui-ci pourra ensuite être exécuté (monde clos : pas de code dynamique).
  • Support partiel de la réflexion et des proxy dynamiques.
  • Dead Code Elimination : lors de l’analyse de code, tout le code non exécutable sera supprimé.
  • JVM partielle : manque JMX, JVM-TI (donc pas d’agent Java), SecurityManager, …

Quarkus s’intègre avec GraalVM en faisant en sorte que toutes les extensions soient compatibles. Il facilite aussi l’utilisation de l’outil native-image en l’appelant pour nous via son plugin Maven.
Plus d’informations : https://quarkus.io/guides/writing-native-applications-tips.


Les fonctionnalités Cloud ready de Quarkus :

Quarkus propose de nombreuses fonctionnalités Cloud Ready, principalement basées sur Eclipse Microprofile et SmallRye une de ses implémentations.

Documentation d’API

OpenAPI permet de documenter vos API avec un jeu d’annotations défini dans la spécification OpenAPI v3 (inspiré de Swagger) : @OpenAPIDefinition, @Operation, @APIResponse, @Parameter, …

Pour les utiliser, il faut ajouter l’extension smallrye-openapi dans votre pom.xml :

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>

Puis ajouter les annotations nécessaires à votre ressource JAX-RS :

@Path("/bookmarks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@OpenAPIDefinition(info = @Info(title = "The Bookmark API", version = "1.0"))
public class BookmarkResource {

   @GET
   @Operation(summary = "List all bookmarks")
   public List<Bookmark> listAll() {
       return Bookmark.listAll();
   }

   @GET
   @Path("/{id}")
   @Operation(summary = "Get a bookmark")
   public Bookmark get(@PathParam("id") @Parameter(description = "Bookmark identifier") Long id) {
       return Bookmark.findById(id);
   }

}

Vous aurez ensuite accès à la documentation au format OpenAPI v3 via http://localhost:8080/openapi
En mode dev (configurable), un swagger UI est déployé pour pouvoir directement visualiser cette documentation et tester votre application via http://localhost:8080/swagger-ui

Heath Check

Quakus propose un système de Heath Check (littéralement test de vie de votre application) via Microprofile Health.

Pour les utiliser, il faut ajouter l’extension smallrye-health à votre pom.xml :

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

Vous pouvez ensuite utiliser l’URL http://localhost:8080/health pour récupérer l’état de santé de votre application.

Les health checks sont séparés en readiness et liveness checks, ceci permet une intégration facilité avec Kubernetes qui sépare les deux notions :

  • Liveness : est-ce que mon application est en vie ? Si elle ne l’est pas, Kubernetes va redémarrer le conteneur).
  • Readiness : est-ce que mon application est prête à recevoir du trafic. Si elle ne l’est pas, Kubernetes ne va pas envoyer de requête vers votre application (elle ne sera pas listée dans les endpoints d’un service par exemple).

Par défaut, certaines extensions fournissent des Readiness Check automatiquement (par exemple Agroal pour les checks de base de données).

Si vous nécessitez d’autres checks, vous pouvez les développer vous-même en suivant ce guide : https://quarkus.io/guides/health-guide

Voici un exemple de Health Check custom :

@Readiness
public class MyHealCheck implements HealthCheck {

   @Override
   public HealthCheckResponse call() {
       return HealthCheckResponse.builder().name("custom").withData("key", "value").up().build();
   }
}

Metrics

Quarkus fournit de la métrologie compatible Prometheus via Microprofile Metrics.

Pour l’utiliser il faut ajouter l’extension smallrye-metrics :

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-smallrye-metrics</artifactId>
</dependency>

Vous aurez ensuite un ensemble de métriques systèmes par défaut accessible via : http://localhost:8080/metrics

Certaines extensions fournissent des métriques, désactivées par défaut (car le coût de la récupération des métriques peut être important). C’est le cas d’Agroal par exemple, qui publie les métriques du pool de connection.

Si vous voulez ajouter vos propres métriques, vous pouvez le faire facilement via les annotations @Counted, @Timed and @Gauge.

Plus d’information dans le guide sur les métriques:  : https://quarkus.io/guides/opentracing-guide 

Voici un exemple de métrique sur un endpoint JAX-RS:

@Path("/bookmarks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookmarkResource {

   @GET
   @Counted(name = "listAll.count")
   @Timed(name="listAll.time")
   public List<Bookmark> listAll(){
       return Bookmark.listAll();
   }

   @GET
   @Path("/{id}")
   @Counted(name = "get.count")
   @Timed(name="get.time")
   public Bookmark get(@PathParam("id") Long id) {
       return Bookmark.findById(id);
   }
}

Tracing

Quarkus fournit une implémentation de métrique compatible OpenTracing via Microprofile Tracing et implémentée par Jaeger.

Pour l’utiliser il faut ajouter l’extension smallrye-opentracing :

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
<dependency>
 <groupId>io.opentracing.contrib</groupId>
 <artifactId>opentracing-jdbc</artifactId>
</dependency>

J’ai aussi intégré ici la librairie optionnelle opentracing-jdbc qui permet d’activer le tracing des requêtes SQL.

Pour finir, il faut configurer l’extension Jaeger :

quarkus.jaeger.service-name=prepaid-card-api
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.jaeger.endpoint=http://localhost:14268/api/traces

Ici on va utiliser un sample de type constant et tracer tous les appels (sampler-param=1).

Il n’y a aucun code spécifique pour activer le tracing. 

Plus d’information dans le guide Open Tracing.

Fault Tolerance

Dans le Cloud, tout peut arriver, chaque composant peut planter !

Pour palier à ça, il est fréquent d’utiliser des techniques de fault tolerance (littéralement tolérance à la panne) telle que des retry (rejeux), fallback (traitement par défaut), circuit breaker, …

Pour cela, Quarkus fournit une implémentation de Microprofile Fault Tolerance qui fournit les annotations @Retry, @Timeout, @Fallback, @CircuitBreaker and @BulkHead.

Pour l’utiliser, il faut ajouter l’extension smallrye-fault-tolerance :

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>

Plus d’information dans le Guide Fault Tolerance.

Configuration 

Quarkus propose un système de configuration centralisé dans un fichier application.properties ou application.yaml.

Chaque propriété de configuration est surchargeable via variable d’environnement ou propriété système Java (via -D).

On peut externaliser ce fichier de configuration sur le disque dur si nécessaire. Mais dans un environnement Cloud Ready, pour coller aux principes édictés ci-dessus, on privilégie les variables d’environnement comme surchage des valeurs de configuration.

Toutes les extensions Quarkus utilisent ce système de configuration centralisé.

Pour l’utiliser au sein de notre application, il faut utiliser l’annotation @ConfigProperty pour injecter une propriété du fichier de configuration dans notre application.

Par exemple si vous avez la propriété suivante dans votre fichier de configuration :

 greeting=World

Vous pouvez l’injecter via la ligne de code suivante :

@ConfigProperty(name="greeting") String greeting;

Quarkus permet aussi de gérer des profils de configuration pour définir des valeurs différentes pour différents environnements au sein du même fichier de configuration.

Quarkus vient avec trois profils par défaut:

  • dev : activé par défaut en mode dev.
  • prod : activé par défaut quand on est ni en dev ni en test.
  • test : activé par défaut dans les tests unitaires.

On peut définir ses propres profils qui peuvent être sélectionnés via la variable d’environnement QUARKUS_PROFILE ou la propriété système quarkus.profile.

Quarkus propose aussi une intégration avec certains systèmes de configuration centralisés (dont Vault) ainsi que les ConfigMap et Secret Kubernetes. 

Plus d’information dans le guide sur la configuration.

Cloud native

Pour finir, Quarkus propose un ensemble d’extension Cloud Native, pour interagir avec les principaux Cloud public et Kubernetes.

  • Serverless / Function as a Service (FaaS) : AWS lambda et Azure functions (Google Cloud Functions en préparation).
  • Client Kubernetes
  • Génération de ressource Kubernetes (dekorate)

Conclusion

Quarkus est depuis sa création un framework pensé pour les conteneurs et le déploiement vers le Cloud (et tout particulièrement Kubernetes). Il facilite grandement le déploiement de votre application comme un exécutable natif via son intégration poussée de GraalVM.

Via ses extensions Microprofile, vous aurez tout ce qu’il faut pour ajouter toutes les fonctionnalités Cloud Ready nécessaires à votre application. 

Et si vous voulez aller vers le Cloud natif, vous pouvez vous interfacer facilement avec les principaux Cloud Natif, et bien sûr, Kubernetes !
Pour aller plus loin, vous pouvez essayer de suivre le Dojo bookmarkit que j’ai développé pour mettre en pratique les fonctionnalités Cloud Ready de Quarkus.
Et si vous voulez directement la solution, elle est ici 😉


Le petit + :
La formation Développement d’applications Cloud Ready avec Quarkus


Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :