Profiler une application Java dans un conteneur déployé dans kubernetes avec JFR – Java Flight Recorder
La plupart des clients chez lesquels j’interviens aujourd’hui utilisent Kubernetes pour déployer leurs applications.
Bien que Kubernetes soit un super outil, si on a besoin de ce type d’outil (qui est une discussion dans laquelle je ne rentrerais pas ici), il peut apporter une certaine complexité quant aux moyens de « regarder » le comportement de nos applications en cours de fonctionnement.
Quand j’ai un problème de performance sur une application, j’utilise régulièrement un profiler Java. Il en existe de nombreux, mais tous nécessitent de pouvoir se connecter à l’application d’une certaine manière (port distant ou local, agent Java, …).
Prenons comme exemple une application traitant des messages Kafka et utilisant une image de base openjdk-15. Cette application n’ayant pas besoin d’accès distant, elle n’exposera aucun port via Kubernetes. La seule manière de s’y connecter sera donc en se connectant au conteneur.
Voici une commande pour ouvrir un shell sur un Pod avec un seul conteneur (si plusieurs conteneurs, il faut ajouter le nom du conteneur sur lequel vous voulez vous connecter) :
kubectl exec -ti name-of-my-pod -- sh
Une fois connecté dans le conteneur, quelques constatations :
- L’image openjdk ne me permet pas de lister les process via ps ni jps.
- L’image openjdk ne contient ni curl ni wget pour télécharger un outil quelconque.
- Mon conteneur ne peut pas installer de nouveaux packages : pour des raisons de sécurité évidente, il tourne avec un utilisateur avec des droits limités.
Donc, ce n’est pas possible de télécharger AsynchProfiler, par exemple, pour profiler mon application.
Ne pouvant même pas lister les process, je ne peux pas utiliser un des outils fournis par la JVM non plus.
Mais attendez ! En fait, pas besoin de lister les process !
Dans un conteneur, il n’y a généralement qu’un process, je peux donc accéder au process de ma JVM via le pid 1 ! (Ce n’est pas toujours vrai, si votre conteneur a redémarré, s’il y a plusieurs conteneurs sur votre pod ou des conteneurs d’init, il se peut que le pid soit plus élevé, mais il est facilement trouvable en testant 2, 3, 4, …).
Vous pourrez donc utiliser jinfo, par exemple, pour récupérer les informations sur votre JVM : jinfo 1
Même si vous ne pouvez rien installer dans votre conteneur ni télécharger d’outils, vous pouvez toujours utiliser ce que vous fournit la JVM, et c’est là qu’entre en jeux JFR – Java Flight Recorder.
JFR est un outil intégré à la JVM qui permet de monitorer une JVM avec un impact minimal sur les performances de celle-ci. Il monitore une JVM via un ensemble d’événements qui sont enregistrés pour être ensuite analysés via JMC – Java Mission Control.
JFR peut être lancé de deux manières : via la ligne de commande de la JVM, ou via jcmd. Comme nous ne voulons pas modifier la ligne de commande de la JVM, surtout que cela nécessiterait un redémarrage de celle-ci, on va utiliser jcmd pour démarrer un enregistrement.
jcmd 1 JFR.start settings=profile
Ici on utilise l’option settings=profile
pour démarrer JFR en mode profiling, ce qui active plus d’événements que le mode par défaut. Il est possible de spécifier un fichier XML de settings qui contient la liste exacte des événements JFR à activer pour plus de flexibilité.
La sortie de la commande est la suivante :
Started recording 1. No limit specified, using maxsize=250MB as default. Use jcmd 1 JFR.dump name=1 filename=FILEPATH to copy recording data to file.
Elle nous indique comment arrêter la session d’enregistrement.
Au bout de quelques temps, vous pouvez arrêter la session via :
jcmd 1 JFR.dump name=1 filename=/tmp/profile.jfr
Ensuite, pour pouvoir exploiter votre session JFR dans JMC, vous devez sortir du conteneur puis récupérer le fichier de profil sur votre poste local. Ceci se fait via la commande kubectl :
kubectl cp name-of-my-pod:/tmp/profile.jfr profile.jfr
En utilisant les outils à disposition dans toute installation de Kubernetes et de la JVM, on peut facilement déclencher une session d’enregistrement JFR sur une application en cours d’exécution, récupérer celle-ci localement puis l’analyser. Cela nous permet de monitorer une application en cours d’exécution dans un conteneur déployé dans Kubernetes sans installation d’outils supplémentaires et donc sans mettre en danger la sécurité de nos conteneurs.