J’ai réussi à faire fonctionner OpenTelemetry, mais pourquoi est-ce si complexe ?
(iconsolutions.com)- La demande d’un client pour une prise en charge d’OpenTelemetry a mis en évidence que l’observabilité existante, fondée sur Prometheus, Jaeger et OpenTracing, ne correspondait pas exactement aux exigences d’OTel en matière de métriques, logs et traces
- Les logs et les métriques ont été migrés assez facilement en ajoutant des appenders et de la configuration, mais le tracing distribué s’est avéré bien plus difficile, car Spring et Akka devaient partager le même contexte de trace dans une même JVM
- OTel et Lightbend Telemetry/OpenTracing utilisent des API de Tracer différentes ainsi que des implémentations distinctes de
SpanContext, ce qui a fragmenté une même transaction en traces séparées - La solution a consisté à injecter le contexte OTel dans une
MapJava, à l’extraire en tant queSpanContextJaeger, puis à l’activer manuellement avecGlobalExtendedTracer.get().local().activateContext()de Lightbend - OTel aide à standardiser l’observabilité, mais lorsqu’on le mélange avec une instrumentation existante basée sur Akka et Jaeger, un raccordement explicite du contexte peut être nécessaire
Les signaux d’observabilité qu’OTel cherche à unifier
- OpenTelemetry (OTel) est à la fois un framework et une boîte à outils d’observabilité, qui vise à standardiser en trois signaux — métriques, logs et traces — des domaines auparavant couverts par des outils séparés
- La combinaison existante était répartie par rôle
- Prometheus : métriques
- Des agrégateurs centralisés comme Logstash et Elasticsearch : logs
- OpenTracing : tracing distribué
- OTel fournit non seulement des spécifications, mais aussi des composants clés
- OpenTelemetry Protocol (OTLP) : protocole par lequel les applications rapportent les données de telemetry
- OpenTelemetry Collector : composant neutre vis-à-vis des fournisseurs pour recevoir, traiter et exporter les données de telemetry
- Des SDK pour plus de 10 langages : implémentations d’OTLP et de l’export de telemetry
- Les applications peuvent envoyer leurs signaux vers l’OTel Collector via l’instrumentation automatique, les API et les SDK de langage, et l’infrastructure peut elle aussi envoyer des signaux au Collector
Le support existant d’IPF et la demande des clients
- IPF disposait déjà d’une intégration avec l’ELK Stack, de fonctions de monitoring et d’une documentation sur l’observabilité, que les clients utilisaient déjà en production
- En raison de la nature du framework, il était impossible de savoir dans quel environnement seraient déployées les applications utilisant la bibliothèque ; il fallait donc éviter d’imposer un fournisseur spécifique et proposer des abstractions avec des valeurs par défaut remplaçables
- La recommandation par défaut consistait à envoyer les logs vers des agrégateurs comme Elasticsearch ou LogScale, et à activer et prendre en charge Prometheus, devenu de fait le standard pour les métriques
- Les clients ont demandé presque simultanément trois fois un tracing basé sur OTel, et le fait d’adopter OTel pour le tracing a entraîné une migration conjointe des logs et des métriques
- Les logs et les métriques ont été traités assez facilement avec de nouveaux appenders et de la configuration, mais le tracing est resté un problème à part
Pourquoi la propagation du contexte est nécessaire en tracing distribué
- Pour suivre une transaction unique dans un système distribué, il faut transmettre entre les systèmes des informations qui relient des appels précis à cette transaction ; on appelle cela la propagation de contexte
- Une trace est une structure parente qui englobe plusieurs unités de travail appelées spans ; il peut y avoir plusieurs spans, potentiellement imbriqués
- Sur un site e-commerce, le parcours déclenché par un clic sur le bouton « buy now » peut être regroupé dans une seule trace
- un span où le frontend communique avec le backend
- des spans enfants où le backend communique avec les services de paiement, d’expédition et de gestion des commandes
- des spans enfants supplémentaires lorsque chaque service aval communique à son tour avec d’autres systèmes
- Avec OTel, chaque service distribué rapporte sa propre partie de la trace à l’OTel Collector, et OTel reconstitue l’ensemble du flux sous un identifiant de trace unique
Quatre standards de propagation de contexte et l’héritage d’OpenTracing
- OTel doit impérativement prendre en charge quatre implémentations de propagation de contexte
- La partie Akka d’IPF prenait déjà en charge OpenTracing, désormais en voie d’abandon, et utilisait le support OpenTracing de Lightbend Telemetry
- Lightbend Telemetry 2.20.0 a ajouté la prise en charge des logs/events et des métriques OpenTelemetry, mais il manquait l’élément essentiel : le support du tracing
- En théorie, le tracing OTel ressemble à un rebranding d’OpenTracing, et l’OpenTracing de Lightbend Telemetry prend lui aussi en charge les quatre modes de propagation, ce qui laissait penser que tout fonctionnerait ; en pratique, le résultat a été tout autre
Une trace interrompue entre Spring et Akka
- IPF utilise à la fois Spring et Akka
- Spring Boot et Spring IoC : bootstrap de l’application, configuration et assemblage des dépendances
- Akka : event sourcing, planification, clustering, sharding, intégration, etc.
- Le client peut démarrer un flux de paiement depuis un contrôleur REST Spring ou une méthode annotée
@KafkaListener - Avant OTel, les trois domaines fonctionnaient sans conflit majeur
- Metrics : Spring et Akka exposaient des endpoints Prometheus distincts
- Logs : les deux frameworks utilisaient SLF4J et Logback
- Traces : les deux utilisaient OpenTracing
- Avec OTel, les métriques et les logs étaient correctement transmis, car Spring et Akka fonctionnaient indépendamment, mais le tracing exigeait qu’ils partagent le même contexte de trace à l’intérieur de la JVM
- Le vrai problème était qu’au sein de l’application, un même concept de suivi était représenté par deux API différentes, qui ne communiquaient pas entre elles
- Le comportement attendu était que le client HTTP Akka réutilise l’identifiant de trace existant
123et ne crée qu’un nouvel identifiant de span, afin de représenter une nouvelle unité de travail dans la même trace ; en réalité, deux traces sans rapport étaient générées
opentracing-shim et conflit d’implémentations
- Côté OTel, il existe un
opentracing-shimqui fait apparaître leTracerOTel comme unTracerOpenTracing - Mais Lightbend Telemetry utilisait une implémentation personnalisée de Tracer, ce qui a provoqué l’échec du shim comme de Jaeger
- Les logs affichaient les erreurs suivantes
Expected to have an OpenTelemetry Span but got cinnamon.opentracing.TraceLocal$ContextOnlySpanExpected to have a JaegerSpanContext but got io.opentelemetry.opentracingshim.SpanContextShim
- Jaeger échouait en recevant un contexte du shim OTel, et OTel échouait en recevant un contexte Lightbend
Analyse du Java Agent et du tracing interne de Lightbend
- L’instrumentation d’OTel comme celle de Lightbend Telemetry utilisent toutes deux un Java Agent pour accrocher des hooks à certains appels de méthodes de classes spécifiques et rapporter l’activité au tracer
- L’instrumentation de Lightbend Telemetry n’étant pas open source, il a fallu examiner le code décompilé du tracer pour comprendre son fonctionnement
- Un exemple minimal de reproduction a été créé afin d’identifier l’endroit précis où le contexte de trace OTel côté Spring cessait d’être transmis vers Akka
- Le point d’échec côté Jaeger se trouvait dans ce code de
JaegerTracer.java - L’appelant était la classe d’instrumentation propriétaire de Lightbend Telemetry
OpenTracingAkkaPersistenceActorInstrumentation.class - Dans le code décompilé, la condition clé dépendait de l’état de
var5- si
var5, unSpanContextOpenTracing, n’était pas null, le nouveau span était rattaché comme enfant du span actif courant - si
var5était null, le span devenait sans relation avec la trace existante - si
var5n’était pas unJaegerSpanContext, Jaeger échouait
- si
Relier OTel et OpenTracing via une conversion manuelle
- Le
SpanContextétait récupéré viathis.traceLocal.currentContext(), ettraceLocalétait initialisé depuisExtendedTracerde Lightbend - La documentation Lightbend montrait comment accéder globalement à
ExtendedTracerGlobalExtendedTracer.get()
GlobalExtendedTracer.get()renvoie unExtendedTracer, etlocal()renvoie le mêmeTraceLocalque celui utilisé par la bibliothèque d’instrumentation- Ce
TraceLocalexpose une méthodeactivateContextqui accepte unSpanContextOpenTracing - La solution s’est déroulée en quatre étapes
- ne pas utiliser le shim OTel
- injecter le contexte OpenTelemetry courant dans une
MapJava - extraire un
JaegerSpanContextà partir des valeurs de cetteMap - activer ce
JaegerSpanContextdans leTraceLocalde Lightbend avant d’entrer dans Akka
- Les opérations de l’API OTel propagators utilisées étaient les opérations d’injection et d’extraction
- Le code essentiel suivait ce flux
- créer une
HashMapvide - injecter le contexte OTel dans cette map avec
GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().inject(...) - créer un
JaegerSpanContextavecnew TextMapCodec(false).extract(new TextMapAdapter(otelContext)) - activer le contexte avant d’entrer dans Akka avec
GlobalExtendedTracer.get().local().activateContext(openTracingContext)
- créer une
Comportement validé et recommandations en production
- Après ce raccord manuel, la trace se poursuivait comme attendu
- à partir de l’appel REST Spring
/submit, l’ensemble du flux était relié dans une trace unique - il était possible de mélanger des parties instrumentées via l’API OTel et d’autres via l’API OpenTracing
- la trace se propageait au-delà des frontières HTTP
- à partir de l’appel REST Spring
- Dans l’exemple, des noms différents étaient utilisés pour distinguer les deux types d’instrumentation, mais en environnement client réel il est recommandé d’aligner
otel.service.nameetcinnamon.applicationafin que l’ensemble apparaisse comme une seule application vue de l’extérieur
Pourquoi cela a été si complexe, et les inquiétudes qui restent
- Réécrire Lightbend Telemetry sur l’API OTel impliquerait probablement de porter vers l’API OTel une grande quantité d’instrumentation actuellement liée à l’API Jaeger, ce qui représenterait un travail important
- OTel Collector prend en charge la collecte de traces au format Zipkin legacy, ce qui permet à Lightbend de s’appuyer sur ce support legacy pour proposer un support OTel complet
- La cause directe de cette complexité venait de la tentative de combiner deux systèmes d’instrumentation utilisant des bibliothèques de tracing différentes
- Le projet OTel cherche à remettre de l’ordre dans le domaine de l’observabilité avec des efforts de standardisation comme les semantic conventions ; même si l’ensemble paraît un peu complexe au premier abord, il est considéré comme un projet FOSS utile
- Il subsiste toutefois une inquiétude : Akka transmet-il correctement le contexte de trace entre threads à l’intérieur du modèle actor ?
- lors de petits tests de charge, cela a fonctionné comme attendu
- un ticket a été ouvert côté Akka à ce sujet
1 commentaires
Avis de Hacker News
Pendant tout le temps où j’ai appris et porté Otel, j’ai eu l’impression de revenir dans le monde Java. À chaque ligne de code que je suivais, ça ressemblait à EnterpriseFizzBuzz, il n’y avait aucune découvrabilité, et sa terminologie semblait avoir été inventée par des gens sous substance
Dans NodeJS, l’utilisation CPU était environ 4 fois supérieure à StatsD, et j’ai fini par créer ma propre agrégation pour réduire l’usage et limiter aussi l’explosion des tags. StatsD ne pose pas de problème si plusieurs processus rapportent les mêmes tags, mais OTEL les écrase
En charge de pointe, un CPU tournait à 60–80 % d’utilisation, et avant que quelque chose ne change, on ne pouvait même pas scaler verticalement. Pour les langages qui utilisent un processus par cœur, OTEL paraît activement hostile, au point que ça ressemble à une blague. Mieux vaut simplement utiliser Prometheus, et il n’y a en fait aucun autre concurrent
En essayant de faire une configuration otel simple en .NET, j’ai passé des heures à lire la documentation du fournisseur choisi par mon organisation sans comprendre, puis je suis allé sur le Discord géré par un collègue. Une partie de leur modèle économique consistait à dire « payez pour avoir un otel correct dans un produit open source », et j’ai immédiatement eu le sentiment que ça en valait le coût, quel qu’il soit
Plutôt que d’implémenter OTEL, je préférerais encore créer une bibliothèque événementielle/pub-sub stable de plus, sans expérience préalable
La configuration minimale est réellement assez réduite : une UI web, une instance serveur, et une base de données que l’on connaît déjà suffisent : https://skywalking.apache.org/docs/main/latest/en/setup/back...
Il a des points communs avec Zabbix dans le domaine du monitoring. Aucun des deux ne va impressionner grand monde, mais ils sont suffisamment pratiques
process.vpid. On peut le coordonner avec quelque chose comme un objet global afin que sa valeur soit unique pendant la durée de vie de l’appEnsuite, il suffit d’avoir quelque chose qui additionne le tout et supprime cet attribut. Avec statsd/delta, si l’on perd une transmission de signal, toutes les données sont faussées ; avec une approche cumulative, on ne perd que de la précision
Mon cas d’usage, ce sont des métriques push venant d’outils « batch », pas de processus longue durée scrapables
Otel paraît complexe parce que plusieurs fournisseurs d’observabilité ont rendu l’implémentation de l’observabilité très facile avec leurs SDK, agents et API propriétaires. Otel cherche à résoudre ce problème, et je trouve que ses créateurs s’en sortent très bien
Le fait que Grafana ait adopté OpenTelemetry comme composant de premier ordre de l’écosystème mérite aussi d’être salué
Pendant des années, j’ai poussé l’usage de Datadog, mais pour une entreprise entre taille intermédiaire et grand groupe, le prix est devenu difficile à supporter. Avec le temps, à mesure que les API et SDK OpenTelemetry se sont stabilisés, nous en avons fait le standard pour l’observabilité applicative
Cela dit, la documentation pourrait globalement être meilleure, et le fait que les documents d’onboarding diffèrent selon les langages n’est pas idéal
Notre équipe utilise actuellement une stack NodeJS/Typescript, et nous avons créé un ensemble de packages ainsi qu’une stack Grafana d’exemple pour démarrer rapidement avec OpenTelemetry : https://github.com/zonneplan/open-telemetry-js
Le plus gros problème avec tous les fournisseurs d’APM, c’est qu’au moment où un agent magique commence à disposer de hooks noyau, toutes sortes de choses que les développeurs ne peuvent pas expliquer se produisent
Dans une ancienne entreprise, nous avions déployé Dynatrace. Heureusement, l’application avait déjà suffisamment de métriques intégrées pour que le lead SRE la considère comme un « modèle » d’instrumentation. Mais dès l’installation de l’agent Dynatrace sur les hôtes applicatifs, plusieurs heisenbugs nécessitant un redémarrage des nœuds sont apparus, et une dégradation des performances a aussi été mesurée directement
Ironiquement, les métriques nous ont évité de souffrir davantage, mais personne ne savait comment corriger le problème. Le pire cas était celui où, lors d’une mise à jour de MSSQL, un basculement contaminait étrangement le pool de connexions ADO.NET
Une architecture qui ne devient complexe que dans la mesure nécessaire. On peut éviter les fonctionnalités magiques et n’utiliser que le sous-ensemble qui apporte le plus de valeur dans le contexte tout en restant facile à comprendre
Dans notre équipe, c’est très simple. Nous utilisons une bibliothèque qui n’envoie que des traces : les traces apportent le plus de valeur pour l’observabilité de l’application et peuvent aussi contenir d’autres types de données. En gros, c’est comme utiliser une hashmap au lieu de chaînes et de nombres à virgule flottante
Nous faisons de l’instrumentation manuelle plutôt qu’automatique, nous décidons volontairement ce que nous voulons observer et nous comprenons bien quel code émet des spans. Nous avons aussi des conventions de nommage adaptées à la structure du code
Côté backend, nous combinons un service tiers peu coûteux avec une installation Jaeger all-in-one pour le développement local. Cette dernière ne nécessite qu’un exécutable ou un conteneur Docker, et n’enregistre pas les spans sur disque. Elle sert surtout à rassurer les membres de l’équipe pour qu’ils n’aient pas peur de saturer le service tiers
Pour la supervision de l’infrastructure, nous avons déjà une configuration existante et, dans notre cas, nous ne voyons pas beaucoup de valeur à collecter tous les logs et métriques d’infrastructure. Je considère que les métriques et logs OTEL en sont encore à leurs débuts, même si les fournisseurs ne le disent pas ainsi
Je cherche encore un endpoint pour envoyer de simples métriques ponctuelles depuis certaines parties de l’infrastructure qui ne peuvent pas encore être scrapées
Otel peut convenir pour un nouveau projet, mais l’activer sur un service en production qui a déjà de la télémétrie donnait l’impression de changer les pneus d’une voiture en marche
Je veux bien croire que ça puisse être bien pour un projet greenfield qui n’utilise qu’otel et aucun framework non-otel. Mais je ne vis pas encore dans ce monde-là
L’un des plus gros problèmes a été l’expérience de développement local. Je voulais prendre en charge les logs, traces et métriques en local, mais sans lancer une pile d’images Docker pour ça. Avant le déploiement, je voulais pouvoir voir dans les logs à quoi ressemblaient les métriques, traces, baggage et activity spans
Récemment, l’équipe .NET a sorti .NET Aspire, et c’est très bien. Dans une stack de développement local, il est facile de tout visualiser au même endroit, et cela fonctionne comme un orchestrateur défini en code
Lors du déploiement sur k8s, il suffit de pointer l’endpoint OTEL vers le DataDog Agent et tout fonctionne. J’évite les bibliothèques de traces personnalisées et les SDK de DataDog, et je n’utilise qu’OTEL
L’expérience de développement est maintenant plutôt bonne
https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals...
https://docs.datadoghq.com/opentelemetry/#overview
La configuration initiale sur une machine de développement locale prend 5 minutes, puis il suffit de lancer
/path/to/openobservedans un onglet de terminal séparé. Si l’on veut éviter l’énorme complexité d’un binaire unique lié statiquement, ils fournissent aussi une image Docker pour l’exécution locale ou distante :PC’est un backend OpenTelemetry tout-en-un avec de jolis graphiques, et dans mes projets il n’a encore jamais échoué de manière détectable
Avec Aspire, on ajoute une complexité inutile au niveau de l’application et on s’enferme dans un écosystème étroit. Pour le développement local, il existe beaucoup d’alternatives éprouvées comme docker compose, et Aspire n’est pas vraiment beaucoup plus simple que docker compose et des variables d’environnement
Si vous faites de l’otel en Python, mieux vaut utiliser le client de Logfire. Même si vous n’utilisez pas le service Logfire
Il est open source et peut envoyer vers n’importe quel endpoint compatible otel. En plus, le client créé par l’équipe pydantic est 10 fois meilleur et plus simple que les bibliothèques otel officielles
Il y a aussi une interview intéressante où Samuel Colvin explique comment ils en sont arrivés là : https://www.bitecode.dev/p/samuel-colvin-on-logfire-mixing-p...
J’ai donc lancé un projet open source visant à rendre l’adoption d’OpenTelemetry aussi simple qu’une seule ligne de commande : https://github.com/odigos-io/odigos
De nos jours, beaucoup de frameworks web prennent en charge la majeure partie de l’instrumentation. Par exemple, si vous utilisez opentelemetry-js et auto-hébergez quelque chose comme https://signoz.io, vous pouvez le lancer en moins d’une heure, et obtenir beaucoup de données sans écrire de code personnalisé
OpenTelemetry s’est développé à partir des traces, mais il vaut bien mieux confier les métriques et les logs à des solutions spécialisées
Ça ressemble à un problème d’« abstraction qui fuit » ou de « framework qui fuit ». Si l’on veut vraiment tout mettre sous le même parapluie, une base de données SQL peut aussi faire tout cela à la fois. Cela ne veut pas dire qu’il faut le faire
https://cra.mr/the-problem-with-otel/
Malgré tout, chaque fois que je manipule OTel, je continue à détester ça
Si on lit jusqu’au bout, on se rend compte que la douleur est en grande partie auto-infligée. Avec la stack standard de Python (mysql, flask, redis, requests, etc.), c’était très facile. Il suffisait d’ajouter quelques imports en haut du service, et tout se connectait automatiquement pour tracer l’ensemble sans trop de bruit
aiohttpen est à la version 3.11.X, et l’instrumentation automatique indique prendre en charge3.X[0], mais le résultat réel dépend de la fraîcheur de votre version deaiohttpet de l’état côté instrumentation automatiqueQuand tout s’aligne, ça marche comme par magie, mais le chas de l’aiguille est assez étroit
[0]: https://github.com/open-telemetry/opentelemetry-python-contr...
Les traces fonctionnent, j’ai aussi configuré le spanmetrics exporter, et si j’interroge directement prometheus, je vois les spanmetrics. Mais quoi que je fasse, elles n’apparaissent pas dans l’onglet « monitor » de jaeger
Après y avoir passé 3 jours, mon chef m’a dit : « Et si on faisait juste de l’instrumentation manuelle, qu’on envoyait tout vers un serveur SQL et qu’on créait un dashboard Grafana ? », mais je n’ai pas envie de faire ça non plus
Même le cas d’usage le plus simple ne marche pas. Est-ce qu’il faut encore ajouter Grafana par-dessus ?
Si c’est compliqué, c’est parce que ça a été conçu non pas pour les ingénieurs qui l’implémentent, mais pour les entreprises qui vendent des logiciels compatibles Otel
Au contraire, j’ai plutôt l’impression que les backends ont été assez lents à l’adopter
Les sens évoluent, donc ce n’est pas grave, mais je trouve amusant qu’il ne reste désormais plus de mot pour désigner le fait d’écrire un logiciel conforme à une spécification, et seulement celui de « déployer un logiciel existant sur un serveur »