[Devoxx] Kubernetes et la JVM

Java sur K8s, c’est plein de préjugés : lent à démarrer, compliqué à exploiter, gourmand en ressources.

Pour commencer, on peut utiliser K8s sur le pote de dév avec des outils comme minikube, k3s, kind, ou flox (qui semble s’inspirer de nix, et permet de créer un environnement de développement reproductible).

Alain nous montre alors comment utiliser flox pour installer kind et démarrer un namespace. On peut même choisir une version spécifique de K8s.

Passons maintenant à la construction des images et aux bonnes pratiques :

  • faire de petites images
  • utiliser du multistage build
  • utiliser Kaniko ou Jib qui optimisent la construction des images


Dans Kubernetes, il ne faut pas oublier d’alimenter les probes, parce que K8s envoie du trafic dès que l’image est démarrée. Et il ne faut pas mettre le même endpoint dans la liveness et la readiness. Pensez aussi à déclarer des requests et des limits pour éviter que le pod soit tué trop facilement.

Dans la suite de la démo, ils vont utiliser une application Java qui se connecte à la Chuck Norris API. Une fois l’application écrite, on peut la déployer dans kind avec un kind deploy docker-image

Son application démarre … en 37 secondes avec 0.2 vCPU, mais instantanément avec 1 vCPU.

Il faut aussi penser à gérer le shutdown hook pour que l’application s’arrête correctement.

Passons à la JVM. Dans cet outil incroyable, deux éléments sont intéressants dans notre cas :

  • Le JIT qui améliore l’exécution, une fois que le démarrage est effectué, celui-ci pouvant être assez long. Une méthode est compilée nativement après 10.000 appels par un compilateur de niveau C1, et il sera par la suite optimisé encore plus.
  • La gestion de mémoire, et en particulier le garbage collector. La JVM en fournit plusieurs, qui évoluent en performance avec les versions de la JVM. On sélectionne rarement le GC, parce que la JVM le fait automatiquement. C’est fait selon trois critères : throughput, empreinte mémoire, et latence.

Depuis Java 5, les ergonomics permettent d’auto-configurer la JVM selon le nombre de CPU ou d’autres facteurs. On peut néanmoins forcer les valeurs de ces paramètres. On peut aussi les lister, soit par une commande spécifique, soit dans les logs. Dans les ergonomics, il y a aussi le choix de la taille mémoire. Les valeurs par défaut sont bonnes pour les postes de dev, moins pour les environnements conteneurisés. Et de la même manière, ces ergonomics déterminent automatiquement le garbage collector. Et les contraintes de mémoire font que dans les conteneurs, on se retrouve souvent avec SerialGC (qui arrête l’application de temps en temps).

Passons maintenant à l’exécution du code Java dans K8s. Avant Java 10, les ergonomics récupéraient les informations de CPU et de RAM du noeud, et pas celles définies dans le conteneur. Après Java 10, on prend les informations du conteneur. Mais on peut les changer avec XX:MaxRAMPercentage pour lequel la bonne pratique est de mettre 75%.

La JVM a besoin de CPU avec certains pics. Et c’est pour ça que les applications Java deviennent très lentes quand la mémoire est limitée.

Et c’est pire pour la mémoire : si il n’y a pas assez de mémoire, ça tue le conteneur. (OutOfMemoryError par Java ou OOMKill par Kubernetes). Et attention, parce que la JVM n’a pas de mémoire que dans la heap, il y aussi le metaspace, les threads, et la mémoire native. Ca complique bien sûr l’évaluation de la mémoire nécessaire pour faire tourner un conteneur.

On peut améliorer le temps de démarrage de l’application avec différentes techniques. CDS (class data sharing) permet d’accélérer les démarrages successifs des applications. Ca s’appuie sur l’ahead of time compiling, issu des travaux du projet Leyden. On peut aussi utiliser GraalVM qui compile le code Java en code natif. Attention, parce que ça double la charge de test.

Pour finir, Alain nous montre comment compiler une application Java en natif. Le processus n’est pas immédiat, puisque GraalVM vérifie beaucoup de choses.

Pour conclure, Java et K8s sont deux technologies complexes qui, lorsqu’elles sont utilisées ensemble, nécessitent pas mal d’attention, une bonne coordination, et une bonne collaboration.

#conférence #devoxx #java #kubernetes
kind