Lab 7 — Traces manuelles & échantillonnage

L’instrumentation automatique (Lab 2) trace les frontières techniques (HTTP, SQL). Pour tracer la logique métier, on crée des spans manuels. Dans ce lab : un span manuel via annotation, la propagation de contexte vers un autre service, le bagage, puis la maîtrise du volume avec le tail sampling.

Prérequis

  • Labs 1 à 6 terminés, agent Java actif sur review-service.
  • Port-forward UIs actif (./scripts/open-ui.sh).
  • Les variables de la formation chargées dans votre shell : . ./scripts/env.sh. Elles donnent le port du review-service ($APP_PORT, accès direct au service, pas via le frontend-proxy) ainsi que $PF_ADDR et $PF_HOST, l’adresse sur laquelle vos port-forward écoutent.

Étapes

Partie 1 — Spans manuels & propagation

  1. Lire l’instrumentation manuelle dans ProductCatalogClient.java :
@WithSpan("product-catalog.lookup")
public void checkProductExists(@SpanAttribute("app.product.id") String productId) {
    ...
    Span.current().setAttribute("app.product.found", true);

et dans ReviewController.java (le bagage) :

Baggage baggage = Baggage.current().toBuilder()
        .put("app.review.channel", "web")
        .build();
try (Scope ignored = baggage.makeCurrent()) { ... }

Trois mécanismes du chapitre s’y trouvent — lesquels ?

Réponse
  • @WithSpan (annotation) : crée un span product-catalog.lookup, enfant automatique du span serveur — équivalent déclaratif de tracer.spanBuilder(...).startSpan() ;
  • @SpanAttribute / Span.current().setAttribute(...) : attributs posés sur le span courant ;
  • Baggage : des paires clé/valeur qui voyagent avec le contexte (header W3C baggage) vers les services aval — contrairement aux attributs, qui restent sur leur span.

Lors du POST /api/reviews, le service appelle le frontend de la boutique (GET /api/products/{id}) : l’agent instrumente ce client HTTP et propage le contexte (header traceparent) — le frontend rejoint donc votre trace.

  1. Générer une trace multi-services :
. ./scripts/env.sh   # si ce n'est pas déjà fait dans ce terminal
kubectl port-forward -n otel-demo --address $PF_ADDR svc/review-service $APP_PORT:8080 &
curl -X POST http://$PF_HOST:$APP_PORT/api/reviews \
  -H "Content-Type: application/json" \
  -d '{"productId": "OLJCESPC7Z", "rating": 5, "comment": "Trace me!", "userEmail": "ada.lovelace@example.com", "userName": "Ada Lovelace"}'
  1. Analyser la trace dans Jaeger (service review-service, opération POST /api/reviews) :
Réponse

La hiérarchie attendue :

POST /api/reviews                (review-service, span serveur)
├── product-catalog.lookup       (review-service, votre span manuel @WithSpan)
│   └── GET /api/products/{id}   (review-service, span client HTTP)
│       └── GET /api/products/…  (frontend ! propagation inter-services)
│           └── …                (spans internes du frontend)
├── INSERT reviews               (review-service, span JDBC)

Deux services dans une même trace = la propagation W3C Trace Context a fonctionné. Le bagage app.review.channel=web a voyagé dans les headers (il n’apparaît pas sur les spans : c’est un canal de transport, pas une donnée stockée — un processor peut le copier en attribut si besoin).

💡 Le span INSERT reviews est un span client (côté review-service) : vous ne trouverez aucun span côté PostgreSQL. La base n’est pas instrumentée et le protocole SQL ne transporte pas traceparent — la trace s’arrête à la base, qui est une feuille. La propagation ne marche qu’entre services instrumentés (ici review-servicefrontend).

Partie 2 — Tail sampling

  1. Le problème : en production, tracer 100 % du trafic coûte cher. Mais échantillonner à la source (head sampling) jette des traces avant de savoir si elles sont intéressantes. Le tail sampling décide après coup, dans le collecteur : on garde les erreurs et les requêtes lentes, on échantillonne le reste.

Écrivez la politique dans manifests/70-otel-traces-values.yaml : 100 % des traces en erreur, 100 % des traces > 1 s, 25 % du reste.

Réponse

Fichier de référence 70-otel-traces-values.yaml. Pour l’utiliser tel quel :

cp content/1_Labs/70-otel-traces-values.yaml manifests/

Son contenu :

opentelemetry-collector:
  config:
    processors:
      tail_sampling:
        decision_wait: 5s
        policies:
          - name: keep-errors
            type: status_code
            status_code:
              status_codes: [ERROR]
          - name: keep-slow
            type: latency
            latency:
              threshold_ms: 1000
          - name: sample-the-rest
            type: probabilistic
            probabilistic:
              sampling_percentage: 25
    service:
      pipelines:
        traces:
          processors: [memory_limiter, resourcedetection, resource, transform, tail_sampling, batch]

⚠️ Les politiques sont évaluées en OU : une trace est gardée si au moins une politique la retient. decision_wait : le collecteur retient les spans 5 s pour voir la trace entière avant de décider — c’est le coût mémoire du tail sampling. En multi-collecteurs, il faut router tous les spans d’une même trace vers la même instance (mode gateway + loadbalancing exporter).

  1. Appliquer (les values des labs précédents restent empilées) :
helm upgrade otel-demo open-telemetry/opentelemetry-demo \
  --version 0.40.9 -n otel-demo \
  -f manifests/values-training.yaml \
  -f manifests/30-otel-collector-values.yaml \
  -f manifests/60-otel-metrics-values.yaml \
  -f manifests/70-otel-traces-values.yaml
kubectl rollout status daemonset/otel-collector-agent -n otel-demo
  1. Vérifier la politique :
# ~20 requêtes OK (25 % devraient survivre) :
for i in $(seq 1 20); do curl -s http://$PF_HOST:$APP_PORT/api/reviews > /dev/null; done
# 3 erreurs (100 % doivent survivre) :
for i in $(seq 1 3); do
  curl -s -o /dev/null -X POST http://$PF_HOST:$APP_PORT/api/reviews \
    -H "Content-Type: application/json" \
    -d '{"productId": "DOESNOTEXIST", "rating": 5, "comment": "?", "userEmail": "x@example.com", "userName": "X"}'
done

Dans Jaeger : comptez les traces GET /api/reviews récentes (nettement moins de 20) et les traces en erreur (les 3, toutes marquées 🔴).

Pourquoi le load generator semble-t-il moins bavard ?

Le tail sampling s’applique à tout le pipeline traces : la démo entière est maintenant échantillonnée à 25 % (hors erreurs/lenteurs). Effet de bord assumé : les métriques spanmetrics (Lab 4) sont calculées après sampling dans notre pipeline — en production, on placerait le connector avant le tail sampling (deux pipelines chaînés) pour garder des métriques exactes.

Livrable

Une trace multi-services (review-service + frontend) analysée, et la politique de tail sampling active (3/3 erreurs conservées, ~25 % du reste).