Aller au contenu

TechniqueDécouverte

OpenTelemetry de bout en bout

Pour : ingénieurs et SRE · architectes · managers d’équipePrérequis : Avoir lu L'observabilité en 10 minutes ou savoir ce que sont une métrique, un log et une trace.

Mode de lecture

L’observabilité en 10 minutes a présenté les signaux sur un exemple : un paiement trop lent. Cette page montre comment ces signaux sont produits et transportés avec OpenTelemetry. On suit un seul span, celui de l’appel à la banque, de l’application jusqu’au stockage.

OpenTelemetry (souvent abrégé OTel) est un projet open source de la CNCF, la fondation qui héberge aussi Kubernetes et Prometheus. Il est né de la fusion de deux projets antérieurs, OpenTracing et OpenCensus (présentation officielle).

OpenTelemetry n’est pas un outil de stockage ni de visualisation. Il produit et transporte la télémétrie. Le stockage reste au choix : Prometheus, Thanos, Mimir ou VictoriaMetrics pour les métriques, Tempo ou Jaeger pour les traces, Loki ou Elasticsearch pour les logs, ou une solution commerciale.

Le projet regroupe six briques :

BriqueRôle, en une phrase
Spécificationle texte qui dit comment tout doit fonctionner, langage par langage
APIles fonctions que le code appelle pour créer un span ou une métrique
SDKla bibliothèque qui implémente l’API : elle échantillonne, regroupe et exporte
OTLPle protocole de transport commun à tous les signaux
Collectorun programme autonome qui reçoit, transforme et réexpédie la télémétrie
Conventions sémantiquesla liste des noms d’attributs standard et de leur signification

Voici le chemin complet. Chaque étape est détaillée ensuite.

flowchart LR
  A["Application<br/>service payment"] -->|"API"| S["SDK<br/>échantillonnage, lots"]
  S -->|"OTLP"| R
  subgraph COL["Collector"]
    R["Receiver<br/>otlp"] --> P["Processors<br/>memory_limiter, batch"] --> E["Exporters"]
  end
  E -->|"OTLP"| B[("Stockage<br/>des traces")]
  E --> D["debug<br/>console"]

Instrumenter, c’est ajouter au programme le code qui produit la télémétrie. Il existe deux façons de le faire.

L’instrumentation automatique (zero-code) ne demande aucune modification du code. Un agent ou un lanceur intercepte les bibliothèques connues : serveur web, client HTTP, pilote de base de données. Il crée les spans tout seul. C’est la meilleure façon de commencer.

L’instrumentation manuelle consiste à appeler l’API dans son propre code. Elle sert à ajouter ce que l’automatique ne peut pas deviner : une étape métier, un attribut comme le type de carte.

En pratique, on combine les deux. L’automatique donne le squelette de la trace. Le manuel ajoute le sens.

Le SDK reçoit les spans terminés. Il décide s’il les garde, c’est l’échantillonnage (sampling). Il les regroupe par lots pour limiter le nombre d’envois. Il ajoute les attributs de ressource. Puis il les confie à un exportateur.

OTLP (OpenTelemetry Protocol) est le format de transport. Il fonctionne sur gRPC (port 4317 par défaut) ou sur HTTP (port 4318) (configuration du Collector). Le même protocole transporte métriques, logs et traces.

Le Collector est un programme qui se place entre les applications et les stockages. Il a trois types de composants.

  • Les receivers reçoivent les données. Le receiver otlp écoute sur les ports 4317 et 4318.
  • Les processors transforment les données, dans l’ordre où ils sont listés. memory_limiter refuse des données quand la mémoire approche de sa limite, pour éviter que le Collector tombe. batch regroupe les données avant l’envoi.
  • Les exporters envoient les données vers un ou plusieurs stockages.

Un pipeline relie des receivers, des processors et des exporters pour un type de signal. On écrit un pipeline pour les traces, un autre pour les métriques.

Le Collector n’est pas obligatoire : un SDK peut exporter directement vers un stockage. Je recommande pourtant de l’utiliser dès le départ. C’est l’endroit unique où l’on protège, filtre et oriente les données, sans toucher aux applications.

Le stockage indexe les spans par identifiant de trace. L’interface reconstruit la cascade vue dans la page précédente. L’explorateur de traces montre à quoi ressemble ce résultat.

L’exemple suivant instrumente un service Python et fait tourner un Collector qui affiche les données reçues dans sa console.

Instrumenter un service Python sans toucher au code

Section intitulée « Instrumenter un service Python sans toucher au code »

Les commandes viennent de la documentation Python zero-code d’OpenTelemetry. app.py est votre application.

Fenêtre de terminal
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
export OTEL_SERVICE_NAME=payment
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.4.2,deployment.environment.name=dev
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
opentelemetry-instrument python app.py

opentelemetry-bootstrap détecte les bibliothèques installées et ajoute les instrumentations correspondantes. opentelemetry-instrument lance l’application avec ces instrumentations actives.

Pour voir l’étape « autorisation » dans la trace, avec le type de carte, quelques lignes suffisent :

from opentelemetry import trace
tracer = trace.get_tracer("payment")
def authorize(order):
with tracer.start_as_current_span("authorize_payment") as span:
span.set_attribute("app.payment.card_type", order.card_type)
return call_bank(order)

Ce span devient l’enfant du span HTTP créé automatiquement. Le préfixe app. signale un attribut propre à l’application, hors conventions standard.

receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
batch: {}
exporters:
debug:
verbosity: detailed
# Vers un stockage de traces qui accepte OTLP (Tempo, Jaeger, etc.).
otlp_grpc/traces:
endpoint: traces-backend:4317
tls:
insecure: true # lab uniquement, jamais en production
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug, otlp_grpc/traces]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [debug]

Trois remarques pour lire ce fichier.

  • memory_limiter vient en premier : il doit refuser les données avant tout autre traitement. batch vient en dernier, après les processors qui filtrent ou transforment.
  • L’exportateur debug affiche chaque span dans la console du Collector. C’est le moyen le plus simple de vérifier que les données arrivent.
  • Les versions récentes du Collector nomment l’exportateur OTLP gRPC otlp_grpc et l’exportateur OTLP HTTP otlp_http. Les anciens noms otlp et otlphttp restent acceptés comme alias dépréciés : certaines configurations du site les emploient encore. Vérifiez le nom attendu par votre version.

L’adresse 0.0.0.0 écoute sur toutes les interfaces. La documentation du Collector l’utilise par commodité et recommande localhost quand tous les clients sont locaux.

Deux notions reviennent partout. Elles sont simples, mais elles font toute la différence au moment de chercher.

La ressource décrit l’émetteur. Elle est attachée une fois pour toutes à tout ce que le service produit. Exemples : service.name="payment", service.version="1.4.2", deployment.environment.name="dev". Dans l’exemple, elle vient des variables OTEL_SERVICE_NAME et OTEL_RESOURCE_ATTRIBUTES. Sans service.name, impossible de savoir quel service a émis un span.

Les conventions sémantiques fixent le nom et le sens des attributs courants (conventions sémantiques). Une requête HTTP porte http.request.method et http.response.status_code. Une route porte http.route. Tout le monde utilise les mêmes noms, quel que soit le langage.

L’intérêt est concret. Si un service Java et un service Python suivent les conventions, une seule requête retrouve toutes les erreurs HTTP 500 des deux. Si chacun invente ses noms (status, httpCode, code_retour), il faut une requête par équipe.

Les conventions évoluent encore. Certains noms ont changé ces dernières années. Je recommande de fixer la version suivie et de la réviser à chaque montée de version des SDK.