Aller au contenu
Edouard Topin's Blog
Observabilité as Code sur VCF & Kubernetes / Série 04/05

OpenTelemetry sur Kubernetes : tracing distribué pour vos apps cloud-native

Configurer l'OTel Collector sur VKS, instrumenter les apps avec l'auto-instrumentation, exporter vers Tempo ou Jaeger, et corréler avec les exemplars Prometheus.

Edouard Topin
5 min de lecture
Illustration éditoriale abstraite d'une cascade de traces distribuées montrant des spans à travers plusieurs services connectés par le pipeline OpenTelemetry.

Le tracing distribué répond à la question que les métriques et les logs ne peuvent pas entièrement résoudre : dans une requête qui a touché sept services sur 400ms, lequel est responsable de 320ms de cette latence ? Les métriques vous disent que le P99 global a dégradé. Les logs vous disent que chaque service a reçu la requête. Seule une trace vous montre l’exact span où le temps a été perdu.

OpenTelemetry est devenu la réponse standardisée CNCF pour la collecte de traces. Il fournit des API indépendantes du langage, des agents d’auto-instrumentation pour les principaux runtimes, un protocole fil vendor-neutral (OTLP), et le Collector comme pipeline de traitement flexible. Cet article couvre le déploiement d’OpenTelemetry sur VKS : la configuration du Collector, la mise en place de l’auto-instrumentation, la stratégie d’échantillonnage, et l’intégration avec les exemplars Prometheus qui crée la boucle de corrélation métrique-vers-trace.

Le modèle de données OTel pour les traces

La spécification OTel pour les traces définit les concepts fondamentaux suivants :

Trace — un ensemble de spans partageant un span racine et un traceId commun (identifiant aléatoire de 16 octets). La trace représente une opération distribuée de bout en bout.

Span — l’unité atomique de travail dans une trace. Chaque span porte :

  • traceId — identifie la trace
  • spanId — identifie de façon unique ce span (8 octets)
  • parentSpanId — référence le span parent (absent pour les spans racines)
  • name — nom d’opération lisible (ex. HTTP GET /api/users)
  • kind — un parmi INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER
  • startTime / endTime — timestamps en nanosecondes
  • statusOK, ERROR, ou UNSET
  • attributes — paires clé-valeur (max 128 par span dans les SDK OTel)
  • events — annotations horodatées dans le span
  • links — références à des spans liés causalement dans d’autres traces

Propagation de contexte — le mécanisme qui passe le contexte de trace (traceId + spanId) à travers les frontières de service. Les requêtes HTTP utilisent le header traceparent (standard W3C Trace Context). Les messages Kafka utilisent le header W3C Baggage. gRPC utilise les métadonnées. Les agents d’auto-instrumentation gèrent automatiquement l’injection et l’extraction du contexte.

Le protocole OTLP

L’OpenTelemetry Protocol (OTLP) est le format fil standard pour les signaux OTel. Il supporte gRPC (port par défaut 4317) et HTTP/Protobuf (port par défaut 4318). OTLP transporte les trois types de signaux (traces, métriques, logs) sur la même connexion.

# Configuration du receiver OTLP dans l'OTel Collector
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

Le SDK envoie les spans par batch via OTLP. Le Collector les reçoit, les traite et les exporte. Ce découplage est la valeur centrale de l’architecture Collector — les applications n’ont besoin de connaître que l’endpoint du Collector, pas la topologie backend.

Déployer l’OpenTelemetry Collector

Le pipeline OTel Collector comporte quatre types de composants : receivers, processors, exporters et extensions. Le schéma de configuration du Collector les définit comme des maps nommées.

helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update

helm upgrade --install otel-collector open-telemetry/opentelemetry-collector \
  --namespace observability \
  --create-namespace \
  --values otel-collector-values.yaml
# otel-collector-values.yaml
mode: deployment

config:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318

  processors:
    memory_limiter:
      check_interval: 1s
      limit_mib: 800
      spike_limit_mib: 200
    batch:
      send_batch_size: 512
      timeout: 5s
    tail_sampling:
      decision_wait: 10s
      num_traces: 100000
      policies:
        - name: errors-policy
          type: status_code
          status_code: { status_codes: [ERROR] }
        - name: slow-traces-policy
          type: latency
          latency: { threshold_ms: 1000 }
        - name: probabilistic-policy
          type: probabilistic
          probabilistic: { sampling_percentage: 5 }

  exporters:
    otlp/tempo:
      endpoint: tempo.observability.svc.cluster.local:4317
      tls:
        insecure: true
    prometheusremotewrite:
      endpoint: http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090/api/v1/write

  extensions:
    health_check:
      endpoint: 0.0.0.0:13133

  service:
    extensions: [health_check]
    pipelines:
      traces:
        receivers: [otlp]
        processors: [memory_limiter, tail_sampling, batch]
        exporters: [otlp/tempo]
      metrics:
        receivers: [otlp]
        processors: [memory_limiter, batch]
        exporters: [prometheusremotewrite]

Auto-instrumentation avec l’OTel Operator

L’OpenTelemetry Operator pour Kubernetes permet une instrumentation zéro-code en injectant des agents d’auto-instrumentation spécifiques au langage comme init containers. Cela évite de demander aux équipes applicatives de modifier leur code.

helm upgrade --install opentelemetry-operator open-telemetry/opentelemetry-operator \
  --namespace observability \
  --set "manager.collectorImage.repository=otel/opentelemetry-collector-contrib"

Créez une ressource Instrumentation qui définit la configuration du SDK :

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: otel-instrumentation
  namespace: production
spec:
  exporter:
    endpoint: http://otel-collector.observability.svc.cluster.local:4318

  propagators:
    - tracecontext
    - baggage

  sampler:
    type: parentbased_traceidratio
    argument: "0.10"   # 10% head-based sampling au niveau SDK

  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.x
    env:
      - name: OTEL_INSTRUMENTATION_JDBC_ENABLED
        value: "true"

  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:0.x

  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:0.x

  go:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-go:0.x

Annotez les déploiements pour activer l’auto-instrumentation :

# Dans les métadonnées du template pod du Deployment
annotations:
  instrumentation.opentelemetry.io/inject-java: "production/otel-instrumentation"

Le webhook Operator intercepte la création de pod et injecte l’init container de l’agent et les variables d’environnement requises (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES). Les pods applicatifs démarrent instrumentés sans aucun changement de code.

Stratégie d’échantillonnage

L’échantillonnage est la pratique d’enregistrer seulement une fraction des traces. Sans échantillonnage, un système en production produisant 10 000 requêtes par seconde génère 10 000 spans racines par seconde — beaucoup trop pour stocker et interroger efficacement.

Décision prise au début de la trace. Le span racine décide si la trace est échantillonnée, et tous les spans en aval héritent de cette décision via la propagation de contexte. Simple et stateless — aucune coordination Collector requise.

Limitation : vous ne pouvez pas retrospectivement garder une trace parce qu’elle s’est avérée intéressante (lente, en erreur). Vous vous engagez avant de connaître le résultat. Utilisez un taux faible (1 à 10%) pour une visibilité de base.

Décision prise après la complétion de la trace. Le Collector met en buffer tous les spans pendant une fenêtre configurable (10 à 30 secondes), puis applique des politiques : garder toutes les traces en erreur, toutes les traces lentes, et N% du reste.

Avantage : vous ne ratez jamais une trace d’erreur ou lente. Le sous-ensemble représentatif conservé contient exactement les traces qui comptent opérationnellement.

Limitation : stateful, nécessite de router tous les spans de la même trace vers la même instance Collector.

Hérite la décision d’échantillonnage du span parent. Si le span racine est échantillonné, tous les spans enfants le sont. Sinon, aucun ne l’est. C’est le défaut du SDK OTel (parentbased_traceidratio) et permet un échantillonnage cohérent à travers des flottes de services hétérogènes.

Corrélation exemplar : métriques vers traces

Les exemplars Prometheus sont le mécanisme qui connecte une observation d’histogramme à une trace spécifique. Quand une application enregistre une latence de requête dans un bucket d’histogramme, elle peut attacher un traceId à cette mesure. Prometheus stocke l’exemplar à côté du bucket d’histogramme. Grafana rend l’exemplar comme une superposition cliquable sur le graphe, pointant vers la trace dans Tempo.

L’agent Java OTel attache automatiquement des exemplars aux histogrammes Micrometer/Prometheus quand Prometheus et l’instrumentation OTel sont tous les deux actifs. Pour l’instrumentation manuelle, utilisez l’API exemplar du SDK :

// Java : attacher un exemplar à une observation d'histogramme Prometheus
histogram.record(latencyMs, Attributes.of(
    AttributeKey.stringKey("http.method"), "GET",
    AttributeKey.stringKey("http.route"), "/api/products"
));
// Le SDK injecte automatiquement le contexte de trace courant comme exemplar

Activez le stockage des exemplars dans Prometheus (enableFeatures: [exemplar-storage] dans les valeurs kube-prometheus-stack) et configurez la source de données Tempo de Grafana avec la requête TraceQL exemplar. Le résultat : depuis un panneau Grafana montrant la latence P99, cliquez sur n’importe quel pic et atterrissez directement sur la trace responsable.

Tempo comme backend de traces

Grafana Tempo stocke les traces en object storage (S3, GCS ou vSAN Object Storage) et fournit une API de requête TraceQL consommée exclusivement par Grafana Explore. Il n’a pas d’UI de traces standalone.

helm upgrade --install tempo grafana/tempo \
  --namespace observability \
  --set storage.trace.backend=local \
  --set storage.trace.local.path=/var/tempo \
  --set persistence.enabled=true \
  --set persistence.storageClassName=vsan-default-storage-policy \
  --set persistence.size=50Gi

Pour de grands volumes de traces, basculez storage.trace.backend vers s3 et configurez un endpoint compatible S3. Le vSAN Object Storage avec compatibilité API S3 fonctionne comme backend, gardant les données de traces sur site.

Références.

Reçois le prochain par email

Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.

Désabonnement en un clic, à tout moment.

Retour au blog
Partager

Articles similaires

  1. 17 min de lecture

    Modèles de coûts FinOps : ce que facturent AWS, Azure et GCP — et ce que VCF calcule

    Un cluster-heure EKS, un palier AKS, une requête de pod GKE et un matériel VCF amorti ne sont pas quatre valeurs de la même variable. Ce que chaque plateforme facture, et ce que VCF calcule.

  2. 23 min de lecture

    Network policies et Cilium : construire un default-deny défendable

    L'API NetworkPolicy est livrée avec Kubernetes ; l'appliquer est le travail du CNI. Ce que Cilium ajoute, ce qui reste standard, et comment atteindre le default-deny sans casser la production.

  3. 18 min de lecture

    RBAC Kubernetes : les fondations, et les pièges qui survivent à l'audit

    Chacun de ces pièges est publié sur kubernetes.io. Ce qui manque, c'est leur mise en ordre — et le chemin qui mène d'un Namespace vSphere jusqu'à cluster-admin.

Suivre le blog

Nouveaux articles, réflexions et mises à jour.