Skip to content

Distributed Tracing

Distributed Tracing in Microservices: Jaeger and OpenTelemetry

Distributed tracing is critical for debugging and optimizing communication between microservices in a Kubernetes environment. As services scale and interdependencies grow, traditional logging and metrics alone cannot capture the full picture of request flows, latency bottlenecks, or error propagation. Tracing provides end-to-end visibility by capturing spans (individual operations) and traces (a sequence of spans across services), enabling teams to diagnose issues in distributed systems.

This section covers how to implement distributed tracing using Jaeger (a popular open-source tool) and OpenTelemetry (a vendor-agnostic observability framework), both of which integrate seamlessly with Kubernetes and service meshes like Istio.


Key Concepts: Traces, Spans, and Context Propagation

Before diving into implementation, understanding these core concepts is essential:

  • Trace: A single unit of work (e.g., a user request) that spans multiple services.
  • Span: A logical unit of work within a trace (e.g., a database query or API call). Each span includes metadata like name, start/end time, and tags.
  • Context Propagation: Mechanism to pass trace identifiers (e.g., trace ID, span ID) across service boundaries via headers, ensuring spans are linked correctly.

For example, a user request might trigger a span in a frontend service, then another span in a backend service, and finally a span in a database. These spans form a trace that reveals the request’s journey through the system.


Implementing Tracing with Jaeger

1. Deploy Jaeger in Kubernetes

Jaeger provides a UI for querying traces and visualizing spans. Deploy it using Helm:

helm repo add jaegertracing https://jaegertracing.github.io/helm-charts  
helm install jaeger jaegertracing/jaeger --set env=prod  

This deploys Jaeger with a UI accessible at http://<jaeger-service>:16686 (e.g., http://jaeger.default.svc.cluster.local:16686).

2. Instrument Services with Jaeger

To trace requests, inject the Jaeger agent into your services. For example, with Istio:

kubectl label namespace default istio-injection=enabled  

Then, deploy your service with the OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_ENDPOINT environment variables set to Jaeger’s OTLP endpoint:

env:
  - name: OTEL_SERVICE_NAME
    value: "my-service"
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "http://jaeger-collector:4317"

3. Query Traces

Access the Jaeger UI and use the Find Trace feature to search by trace ID or service name. Filter by span names (e.g., HTTP Request) to analyze latency or errors.


Implementing Tracing with OpenTelemetry

1. Deploy OpenTelemetry Collector

The OpenTelemetry Collector aggregates traces and metrics. Deploy it as a Kubernetes DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
        - name: otel-collector
          image: otelcol-contrib/otelcol-contrib:0.100.0
          args:
            - "-config.file=/etc/otel-collector-config.yaml"
          volumeMounts:
            - name: config
              mountPath: /etc/otel-collector-config.yaml
              subPath: otel-collector-config.yaml
      volumes:
        - name: config
          configMap:
            name: otel-collector-config

Configure the collector to send traces to Jaeger or a cloud backend:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
exporters:
  jaeger:
    endpoint: http://jaeger-collector:14250
    headers:
      "jaeger-endpoint": "http://jaeger-collector:14250"
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger]

2. Instrument Services with OpenTelemetry SDK

Add the OpenTelemetry SDK to your service code. For example, in Python:

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("user-request"):
    # Your service logic here

3. Analyze Traces

Use the OpenTelemetry Collector’s UI or integrate with tools like Prometheus and Grafana for advanced analysis.


Debugging with Traces

Once tracing is enabled, use these techniques to debug:
- Identify latency hotspots: Filter spans by duration to find slow operations.
- Track error propagation: Look for spans with status.code = "ERROR" and trace their parent-child relationships.
- Validate context propagation: Ensure trace IDs are consistent across service boundaries.


Key takeaways

  • Distributed tracing is essential for debugging microservices communication.
  • Jaeger provides a simple, UI-first approach for tracing.
  • OpenTelemetry offers flexibility and vendor-agnostic integration with Kubernetes.
  • Always ensure context propagation via headers to link spans across services.
  • Use trace analysis to optimize performance and resolve errors in distributed systems.