Set up comprehensive distributed tracing across microservices using OpenTelemetry with automatic context propagation, trace correlation headers, and framework-specific auto-instrumentation for Python, Java, and Node.js applications.
Prerequisites
- Root or sudo access
- Python 3.8+
- Node.js 16+
- Java 17+
- 4GB RAM minimum
What this solves
Distributed tracing becomes essential when debugging performance issues across multiple microservices, but manually correlating requests between services requires significant development effort. OpenTelemetry provides automatic instrumentation and context propagation that traces requests across service boundaries without code changes, giving you complete visibility into distributed transactions and their performance bottlenecks.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of dependencies.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 software-properties-common
Install OpenTelemetry Collector
Download and install the OpenTelemetry Collector which will receive, process, and export telemetry data from your applications.
curl -L https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.91.0/otelcol_0.91.0_linux_amd64.tar.gz -o otelcol.tar.gz
tar -xzf otelcol.tar.gz
sudo mv otelcol /usr/local/bin/
sudo chmod +x /usr/local/bin/otelcol
Create OpenTelemetry Collector configuration
Configure the collector to receive traces via OTLP and export to Jaeger. This setup enables context propagation across all instrumented services.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- "*"
allowed_headers:
- "*"
processors:
batch:
timeout: 1s
send_batch_size: 1024
resource:
attributes:
- key: service.name
action: upsert
from_attribute: service_name
exporters:
jaeger:
endpoint: localhost:14250
tls:
insecure: true
logging:
loglevel: debug
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, resource]
exporters: [jaeger, logging]
telemetry:
logs:
level: info
Create collector directory and set permissions
Create the configuration directory and set proper ownership for the OpenTelemetry Collector service.
sudo mkdir -p /etc/otelcol
sudo chown -R root:root /etc/otelcol
sudo chmod 755 /etc/otelcol
sudo chmod 644 /etc/otelcol/config.yaml
Install Jaeger for trace visualization
Install Jaeger to store and visualize the distributed traces collected by OpenTelemetry.
curl -L https://github.com/jaegertracing/jaeger/releases/download/v1.52.0/jaeger-1.52.0-linux-amd64.tar.gz -o jaeger.tar.gz
tar -xzf jaeger.tar.gz
sudo mv jaeger-1.52.0-linux-amd64/jaeger-all-in-one /usr/local/bin/
sudo chmod +x /usr/local/bin/jaeger-all-in-one
Create systemd service for Jaeger
Set up Jaeger as a system service to ensure it starts automatically and runs continuously.
[Unit]
Description=Jaeger Tracing Platform
After=network.target
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-all-in-one --memory.max-traces=10000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Create Jaeger user and start service
Create a dedicated user for Jaeger and enable the service to start on boot.
sudo useradd -r -s /bin/false jaeger
sudo systemctl daemon-reload
sudo systemctl enable --now jaeger
Create systemd service for OpenTelemetry Collector
Configure the OpenTelemetry Collector as a system service for reliable operation.
[Unit]
Description=OpenTelemetry Collector
After=network.target
[Service]
Type=simple
User=otelcol
Group=otelcol
ExecStart=/usr/local/bin/otelcol --config=/etc/otelcol/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Create collector user and start service
Create a dedicated user for the OpenTelemetry Collector and start the service.
sudo useradd -r -s /bin/false otelcol
sudo chown -R otelcol:otelcol /etc/otelcol
sudo systemctl daemon-reload
sudo systemctl enable --now otelcol
Install Python auto-instrumentation
Install OpenTelemetry auto-instrumentation for Python applications including Flask, Django, and FastAPI support.
pip install opentelemetry-distro[otlp]
opentelemetry-bootstrap -a install
Create Python service example with auto-instrumentation
Create a sample Flask application to demonstrate automatic context propagation between services.
from flask import Flask, request, jsonify
import requests
import os
app = Flask(__name__)
@app.route('/users/')
def get_user(user_id):
# This request will automatically propagate trace context
orders_response = requests.get(f'http://localhost:5001/orders/{user_id}')
user_data = {
'user_id': user_id,
'name': f'User {user_id}',
'orders': orders_response.json() if orders_response.status_code == 200 else []
}
return jsonify(user_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Create orders service with trace correlation
Create a second service that will receive and continue traces from the user service automatically.
from flask import Flask, jsonify
import time
import random
app = Flask(__name__)
@app.route('/orders/')
def get_orders(user_id):
# Simulate database query time
time.sleep(random.uniform(0.1, 0.5))
orders = [
{'order_id': f'ord_{user_id}_001', 'amount': 99.99},
{'order_id': f'ord_{user_id}_002', 'amount': 149.50}
]
return jsonify(orders)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)
Create service directories and set permissions
Create application directories with proper permissions for the service files.
sudo mkdir -p /opt/app
sudo chown -R www-data:www-data /opt/app
sudo chmod 755 /opt/app
sudo chmod 644 /opt/app/*.py
Install Node.js auto-instrumentation
Set up automatic instrumentation for Node.js applications with comprehensive framework support.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g @opentelemetry/auto-instrumentations-node
Create Node.js service with Express
Create an Express.js service that demonstrates automatic HTTP instrumentation and context propagation.
const express = require('express');
const axios = require('axios');
const app = express();
const port = 5002;
app.use(express.json());
app.get('/payments/:userId', async (req, res) => {
const { userId } = req.params;
try {
// Simulate payment processing time
await new Promise(resolve => setTimeout(resolve, Math.random() * 300));
const payments = [
{ payment_id: pay_${userId}_001, amount: 99.99, status: 'completed' },
{ payment_id: pay_${userId}_002, amount: 149.50, status: 'pending' }
];
res.json(payments);
} catch (error) {
res.status(500).json({ error: 'Payment service error' });
}
});
app.listen(port, '0.0.0.0', () => {
console.log(Payment service listening on port ${port});
});
Install Node.js dependencies
Install the required Node.js packages for the payment service.
cd /opt/app
npm init -y
npm install express axios
Create Java auto-instrumentation setup
Download the OpenTelemetry Java agent for automatic instrumentation of Spring Boot and other Java frameworks.
sudo apt install -y openjdk-17-jdk maven
curl -L https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.32.0/opentelemetry-javaagent.jar -o /opt/app/opentelemetry-javaagent.jar
Configure context propagation headers
Create a configuration file that defines which trace context headers should be propagated across service boundaries.
# OpenTelemetry Configuration
otel.service.name=microservices-demo
otel.traces.exporter=otlp
otel.metrics.exporter=none
otel.logs.exporter=none
otel.exporter.otlp.endpoint=http://localhost:4318
otel.exporter.otlp.protocol=http/protobuf
Context Propagation Settings
otel.propagators=tracecontext,baggage,b3multi
otel.instrumentation.http.client.capture-request-headers=traceparent,tracestate,baggage,b3,x-trace-id
otel.instrumentation.http.server.capture-request-headers=traceparent,tracestate,baggage,b3,x-trace-id
Resource attributes
otel.resource.attributes=service.version=1.0.0,deployment.environment=production
Create service startup scripts
Create wrapper scripts that automatically apply OpenTelemetry instrumentation when starting services.
#!/bin/bash
export OTEL_SERVICE_NAME="user-service"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_PROPAGATORS="tracecontext,baggage,b3multi"
opentelemetry-instrument python /opt/app/user-service.py
#!/bin/bash
export OTEL_SERVICE_NAME="orders-service"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_PROPAGATORS="tracecontext,baggage,b3multi"
opentelemetry-instrument python /opt/app/orders-service.py
#!/bin/bash
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
export OTEL_SERVICE_NAME="payment-service"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_PROPAGATORS="tracecontext,baggage,b3multi"
node /opt/app/payment-service.js
Make startup scripts executable
Set execute permissions on the startup scripts and ensure proper ownership.
sudo chmod +x /opt/app/start-*.sh
sudo chown www-data:www-data /opt/app/start-*.sh
Configure firewall for OpenTelemetry ports
Open the necessary ports for OpenTelemetry Collector and Jaeger UI access.
sudo ufw allow 4317/tcp comment 'OpenTelemetry OTLP gRPC'
sudo ufw allow 4318/tcp comment 'OpenTelemetry OTLP HTTP'
sudo ufw allow 16686/tcp comment 'Jaeger UI'
sudo ufw allow 14250/tcp comment 'Jaeger gRPC'
sudo ufw reload
Verify your setup
Check that all OpenTelemetry components are running correctly and can communicate with each other.
sudo systemctl status jaeger
sudo systemctl status otelcol
curl -f http://localhost:16686/api/services
curl -f http://localhost:4318/v1/traces -X POST -H "Content-Type: application/json" -d '{}'
ss -tlnp | grep -E '(4317|4318|16686|14250)'
Start the sample services and test trace propagation:
# Start services in separate terminals
/opt/app/start-user-service.sh &
/opt/app/start-orders-service.sh &
/opt/app/start-payment-service.sh &
Test the distributed trace
curl http://localhost:5000/users/123
Check Jaeger UI for traces
echo "Visit http://your-server:16686 to view traces in Jaeger"
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Traces not appearing in Jaeger | OpenTelemetry Collector not forwarding | Check collector logs: sudo journalctl -u otelcol -f |
| Context not propagated between services | Missing or incorrect propagator configuration | Verify OTEL_PROPAGATORS includes 'tracecontext,baggage' |
| Service names showing as 'unknown_service' | OTEL_SERVICE_NAME not set | Export service name in startup scripts |
| High memory usage in collector | No batch processing configured | Add batch processor in collector config with smaller batch sizes |
| Missing HTTP headers in traces | Auto-instrumentation not capturing headers | Configure header capture in otel-config.properties |
| Jaeger service fails to start | Port 16686 already in use | Check port usage: sudo ss -tlnp | grep 16686 |
Next steps
- Configure OpenTelemetry sampling strategies for high-traffic applications to optimize trace collection performance
- Implement Jaeger security with TLS encryption and authentication for distributed tracing to secure your tracing infrastructure
- Configure Jaeger data retention policies and automated archiving with Elasticsearch backend for long-term trace storage
- Set up OpenTelemetry metrics collection with Prometheus integration for comprehensive observability
- Implement custom OpenTelemetry spans and attributes for application-specific tracing to enhance trace visibility
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
OTEL_VERSION="0.91.0"
JAEGER_VERSION="1.52.0"
TOTAL_STEPS=10
# Function to print colored output
print_status() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Function to print step progress
print_step() {
local step=$1
local message=$2
print_status $GREEN "[${step}/${TOTAL_STEPS}] ${message}"
}
# Cleanup function for rollback
cleanup() {
print_status $RED "Installation failed. Cleaning up..."
systemctl stop jaeger 2>/dev/null || true
systemctl stop otelcol 2>/dev/null || true
systemctl disable jaeger 2>/dev/null || true
systemctl disable otelcol 2>/dev/null || true
rm -f /etc/systemd/system/jaeger.service
rm -f /etc/systemd/system/otelcol.service
rm -rf /etc/otelcol
rm -f /usr/local/bin/otelcol
rm -f /usr/local/bin/jaeger-all-in-one
userdel jaeger 2>/dev/null || true
userdel otelcol 2>/dev/null || true
rm -f otelcol.tar.gz jaeger.tar.gz
rm -rf jaeger-*-linux-amd64
}
# Set trap for cleanup on error
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
print_status $RED "This script must be run as root"
exit 1
fi
# Auto-detect distribution
print_step 1 "Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
print_status $RED "Unsupported distribution: $ID"
exit 1
;;
esac
else
print_status $RED "Cannot detect distribution"
exit 1
fi
print_status $GREEN "Detected distribution: $ID"
# Update system packages
print_step 2 "Updating system packages..."
$PKG_UPDATE
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL curl wget gnupg2 software-properties-common tar
else
$PKG_INSTALL curl wget gnupg2 tar
fi
# Install OpenTelemetry Collector
print_step 3 "Downloading and installing OpenTelemetry Collector..."
curl -L "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTEL_VERSION}/otelcol_${OTEL_VERSION}_linux_amd64.tar.gz" -o otelcol.tar.gz
tar -xzf otelcol.tar.gz
mv otelcol /usr/local/bin/
chmod 755 /usr/local/bin/otelcol
rm otelcol.tar.gz
# Create OpenTelemetry Collector configuration
print_step 4 "Creating OpenTelemetry Collector configuration..."
mkdir -p /etc/otelcol
cat > /etc/otelcol/config.yaml << 'EOF'
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- "*"
allowed_headers:
- "*"
processors:
batch:
timeout: 1s
send_batch_size: 1024
resource:
attributes:
- key: service.name
action: upsert
from_attribute: service_name
exporters:
jaeger:
endpoint: localhost:14250
tls:
insecure: true
logging:
loglevel: debug
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, resource]
exporters: [jaeger, logging]
telemetry:
logs:
level: info
EOF
chown -R root:root /etc/otelcol
chmod 755 /etc/otelcol
chmod 644 /etc/otelcol/config.yaml
# Install Jaeger
print_step 5 "Downloading and installing Jaeger..."
curl -L "https://github.com/jaegertracing/jaeger/releases/download/v${JAEGER_VERSION}/jaeger-${JAEGER_VERSION}-linux-amd64.tar.gz" -o jaeger.tar.gz
tar -xzf jaeger.tar.gz
mv "jaeger-${JAEGER_VERSION}-linux-amd64/jaeger-all-in-one" /usr/local/bin/
chmod 755 /usr/local/bin/jaeger-all-in-one
rm -rf jaeger.tar.gz "jaeger-${JAEGER_VERSION}-linux-amd64"
# Create Jaeger user
print_step 6 "Creating Jaeger user..."
useradd -r -s /bin/false jaeger 2>/dev/null || true
# Create systemd service for Jaeger
print_step 7 "Creating systemd service for Jaeger..."
cat > /etc/systemd/system/jaeger.service << 'EOF'
[Unit]
Description=Jaeger Tracing Platform
After=network.target
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-all-in-one --memory.max-traces=10000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Create OpenTelemetry Collector user
print_step 8 "Creating OpenTelemetry Collector user..."
useradd -r -s /bin/false otelcol 2>/dev/null || true
chown -R otelcol:otelcol /etc/otelcol
# Create systemd service for OpenTelemetry Collector
print_step 9 "Creating systemd service for OpenTelemetry Collector..."
cat > /etc/systemd/system/otelcol.service << 'EOF'
[Unit]
Description=OpenTelemetry Collector
After=network.target
[Service]
Type=simple
User=otelcol
Group=otelcol
ExecStart=/usr/local/bin/otelcol --config=/etc/otelcol/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Start and enable services
print_step 10 "Starting and enabling services..."
systemctl daemon-reload
systemctl enable jaeger
systemctl enable otelcol
systemctl start jaeger
systemctl start otelcol
# Wait for services to start
sleep 5
# Verification
print_status $YELLOW "Verifying installation..."
# Check if services are running
if systemctl is-active --quiet jaeger; then
print_status $GREEN "✓ Jaeger service is running"
else
print_status $RED "✗ Jaeger service is not running"
systemctl status jaeger --no-pager
fi
if systemctl is-active --quiet otelcol; then
print_status $GREEN "✓ OpenTelemetry Collector service is running"
else
print_status $RED "✗ OpenTelemetry Collector service is not running"
systemctl status otelcol --no-pager
fi
# Check if ports are listening
if ss -tuln | grep -q ":16686"; then
print_status $GREEN "✓ Jaeger UI is available on port 16686"
else
print_status $YELLOW "⚠ Jaeger UI port 16686 not detected"
fi
if ss -tuln | grep -q ":4317"; then
print_status $GREEN "✓ OTLP gRPC receiver is listening on port 4317"
else
print_status $YELLOW "⚠ OTLP gRPC port 4317 not detected"
fi
print_status $GREEN "OpenTelemetry distributed tracing setup completed successfully!"
print_status $YELLOW "Access Jaeger UI at: http://$(hostname -I | awk '{print $1}'):16686"
print_status $YELLOW "Send traces to OTLP endpoint: http://$(hostname -I | awk '{print $1}'):4318"
# Remove trap as installation completed successfully
trap - ERR
Review the script before running. Execute with: bash install.sh