blog-image

Monitoring/Gestion des performances applicatives (APM) - [ Partie 1]

  • dHENRY
  • 13/12/2022
  • (Durée de lecture : 11 mn)

La mesure des performances d’une application peut-être obtenue de différentes manières. Le sysadmin va collecter des métriques sur l’utilisation CPU, la performance des requêtes SQL, etc…

On peut, en plus, implémenter un système au sein même de l’application pour permettre une meilleure expérience utilisateur. Par exemple, un utilisateur se plaint de l’affichage d’une page en particulier et le sysadmin considère les performances globales de son infrastructure correctes.

Mais… il y a un bien un réel problème lors de l’appel d’une API en particulier dans une page. Cette mesure va pouvoir être isolée au travers d’APM (Application Performance Monitoring). Le logiciel chargé de présenter les données télémétriques va permettre d ‘isoler cet évènement et de mieux orienter les développeurs sur la résolution du problème.

Il existe plusieurs solutions sur le marché : Opentelemetry, dynatrace, elastic APM, ZIPKIN, Jaeger…

Dans cet article, j’ai choisi d’utiliser la solution Elastic APM, qui est un produit commercial mais également disponible en version communautaire opensource. Pourquoi Elastic ? tout simplement parce que je l’utilise dans mon travail et que sa mise en place dans le cadre d’un POC est trivial.

Pour ce POC, pas de conteneurs, pas de MytinyDC, mais deux machines virtuelles (amd64) :

  • 1 serveur Elastic Search/Fleet
  • 1 Desktop de développement (Nodejs/Agent Elastic)

Spécifications

Concernant les machines virtuelles (vm), j’utilise VirtualBox et je ne rentre pas dans les détails (profusions d’articles sur internet).

Nos besoins :

  • Serveur ElasticSearch/Kibana/Fleet :

    • Système Debian 11
    • RAM 4Go
    • CPU 4
    • Disque dur 10Go (privilégier de l’espace pour /usr/share et /var/lib)
    • Adresse IP : 192.168.1.22
  • Une station de développement équipée d’un “Agent Elastic” qui émettra les données de performances d’une application Nodejs vers le serveur Elastic.

source : Documentation officielle Elastic

Elastic Search est une solution de stockage et d’indexation de contenu. Votre machine virtuelle est prête et connectée à Internet

Connecté “root” à la console du serveur, exécuter :

apt-get install apt-transport-https sudo
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
apt-get update && apt-get install elasticsearch

Restez devant l’écran, le processus affichera ce type de d’information :

--------------------------- Security autoconfiguration information ------------------------------

Authentication and authorization are enabled.
TLS for the transport and HTTP layers is enabled and configured.

The generated password for the elastic built-in superuser is : 9xfPytbxhPSS1Zxabgd1

[...]

Collecter ce mot de passe, puis continuez en exécutant :

systemctl daemon-reload
systemctl enable elasticsearch.service
systemctl start elasticsearch.service

Ne fermez pas cette console

Patientez un instant, effectuer un premier test de connexion au service ElasticSearch, en utilisant un navigateur et l’URL : (https) “https://[adresse IP du serveur Elastic]:9200” Accepter l’exception liée au certificat auto-signé, et saisir :

  • un compte utilisateur : “elastic”
  • un mot de passe : le mot de passe collecté ci-avant

Tout s’est bien passé, vous obtenez un contenu au format JSON, donnant le nom du cluster, sa version, etc…

Installation de Kibana

source : Documentation officielle Elastic

Kibana est un outil fourni dans la suite Elastic, permettant de visualiser le contenu des données stockées dans le service ElasticSearch.

Restez sur la console du serveur Elastic et exécuter :

apt-get install kibana
systemctl daemon-reload
systemctl enable kibana.service
systemctl start kibana.service
/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana

Ne fermez pas cette console et Collecter le token obtenu, patientez un instant, et effectuer un premier test de connexion au service Kibana, en utilisant un navigateur et l’url qui est : (http et non https) “http://[adresse IP du serveur Elastic]:5601”

  • Indiquez :
    • le compte utilisateur : “elastic”
    • le mot de passe : le mot de passe collecté lors de l’installation d’elasticsearch (pas le token précédent!!!)

Pour finaliser l’installation, le processus demande de saisir le token obtenu précédemment, validez et patienter jusqu’à la fin de l’installation.

Installation d’un agent Fleet

source : Documentation officielle Elastic

Le service Fleet permet de centraliser la gestion des différents agents pouvant être utilisés par la solution Elastic.

Connectez-vous a Kibana (vu précédemment) :

  • A partir du menu de gauche

  • Cliquez sur Management/Fleet (tout en bas)

  • Ajouter un Fleet Server

  • Saisir l’url : https://[adresse IP du serveur Elastic]:8220 Respectez bien le numéro de port
  • Cliquez sur le bouton “Generate Fleet Server policy”, patientez…

  • Récupérer la console déjà ouverte sur le serveur Elastic, et coller le code fourni par le processus, patientez jusqu’à obtenir le message “Elastic Agent has been successfully installed”
{"log.level":"info","@timestamp":"2022-12-13T06:26:53.516+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":403},"message":"Generating self-signed certificate for Fleet Server","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2022-12-13T06:27:00.383+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":773},"message":"Fleet Server - Running on policy with Fleet Server integration: fleet-server-policy; missing config fleet.agent.id (expected during bootstrap process)","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2022-12-13T06:27:00.893+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":471},"message":"Starting enrollment to URL: https://elastic:8220/","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2022-12-13T06:27:02.729+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":273},"message":"Successfully triggered restart on running Elastic Agent.","ecs.version":"1.6.0"}
Successfully enrolled the Elastic Agent.
Elastic Agent has been successfully installed.
  • L’interface Kibana affiche maintenant :

Dans le cas où cet élément reste en attente, à partir de la console du serveur Elastic, exécuter :

systemctl restart elastic-agent

et tout devrait rentrer dans l’ordre… Fermer la fenêtre, l’agent Fleet est maintenant dans un état “healthy” :

(**)elastic : nom hôte de mon serveur Elastic

  • Cliquer sur Data Streams, nous voyons que l’agent Fleet ne fait pas qu’être agent Fleet, mais remonte des métriques auprès du serveur Elastic

  • Des tableaux de bord, situés dans le menu “Observability”, permettent de visualiser ces données selon les modèles pré-définis.

Reinstallation de l’agent Fleet

Si vous devez réinstaller l’agent Fleet, à partir d’une console connectée “root” au serveur Elastic, exécuter :

systemctl stop elastic-agent
rm -rf /opt/Elastic/

et suivre à nouveau la procédure mentionnée ci-avant. Le processus indiquera qu’une configuration existe déjà, confirmer pour réinstaller en tapant sur “Entrée”

Continuing will re-install Elastic Agent over the current installation at /opt/Elastic/Agent. Do you want to continue? [Y/n]:

Installation d’un agent Elastic APM (Application Performance Monitoring)

source : Documentation officielle Elastic

Cet agent sera chargé de colleter les données de l’agent embarqué dans le code source de votre projet Nodejs (instrumentation du code).

En effet, les projets instrumentés (plusieurs langages supportés), n’envoient pas directement leur télémétrie au service Elastic Search, mais au travers d’un agent et ce pour divers raisons (je vous laisse comprendre et si besoin contacter l’équipe Elastic).

Cet agent va être installé au plus proche de l’exécution de notre programme de tests, à savoir dans notre cas, sur la machine virtuelle qui permet le développement du code source du projet Nodejs utilisé pour la démonstration. Si il s’agissait de conteneurs, nous utiliserions la méthode “sidecar”…

  • Connecté à Kibana, menu “Management/Integration”, cliquer sur “Elastic APM”

  • Cliquez sur le bouton “APM integration”

  • Cliquer sur le bouton “Add Elastic APM”

  • Cliquer sur le bouton en bas à droite “Save and continue”

  • Cliquer sur le bouton “Add Elastic Agent to your host”

  • Le processus affiche la commande à exécuter sur l’hôte souhaité

  • A partir de la machine virtuelle où est installé l’environnement de développement Nodejs, ouvrir une console connectée “root”, puis créer le fichier “install-agent.sh”, ajouter les commandes indiquées par “Kibana”. ATTENTION :
  • La commande est préfixée par “sudo”, oubliez ce terme dans le copier-coller si “sudo” n’est pas installé sur cet environnement
  • la commande “d’enrollement Fleet” utilise une connexion chiffrée avec un certificat auto-signé, vous devrez donc ajouter le terme “–insecure” à la commande “d’enrollment”

Exemple, pour un environnement sans “sudo” :

curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.5.3-linux-x86_64.tar.gz
tar xzvf elastic-agent-8.5.3-linux-x86_64.tar.gz
cd elastic-agent-8.5.3-linux-x86_64
./elastic-agent install --url=https://[IP serveur Elastic]:8220  --insecure --enrollment-token=[token]
  • Enregistrer, puis exécuter : bash install-agent.sh

  • Vous obtenez un résultat similaire :

Elastic Agent will be installed at /opt/Elastic/Agent and will run as a service. Do you want to continue? [Y/n]:Y
{"log.level":"warn","@timestamp":"2022-12-13T07:43:05.693+0100","log.logger":"tls","log.origin":{"file.name":"tlscommon/tls_config.go","file.line":104},"message":"SSL/TLS verifications disabled.","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2022-12-13T07:43:06.193+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":471},"message":"Starting enrollment to URL: https://fleet.mtdc:8220/","ecs.version":"1.6.0"}
{"log.level":"warn","@timestamp":"2022-12-13T07:43:06.429+0100","log.logger":"tls","log.origin":{"file.name":"tlscommon/tls_config.go","file.line":104},"message":"SSL/TLS verifications disabled.","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2022-12-13T07:43:07.268+0100","log.origin":{"file.name":"cmd/enroll_cmd.go","file.line":273},"message":"Successfully triggered restart on running Elastic Agent.","ecs.version":"1.6.0"}
Successfully enrolled the Elastic Agent.
Elastic Agent has been successfully installed.
  • Retourner sur l’interface Kibana, l’agent est bien intégré :

  • Fermer la fenêtre.

Reinstallation de l’agent APM

Si vous devez réinstaller l’agent APM sur la machine virtuelle qui sert au développement Nodejs, à partir d’une console connectée “root” à la machine virtuelle de développement, exécuter :

systemctl stop elastic-agent
rm -rf /opt/Elastic/

et suivre la procédure mentionnée ci-avant. Le processus indiquera qu’une configuration existe déjà, confirmer pour réinstaller en tapant sur “Entrée”

Continuing will re-install Elastic Agent over the current installation at /opt/Elastic/Agent. Do you want to continue? [Y/n]:

Installation RUM agent (Instrumentation du code)

source : Documentation officielle Elastic

Pour l’exemple, j’ai pris une de mes applications perso “Mytinydc-Music”. C’est un soft qui me sert à écouter les mp3 produits à partir de ma collection de CD (je la publierai bientôt). Je m’en sers de “lab” (OpenAPI 3.0, Nodejs + Reactjs + Redux + Websocket + Postgresql, Sonarqube, CI/CD drone, Docker, Kubernetes,…).

L’architecture de cette application est commune : un client React qui fonctionne dans un navigateur, faisant appel à l’API implémentée dans le service Nodejs.

  • Rendez-vous sur Kibana, menu “Observability/overview” et cliquer sur “Install RUM agent”

  • Cliquer sur “Elastic APM in Fleet”

  • Dans le bloc “APM Agents”, sélectionner “Node.js”

  • et suivre les instructions. A partir de la console de la machine virtuelle de développement, et dans le répertoire du code source de l’application (répertoire ou est situé le fichier “package.json”)

Exécuter :

# Ajout du module npm nécessaire
npm install elastic-apm-node --save
  • Dans le fichier “main” de l’application, de mon côté, le fichier app.js, j’ajoute au tout début de ce fichier les informations mentionnées comme indiqué dans la documentation qui précise d’ajouter le code le plus au dessus du reste, dans mon cas :
// Add this to the VERY top of the first file loaded in your app
const apm = require("elastic-apm-node").start({
  // Override the service name from package.json
  // Allowed characters: a-z, A-Z, 0-9, -, _, and space
  serviceName: "music-production-elastic-instrumentation",

  // Use if APM Server requires a secret token
  secretToken: "",

  // Set the custom APM Server URL (default: http://localhost:8200)
  serverUrl: "http://localhost:8200",

  // Set the service environment
  environment: "production",

  logLevel: "trace",
});

Je code en ES6, je remplace “var” par “const”, j’indique le nom de mon application dans l’attribut “serviceName” et ajoute l’attribut “logLevel” avec la valeur “trace” pour vérifier que les données sont bien envoyées à l’agent

  • Enregistrer les modification, et démarrer l’application, attendre quelques instants, la console de votre application affiche ce type de traces :
[...]
{"log.level":"trace","@timestamp":"2022-12-13T07:20:13.770Z","log":{"logger":"elastic-apm-node"},"event.module":"apmclient","reqId":"f6788f3e04ef78b94caa34d1c115700f","timeline":[[10005.126106,"completePart gzipStream",null],[10005.516083,"completePart intakeReq",null],[10008.999366,"completePart intakeRes",null]],"bytesWritten":604,"backoffDelayMs":0,"ecs":{"version":"1.6.0"},"message":"conclude intake request: success"}
[...]
  • Dans ce cas, l’agent RUM communique correctement avec l’agent APM.

  • Pour diminuer le niveau de traces de la console, passer la valeur de “logLevel” à “info”, redémarrer l’application.

  • Sur l’interface Kibana, cliquer sur le bouton “Check agent status”, pour vérifier que l’agent RUM fonctionne correctement

  • Maintenant, utiliser l’application quelques minutes pour générer des données télémétriques…

Visualisation des données

source : Documentation officielle Elastic

  • A partir de l’interface Kibana, menu “Observability/Services”, nous voyons apparaître “music-production-elastic-instrumentation”, qui correspond à la valeur de l’attribut “serviceName” indiqué en paramétrage de l’agent RUM dans mon application, cliquer sur ce lien :

  • Kibana affiche plusieurs informations

  • Intéressons-nous à la partie “Transactions”, qui représente l’utilisation de l’API applicative.

  • Nous voyons plusieurs valeurs dont une paraît au dessus des autres. Ceci peut être la cause du problème remonté par l’utilisateur !!!

  • Cliquer sur le lien de cette transaction pour obtenir plus de détails

  • La timeline indique que la transaction Postgresql représente la plus grande part de la durée de cette dernière. Rendez-vous dans le code source de l’application pour consulter la requête SQL exécutée, s’il s’agit d’un réel problème alors il est localisé. Il pourrait aussi s’agir d’un nombre d’appels concurrents trop élevé, l’outil permet de filtrer très finement sur une période de temps donnée…

Instrumentation OpenTelemetry

Le cas présenté utilise l’instrumentation Nodejs fournie par ElasticSearch, mais vous pouvez instrumentrer avec le standard “Opentelemetry”.

Code NodeJS

Procédons à l’installation des paquets NPM nécessaires, rappel : j’utilise le framework express, et une base de données postgresql. La registry opentelemetry est ici

npm install @opentelemetry/sdk-trace-node @opentelemetry/instrumentation @opentelemetry/instrumentation-express @opentelemetry/instrumentation-pg --save

Instrumentons le code (IMPORTANT : juste avant la déclaration de “const apm = ….;”, il faut démarrer la télémétrie OTEL avant de démarrer l’apm elastic, sinon le bridge Opentelemetry ne sera pas assuré):

const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const {
  ExpressInstrumentation,
} = require("@opentelemetry/instrumentation-express");
const { PgInstrumentation } = require("@opentelemetry/instrumentation-pg");

// Register instrumentation
const provider = new NodeTracerProvider();
provider.register();
registerInstrumentations({
  instrumentations: [
    // http instrumentation
    new HttpInstrumentation(),
    // Express framework instrumentation
    new ExpressInstrumentation(),
    // Postgresql Database instrumentation
    new PgInstrumentation(),
  ],
});
const apm = require("elastic-apm-node").start({
[...]

Modifions le paramétrage de l’agent Elastic, en ajoutant, dans le bloc de déclaration “const apm=”, par exemple, juste après l’attribut “serviceName”, la constante opentelemetryBridgeEnabled: true, comme suit :

// Add this to the VERY top of the first file loaded in your app
const apm = require("elastic-apm-node").start({
  // Override the service name from package.json
  // Allowed characters: a-z, A-Z, 0-9, -, _, and space
  serviceName: "music-production-elastic-instrumentation",
  opentelemetryBridgeEnabled: true,
  [...]

Redémarrer l’application. Utilisez votre API et rendez-vous sur l’interface Kibana.

La télémétrie est maintenant fournie par la librairie “Opentelemetry” et permet d’obtenir un niveau de détail plus élevé qu’avec l’instrumentation Elastic.

Pour la même API “/api/mostpopulartracks” :

  • Instrumentation ElasticSearch :

  • Instrumentation Opentelemetry

Dans ce cas, on peut ainsi voir que mon code réalise deux connexions à la base de données. Le driver permet de gérer un pool de connexion, pour n’effectuer qu’une seule connexion et la maintenir, ceci ferait au moins gagner les 28ms de la seconde connexion…

Conclusion

Il s’agit là d’un bref aperçu des capacités d’une telle solution, mais vous pouvez constater qu’il est aujourd’hui très facile de localiser rapidement le ou les goulets d’étranglement d’une application.

L’outil présenté dispose d’autres capacités, notamment de superposer les logs applicatifs au traces, de paramétrer des alertes… Je vous laisse à votre imagination et n’oubliez pas d’explorer d’autres solutions…

Pour éviter d’installer une telle infrastructure et de disposer d’un environnement de développement plus simple, l’utilisation de Jaeger est tout à fait indiqué. Jaeger, c’est rapide, facile à installer et ne demande pas de gérer des comptes d’accès.

Ce sera l’objet du prochain article…

+++

(*) Image Rendering Powered by