Jaeger is an open-source, end-to-end distributed tracing system originally developed by Uber Technologies and now a CNCF graduated project. It lets you follow a single user request as it propagates across dozens of microservices, measure the latency contribution of each span, and pinpoint the exact service or database call responsible for a slowdown. On RHEL 8 you can run the all-in-one binary for development and proof-of-concept work, then graduate to a production Docker Compose stack backed by Elasticsearch for persistence. This tutorial covers both paths and shows how to instrument a Go or Python application to send spans directly to Jaeger.
Prerequisites
- RHEL 8 server (or AlmaLinux 8 / Rocky Linux 8) with sudo access
- At least 1 GB RAM for the all-in-one binary; 4 GB recommended for the Elasticsearch-backed stack
- Docker and Docker Compose installed for the production path
- Go 1.21+ or Python 3.9+ for the instrumentation examples
- Ports 14250 (Jaeger gRPC), 14268 (HTTP collector), and 16686 (UI) open in firewalld
Step 1 — Run Jaeger All-in-One for Development
The jaeger-all-in-one binary bundles the collector, query service, and UI into a single process with an in-memory storage backend. It is ideal for local development and demos but loses all trace data on restart.
JAEGER_VERSION="1.57.0"
curl -LO "https://github.com/jaegertracing/jaeger/releases/download/v${JAEGER_VERSION}/jaeger-${JAEGER_VERSION}-linux-amd64.tar.gz"
tar xzf jaeger-${JAEGER_VERSION}-linux-amd64.tar.gz
cd jaeger-${JAEGER_VERSION}-linux-amd64
./jaeger-all-in-one
--collector.otlp.grpc.host-port=":14250"
--collector.otlp.http.host-port=":14268"
--query.http-server.host-port=":16686"
Open http://localhost:16686 to confirm the Jaeger UI is running. You should see the search interface with no services listed yet.
Step 2 — Create a systemd Unit for Jaeger All-in-One
To run Jaeger all-in-one as a managed service rather than in a foreground terminal, install the binary system-wide and create a systemd unit.
sudo mv jaeger-all-in-one /usr/local/bin/jaeger-all-in-one
sudo chmod +x /usr/local/bin/jaeger-all-in-one
sudo tee /etc/systemd/system/jaeger.service > /dev/null <<'EOF'
[Unit]
Description=Jaeger All-in-One Distributed Tracing
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/jaeger-all-in-one
--collector.otlp.grpc.host-port=":14250"
--collector.otlp.http.host-port=":14268"
--query.http-server.host-port=":16686"
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now jaeger
sudo systemctl status jaeger
Step 3 — Production Setup: Docker Compose with Elasticsearch
For persistent storage, deploy the Jaeger Collector and Query services in Docker Compose backed by a single-node Elasticsearch cluster. Save the following as /opt/jaeger/docker-compose.yml.
mkdir -p /opt/jaeger && cd /opt/jaeger
cat > docker-compose.yml <<'EOF'
version: "3.8"
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
volumes:
- es-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
jaeger-collector:
image: jaegertracing/jaeger-collector:1.57
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
ports:
- "14250:14250"
- "14268:14268"
depends_on:
- elasticsearch
jaeger-query:
image: jaegertracing/jaeger-query:1.57
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
ports:
- "16686:16686"
depends_on:
- elasticsearch
volumes:
es-data:
EOF
docker compose up -d
docker compose ps
Step 4 — Instrument a Python Application
Install the OpenTelemetry Python SDK with the OTLP exporter and send spans to the Jaeger collector endpoint. The example creates a manual span to demonstrate the basic API.
pip install opentelemetry-sdk
opentelemetry-exporter-otlp-proto-grpc
opentelemetry-instrumentation
cat > trace_example.py <<'EOF'
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
exporter = OTLPSpanExporter(endpoint="http://localhost:14250", insecure=True)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("demo-service")
with tracer.start_as_current_span("process-order") as span:
span.set_attribute("order.id", "ORD-12345")
span.set_attribute("order.amount", 99.99)
print("Span sent to Jaeger")
EOF
python trace_example.py
Step 5 — Search Traces in the Jaeger UI
Open the Jaeger UI at http://<your-server-ip>:16686. Use the search panel to filter by service, operation, tag values, or minimum duration to narrow down the spans of interest.
# Firewall: open the Jaeger UI port
sudo firewall-cmd --permanent --add-port=16686/tcp
sudo firewall-cmd --reload
# Verify the collector is accepting spans
curl -s http://localhost:14269/metrics | grep jaeger_collector_spans_received_total
# Check query service health
curl -s http://localhost:16687/
In the UI’s Search tab, select demo-service from the Service dropdown (it appears after the first span is received), set Lookback to Last 1 Hour, and click Find Traces. Click any result to expand the full span timeline with attribute key-value pairs displayed in the right panel.
Conclusion
You have run Jaeger all-in-one on RHEL 8 for development, created a systemd service for persistent operation, deployed a Docker Compose production stack with Elasticsearch storage, and sent spans from a Python application using the OpenTelemetry OTLP exporter. Jaeger’s UI makes it straightforward to identify latency outliers, trace request flows across service boundaries, and correlate trace IDs with log lines in your centralised log system. For high-throughput environments, consider scaling the collector horizontally behind a load balancer and increasing the number of Elasticsearch shards.
Next steps: Correlating Jaeger Traces with Prometheus Metrics Using Grafana, Setting Up Adaptive Sampling in Jaeger, and Deploying Jaeger on Kubernetes with the Jaeger Operator.