Distributed Tracing on OpenShift
Learn distributed tracing in OpenShift using Spring Boot, OpenTelemetry, Jaeger, and Tempo. Understand Trace IDs, Span IDs, request flow, context propagation, and enterprise microservices observability.
Introduction
In a monolithic application, debugging is straightforward because a request is processed within a single application.
In a microservices architecture, a single user request may travel through multiple services.
Example:
- API Gateway
- Authentication Service
- Customer Service
- Payment Service
- Inventory Service
- Notification Service
- Database
- Kafka
If one service becomes slow, how do you identify it?
Logs from one service are not enough.
Metrics show system health but cannot follow an individual request.
This is where Distributed Tracing becomes essential.
Distributed tracing follows a request across every microservice, allowing developers to understand where time is spent and where failures occur.
Learning Objectives
By the end of this article, you will understand:
- What is Distributed Tracing?
- Trace ID and Span ID
- OpenTelemetry Architecture
- Jaeger Architecture
- Spring Boot Integration
- Context Propagation
- Distributed Tracing in OpenShift
- Enterprise Best Practices
Why Distributed Tracing?
Consider an online payment.
The request travels through multiple services.
flowchart LR
A[Client]
B[API Gateway]
C[Payment Service]
D[Fraud Service]
E[Notification Service]
F[(Oracle Database)]
A --> B
B --> C
C --> D
C --> E
C --> F
If the payment takes 6 seconds, which service caused the delay?
Logs alone cannot answer this.
What is a Trace?
A Trace represents the complete lifecycle of one request.
Example
Trace ID
8a3f12bc45de78
Every microservice handling the request shares the same Trace ID.
What is a Span?
A Span represents one unit of work.
Example
Client Request
↓
API Gateway
↓
Payment Service
↓
Database Query
Each operation creates its own Span.
Trace Architecture
flowchart LR
A[Client]
B[API Gateway]
C[Payment Service]
D[Fraud Service]
E[Oracle Database]
A --> B
B --> C
C --> D
C --> E
One Trace contains multiple Spans.
Trace vs Span
| Component | Description |
|---|---|
| Trace | Complete request |
| Span | Single operation |
| Parent Span | Calls another Span |
| Child Span | Sub-operation |
| Trace ID | Shared by all Spans |
| Span ID | Unique per Span |
OpenTelemetry Architecture
flowchart LR
A[Spring Boot]
B[OpenTelemetry SDK]
C[OpenTelemetry Collector]
D[Jaeger]
E[Grafana]
A --> B
B --> C
C --> D
D --> E
OpenTelemetry generates traces and exports them.
Request Flow
sequenceDiagram
participant Client
participant Gateway
participant Payment
participant Database
Client->>Gateway: Request
Gateway->>Payment: Forward Request
Payment->>Database: Query
Database-->>Payment: Response
Payment-->>Gateway: Success
Gateway-->>Client: Response
Each call becomes a Span.
Trace Context Propagation
Every request carries tracing headers.
traceparent
00-4bf92f3577b34da6a3ce929d0e0e4736
b7ad6b7169203331
01
These headers ensure every downstream service continues the same Trace.
Spring Boot Architecture
flowchart LR
A[REST Controller]
B[Service Layer]
C[Repository]
D[(Database)]
A --> B
B --> C
C --> D
Each layer contributes a Span.
Add Dependencies
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
Configure Spring Boot
management.tracing.enabled=true
management.tracing.sampling.probability=1.0
management.otlp.tracing.endpoint=http://otel-collector:4318/v1/traces
Trace Flow
flowchart LR
A[Spring Boot]
B[OpenTelemetry]
C[Collector]
D[Jaeger]
A --> B
B --> C
C --> D
Jaeger Architecture
flowchart LR
A[Spring Boot Pods]
B[OpenTelemetry Collector]
C[Jaeger Collector]
D[Jaeger Query]
E[Jaeger UI]
A --> B
B --> C
C --> D
D --> E
Viewing Traces
Jaeger displays:
- Trace ID
- Span Duration
- Service Name
- Errors
- Timeline
- Parent/Child Relationships
Banking Example
A payment request travels through:
flowchart LR
A[Customer]
B[API Gateway]
C[Payment Service]
D[Fraud Service]
E[Kafka]
F[Notification Service]
G[(Oracle Database)]
A --> B
B --> C
C --> D
C --> G
D --> E
E --> F
Distributed tracing shows the exact time spent in each component.
Correlation Between Logs and Traces
Every log should include:
- Trace ID
- Span ID
Example
INFO
TraceID=8a3f12bc45de78
SpanID=bf1298aa
Payment Created
This allows engineers to correlate logs with traces.
OpenShift Architecture
flowchart TD
A[Spring Boot Pods]
B[OpenTelemetry Collector]
C[Jaeger]
D[Grafana]
E[Operations Team]
A --> B
B --> C
C --> D
D --> E
Useful Commands
View Pods
oc get pods
View Collector
oc get deployment opentelemetry-collector
View Jaeger
oc get route jaeger
Describe Pod
oc describe pod payment-service
Common Problems
No Traces
Verify:
- OpenTelemetry dependency
- Collector endpoint
- Sampling enabled
Missing Trace IDs
Verify context propagation between services.
Empty Jaeger Dashboard
Check:
- Collector availability
- Network connectivity
- Exporter configuration
Partial Traces
Possible causes:
- One service not instrumented
- Missing tracing headers
- Incorrect OpenTelemetry configuration
Production Best Practices
- Instrument every microservice.
- Enable OpenTelemetry auto-instrumentation where possible.
- Include Trace ID in application logs.
- Propagate trace context across HTTP and messaging.
- Monitor collector health.
- Sample intelligently in production.
- Protect tracing endpoints.
- Retain traces according to business requirements.
- Integrate traces with logs and metrics.
- Use dashboards for root-cause analysis.
Common Mistakes
❌ Instrumenting only one microservice.
❌ Disabling context propagation.
❌ Logging without Trace IDs.
❌ Sending every trace in high-volume production without sampling.
❌ Ignoring asynchronous tracing for Kafka events.
Advantages
- End-to-end request visibility
- Faster root-cause analysis
- Better performance optimization
- Reduced Mean Time to Resolution (MTTR)
- Improved microservices observability
- Better production debugging
- Enterprise-ready monitoring
- Complete request lifecycle tracking
Summary
Distributed tracing enables developers to follow a request across every microservice involved in processing it.
Key takeaways:
- A Trace represents the complete request lifecycle.
- A Span represents an individual operation within that request.
- OpenTelemetry generates and exports traces.
- Jaeger visualizes request execution across services.
- Trace IDs should be included in application logs for complete observability.
- Combining logs, metrics, and traces provides a comprehensive observability solution for Spring Boot applications running on OpenShift.
Interview Questions
- What is Distributed Tracing?
- What is the difference between a Trace and a Span?
- What is a Trace ID?
- What is a Span ID?
- Why is OpenTelemetry preferred over vendor-specific libraries?
- How does context propagation work?
- What role does Jaeger play?
- Why should logs include Trace IDs?
- How do traces complement logs and metrics?
- What are the best practices for distributed tracing in OpenShift?