Set up production-grade distributed tracing in Kubernetes using Jaeger with Istio service mesh integration. Configure OpenTelemetry instrumentation, Elasticsearch storage backend, and comprehensive observability for microservices.
Prerequisites
- Kubernetes cluster with admin access
- kubectl and helm installed
- At least 8GB RAM available for cluster
- Persistent storage provisioner configured
What this solves
Distributed tracing becomes critical when microservices span multiple containers and nodes in Kubernetes clusters. Without proper observability, troubleshooting performance issues and understanding request flows across services becomes nearly impossible. This tutorial integrates Jaeger with Istio service mesh to provide comprehensive distributed tracing, automatic trace collection, and performance monitoring for your entire Kubernetes infrastructure.
Step-by-step installation
Update system packages and install prerequisites
Start by updating your package manager and installing required tools for Kubernetes and container management.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg software-properties-common apt-transport-https ca-certificatesInstall kubectl and helm
Install kubectl for Kubernetes cluster management and Helm for package deployment.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.listsudo apt update
sudo apt install helmInstall Istio service mesh
Download and install Istio service mesh which provides the infrastructure for distributed tracing integration.
curl -L https://istio.io/downloadIstio | sh -
cd istio-*
sudo cp bin/istioctl /usr/local/bin/
istioctl install --set values.defaultRevision=default -yEnable Istio sidecar injection
Configure automatic sidecar injection for namespaces where you want distributed tracing enabled.
kubectl label namespace default istio-injection=enabled
kubectl create namespace jaeger-system
kubectl label namespace jaeger-system istio-injection=enabledInstall Jaeger Operator
Deploy the Jaeger Operator which manages Jaeger instances and their lifecycle in Kubernetes.
kubectl create namespace observability
kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.49.0/jaeger-operator.yaml -n observabilityDeploy Elasticsearch for Jaeger storage
Set up Elasticsearch as the backend storage for Jaeger traces with proper configuration for production workloads.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
namespace: jaeger-system
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
env:
- name: discovery.type
value: single-node
- name: ES_JAVA_OPTS
value: "-Xms2g -Xmx2g"
- name: xpack.security.enabled
value: "false"
ports:
- containerPort: 9200
- containerPort: 9300
volumeMounts:
- name: elasticsearch-data
mountPath: /usr/share/elasticsearch/data
resources:
requests:
memory: "4Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
namespace: jaeger-system
spec:
selector:
app: elasticsearch
ports:
- port: 9200
targetPort: 9200kubectl apply -f /tmp/elasticsearch.yamlConfigure Jaeger instance with Elasticsearch backend
Create a production-ready Jaeger instance that uses Elasticsearch for storage and integrates with Istio.
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger-production
namespace: jaeger-system
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch.jaeger-system.svc.cluster.local:9200
index-prefix: jaeger
username: ""
password: ""
esIndexCleaner:
enabled: true
numberOfDays: 7
schedule: "55 23 *"
collector:
replicas: 3
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 256m
memory: 512Mi
options:
collector:
num-workers: 100
queue-size: 2000
query:
replicas: 2
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 256m
memory: 256Mi
ingester:
replicas: 2
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 256m
memory: 512Mikubectl apply -f /tmp/jaeger-production.yamlConfigure Istio for Jaeger integration
Update Istio configuration to send traces to your Jaeger instance automatically.
apiVersion: v1
kind: ConfigMap
metadata:
name: istio
namespace: istio-system
data:
mesh: |
extensionProviders:
- name: jaeger
envoyOtelAls:
service: jaeger-production-collector.jaeger-system.svc.cluster.local
port: 14268
defaultConfig:
proxyStatsMatcher:
inclusionRegexps:
- ".outlier_detection."
- ".circuit_breakers."
- ".upstream_rq_retry."
- "._cx_."
tracing:
sampling: 1.0
custom_tags:
my_tag_header:
header:
name: x-my-tag
defaultProviders:
tracing:
- jaegerkubectl apply -f /tmp/istio-jaeger-config.yaml
istioctl proxy-config cluster -n istio-system istio-proxyDeploy OpenTelemetry Collector
Install OpenTelemetry Collector to handle trace collection and forwarding from applications.
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo updatemode: deployment
replicaCount: 3
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
jaeger:
protocols:
grpc:
endpoint: 0.0.0.0:14250
thrift_http:
endpoint: 0.0.0.0:14268
processors:
batch:
timeout: 5s
send_batch_size: 1024
memory_limiter:
limit_mib: 512
exporters:
jaeger:
endpoint: jaeger-production-collector.jaeger-system.svc.cluster.local:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp, jaeger]
processors: [memory_limiter, batch]
exporters: [jaeger]
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 256m
memory: 512Mihelm install otel-collector open-telemetry/opentelemetry-collector -f /tmp/otel-values.yaml -n observabilityConfigure tracing for sample application
Deploy a sample microservice application with automatic tracing enabled through Istio sidecar injection.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
version: v1
spec:
containers:
- name: frontend
image: gcr.io/google-samples/microservices-demo/frontend:v0.8.0
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: default
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
version: v1
spec:
containers:
- name: backend
image: gcr.io/google-samples/microservices-demo/cartservice:v0.8.0
ports:
- containerPort: 7070
env:
- name: PORT
value: "7070"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: default
spec:
selector:
app: backend
ports:
- port: 7070
targetPort: 7070kubectl apply -f /tmp/sample-app.yamlConfigure sampling and trace retention policies
Set up intelligent sampling to control trace volume and configure retention policies for optimal storage usage.
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: tracing-config
namespace: istio-system
spec:
tracing:
- providers:
- name: jaeger
- customTags:
request_id:
header:
name: x-request-id
user_agent:
header:
name: user-agent
- sampling:
- value: 10.0
match:
headers:
x-trace-sample:
exact: "full"
- value: 1.0kubectl apply -f /tmp/telemetry-config.yamlSet up Jaeger UI access and ingress
Configure secure access to Jaeger UI through Kubernetes ingress with proper authentication.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jaeger-ingress
namespace: jaeger-system
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: jaeger-auth
nginx.ingress.kubernetes.io/auth-realm: 'Jaeger Authentication Required'
spec:
tls:
- hosts:
- jaeger.example.com
secretName: jaeger-tls
rules:
- host: jaeger.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jaeger-production-query
port:
number: 16686kubectl create secret generic jaeger-auth --from-literal=auth=$(htpasswd -nb admin 'YourSecurePassword123!') -n jaeger-system
kubectl apply -f /tmp/jaeger-ingress.yamlConfigure Grafana integration for Jaeger metrics
Set up Grafana dashboards to visualize Jaeger metrics and create alerts for trace anomalies. This step references our existing Grafana monitoring setup.
{
"dashboard": {
"id": null,
"title": "Jaeger Tracing Overview",
"tags": ["jaeger", "tracing"],
"timezone": "browser",
"panels": [
{
"title": "Traces per Second",
"type": "stat",
"targets": [
{
"expr": "rate(jaeger_collector_traces_received_total[5m])",
"legendFormat": "Traces/sec"
}
]
},
{
"title": "Trace Duration Distribution",
"type": "heatmap",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(jaeger_query_requests_duration_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
}
]
},
{
"title": "Error Rate by Service",
"type": "table",
"targets": [
{
"expr": "rate(jaeger_collector_spans_received_total{result=\"err\"}[5m]) by (service)",
"legendFormat": "{{service}}"
}
]
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}curl -X POST http://admin:admin@grafana.example.com/api/dashboards/db \
-H "Content-Type: application/json" \
-d @/tmp/jaeger-grafana-dashboard.jsonConfigure OpenTelemetry instrumentation for applications
Set up automatic instrumentation for Java applications
Configure OpenTelemetry Java agent for automatic trace collection without code changes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-microservice
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: java-microservice
template:
metadata:
labels:
app: java-microservice
spec:
initContainers:
- name: otel-agent-download
image: busybox:1.35
command:
- sh
- -c
- |
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.32.0/opentelemetry-javaagent.jar -O /shared/opentelemetry-javaagent.jar
volumeMounts:
- name: shared-data
mountPath: /shared
containers:
- name: app
image: openjdk:11-jre
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/shared/opentelemetry-javaagent.jar"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector-opentelemetry-collector.observability.svc.cluster.local:4318"
- name: OTEL_SERVICE_NAME
value: "java-microservice"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.version=1.0,environment=production"
volumeMounts:
- name: shared-data
mountPath: /shared
ports:
- containerPort: 8080
volumes:
- name: shared-data
emptyDir: {}kubectl apply -f /tmp/java-app-instrumentation.yamlConfigure Python application tracing
Set up OpenTelemetry for Python applications with automatic Django and Flask instrumentation.
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-microservice
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: python-microservice
template:
metadata:
labels:
app: python-microservice
spec:
containers:
- name: app
image: python:3.11-slim
command:
- sh
- -c
- |
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap --action=install
opentelemetry-instrument python app.py
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector-opentelemetry-collector.observability.svc.cluster.local:4318"
- name: OTEL_SERVICE_NAME
value: "python-microservice"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.version=1.0,environment=production"
- name: OTEL_PYTHON_LOG_CORRELATION
value: "true"
ports:
- containerPort: 5000kubectl apply -f /tmp/python-app-instrumentation.yamlConfigure Node.js application tracing
Set up automatic tracing for Node.js applications using OpenTelemetry auto-instrumentation. This integrates well with distributed tracing setups for multiple language stacks.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-microservice
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: nodejs-microservice
template:
metadata:
labels:
app: nodejs-microservice
spec:
containers:
- name: app
image: node:18-alpine
command:
- sh
- -c
- |
npm install @opentelemetry/api @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http
node --require @opentelemetry/auto-instrumentations-node/register app.js
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector-opentelemetry-collector.observability.svc.cluster.local:4318"
- name: OTEL_SERVICE_NAME
value: "nodejs-microservice"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.version=1.0,environment=production"
ports:
- containerPort: 3000kubectl apply -f /tmp/nodejs-app-instrumentation.yamlSet up security and RBAC for Jaeger access
Create RBAC policies for Jaeger components
Configure Role-Based Access Control to secure Jaeger components and limit access to sensitive tracing data.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: jaeger-operator
rules:
- apiGroups: [""]
resources: ["pods", "services", "endpoints", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["jaegertracing.io"]
resources: ["jaegers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jaeger-operator
namespace: observability
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jaeger-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jaeger-operator
subjects:
- kind: ServiceAccount
name: jaeger-operator
namespace: observability
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jaeger-reader
namespace: jaeger-system
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["services"]
resourceNames: ["jaeger-production-query"]
verbs: ["get"]kubectl apply -f /tmp/jaeger-rbac.yamlConfigure network policies for Jaeger isolation
Set up network policies to control traffic flow and enhance security for Jaeger components.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jaeger-collector-policy
namespace: jaeger-system
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: collector
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: default
- namespaceSelector:
matchLabels:
name: observability
ports:
- protocol: TCP
port: 14268
- protocol: TCP
port: 14250
egress:
- to:
- podSelector:
matchLabels:
app: elasticsearch
ports:
- protocol: TCP
port: 9200
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jaeger-query-policy
namespace: jaeger-system
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: query
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: istio-system
ports:
- protocol: TCP
port: 16686
egress:
- to:
- podSelector:
matchLabels:
app: elasticsearch
ports:
- protocol: TCP
port: 9200kubectl apply -f /tmp/jaeger-network-policy.yamlVerify your setup
Check that all Jaeger components are running correctly and trace collection is working.
kubectl get pods -n jaeger-system
kubectl get pods -n observability
kubectl logs -n jaeger-system -l app.kubernetes.io/component=collector --tail=50Verify Elasticsearch connectivity and Jaeger data storage:
kubectl port-forward -n jaeger-system svc/elasticsearch 9200:9200 &
curl -X GET "localhost:9200/_cat/indices?v" | grep jaeger
kubectl port-forward -n jaeger-system svc/jaeger-production-query 16686:16686 &Generate test traces and verify collection:
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- sh -c "for i in \$(seq 1 10); do curl -H 'x-request-id: test-\$i' http://frontend.default.svc.cluster.local/; sleep 2; done"Check trace data in Jaeger UI by accessing http://localhost:16686 and searching for recent traces from your test services.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| No traces appearing in Jaeger UI | Sampling rate too low or collector not receiving data | Check telemetry config sampling rate and verify collector logs with kubectl logs -n observability -l app.kubernetes.io/name=opentelemetry-collector |
| Elasticsearch connection errors | Network policy blocking access or service discovery issues | Verify service DNS resolution: kubectl exec -n jaeger-system deploy/jaeger-production-collector -- nslookup elasticsearch.jaeger-system.svc.cluster.local |
| High memory usage in collector | Insufficient batch processing or memory limits | Increase collector memory limits and tune batch processor settings in OpenTelemetry config |
| Missing traces for specific services | Sidecar injection not enabled or application not instrumented | Verify namespace labeling: kubectl get namespace default --show-labels and check sidecar injection |
| Jaeger UI authentication failures | Incorrect htpasswd format or missing auth secret | Recreate auth secret: kubectl delete secret jaeger-auth -n jaeger-system && kubectl create secret generic jaeger-auth --from-literal=auth=$(htpasswd -nb admin 'NewPassword') -n jaeger-system |
Next steps
- Configure advanced retention and archiving policies for Jaeger traces
- Implement comprehensive security with TLS encryption for Jaeger components
- Set up multi-region Jaeger clustering for disaster recovery
- Create custom Prometheus alerts based on Jaeger trace metrics
- Enhance service mesh security with mutual TLS and authorization policies
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'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
KUBERNETES_NAMESPACE="${1:-default}"
JAEGER_VERSION="v1.49.0"
ISTIO_VERSION="1.19.0"
HELM_VERSION="3.12.0"
# Global variables
TEMP_DIR=""
CLEANUP_FILES=()
# Usage message
usage() {
echo "Usage: $0 [namespace]"
echo " namespace: Kubernetes namespace for application services (default: default)"
echo ""
echo "This script installs Jaeger distributed tracing with Istio service mesh"
exit 1
}
# Cleanup function
cleanup() {
local exit_code=$?
if [ ${exit_code} -ne 0 ]; then
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
fi
# Remove temporary files
for file in "${CLEANUP_FILES[@]}"; do
[ -f "$file" ] && rm -f "$file"
done
# Remove temporary directory
[ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
exit $exit_code
}
trap cleanup ERR EXIT
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf upgrade -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum upgrade -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
export PKG_MGR PKG_UPDATE PKG_INSTALL PKG_UPGRADE
log_info "Detected distribution: $PRETTY_NAME"
else
log_error "Cannot detect Linux distribution"
exit 1
fi
}
# Check prerequisites
check_prerequisites() {
log_info "Checking prerequisites..."
# Check if running as root or with sudo
if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then
log_error "This script requires root privileges or passwordless sudo"
exit 1
fi
# Check if kubectl is available or can connect to cluster
if ! command -v kubectl >/dev/null 2>&1; then
log_warning "kubectl not found, will install it"
elif ! kubectl cluster-info >/dev/null 2>&1; then
log_warning "kubectl found but cannot connect to Kubernetes cluster"
log_warning "Please ensure you have a running Kubernetes cluster and proper kubeconfig"
fi
}
# Validate arguments
if [ $# -gt 1 ]; then
usage
fi
# Main installation steps
echo -e "${GREEN}=== Jaeger + Istio Distributed Tracing Installation ===${NC}"
echo "Target namespace: $KUBERNETES_NAMESPACE"
echo ""
detect_distro
check_prerequisites
# Create temporary directory
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo -e "${BLUE}[1/8]${NC} Updating system packages..."
if [ "$EUID" -eq 0 ]; then
$PKG_UPDATE && $PKG_UPGRADE
$PKG_INSTALL curl wget gnupg ca-certificates tar
else
sudo $PKG_UPDATE && sudo $PKG_UPGRADE
sudo $PKG_INSTALL curl wget gnupg ca-certificates tar
fi
# Install additional packages based on distro
if [ "$PKG_MGR" = "apt" ]; then
if [ "$EUID" -eq 0 ]; then
$PKG_INSTALL software-properties-common apt-transport-https
else
sudo $PKG_INSTALL software-properties-common apt-transport-https
fi
fi
log_success "System packages updated"
echo -e "${BLUE}[2/8]${NC} Installing kubectl..."
if ! command -v kubectl >/dev/null 2>&1; then
KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl"
chmod 755 kubectl
if [ "$EUID" -eq 0 ]; then
mv kubectl /usr/local/bin/
else
sudo mv kubectl /usr/local/bin/
fi
CLEANUP_FILES+=("kubectl")
fi
log_success "kubectl installed"
echo -e "${BLUE}[3/8]${NC} Installing Helm..."
if ! command -v helm >/dev/null 2>&1; then
if [ "$PKG_MGR" = "apt" ]; then
curl -fsSL https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo $PKG_UPDATE
sudo $PKG_INSTALL helm
else
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" -o helm.tar.gz
tar -zxf helm.tar.gz
if [ "$EUID" -eq 0 ]; then
mv linux-amd64/helm /usr/local/bin/
else
sudo mv linux-amd64/helm /usr/local/bin/
fi
CLEANUP_FILES+=("helm.tar.gz")
fi
fi
log_success "Helm installed"
echo -e "${BLUE}[4/8]${NC} Installing Istio..."
if ! command -v istioctl >/dev/null 2>&1; then
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIO_VERSION sh -
if [ "$EUID" -eq 0 ]; then
cp istio-*/bin/istioctl /usr/local/bin/
else
sudo cp istio-*/bin/istioctl /usr/local/bin/
fi
chmod 755 /usr/local/bin/istioctl
fi
# Install Istio with default configuration
istioctl install --set values.defaultRevision=default -y
log_success "Istio service mesh installed"
echo -e "${BLUE}[5/8]${NC} Configuring namespaces for Istio sidecar injection..."
kubectl create namespace jaeger-system --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace observability --dry-run=client -o yaml | kubectl apply -f -
# Enable sidecar injection for target namespace
kubectl label namespace "$KUBERNETES_NAMESPACE" istio-injection=enabled --overwrite
kubectl label namespace jaeger-system istio-injection=enabled --overwrite
log_success "Namespace configuration completed"
echo -e "${BLUE}[6/8]${NC} Installing Jaeger Operator..."
kubectl apply -f "https://github.com/jaegertracing/jaeger-operator/releases/download/$JAEGER_VERSION/jaeger-operator.yaml" -n observability
# Wait for operator to be ready
kubectl wait --for=condition=available --timeout=300s deployment/jaeger-operator -n observability
log_success "Jaeger Operator installed"
echo -e "${BLUE}[7/8]${NC} Deploying Elasticsearch for Jaeger storage..."
cat > elasticsearch.yaml << 'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
namespace: jaeger-system
spec:
serviceName: elasticsearch
replicas: 1
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
env:
- name: discovery.type
value: single-node
- name: ES_JAVA_OPTS
value: "-Xms1g -Xmx1g"
- name: xpack.security.enabled
value: "false"
ports:
- containerPort: 9200
- containerPort: 9300
volumeMounts:
- name: elasticsearch-data
mountPath: /usr/share/elasticsearch/data
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
namespace: jaeger-system
spec:
selector:
app: elasticsearch
ports:
- port: 9200
targetPort: 9200
EOF
kubectl apply -f elasticsearch.yaml
CLEANUP_FILES+=("elasticsearch.yaml")
# Wait for Elasticsearch to be ready
kubectl wait --for=condition=ready --timeout=600s pod -l app=elasticsearch -n jaeger-system
cat > jaeger-instance.yaml << 'EOF'
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger-prod
namespace: jaeger-system
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch:9200
collector:
replicas: 2
query:
replicas: 2
ingester:
replicas: 2
EOF
kubectl apply -f jaeger-instance.yaml
CLEANUP_FILES+=("jaeger-instance.yaml")
log_success "Elasticsearch and Jaeger instance deployed"
echo -e "${BLUE}[8/8]${NC} Verifying installation..."
# Wait for Jaeger components to be ready
log_info "Waiting for Jaeger components to be ready..."
kubectl wait --for=condition=available --timeout=300s deployment -l app.kubernetes.io/name=jaeger -n jaeger-system
# Verify Istio installation
if istioctl verify-install > /dev/null 2>&1; then
log_success "Istio installation verified"
else
log_warning "Istio verification failed, but installation may still work"
fi
# Check if Jaeger UI is accessible
if kubectl get service jaeger-prod-query -n jaeger-system > /dev/null 2>&1; then
log_success "Jaeger Query service is running"
log_info "To access Jaeger UI, run: kubectl port-forward -n jaeger-system service/jaeger-prod-query 16686:16686"
log_info "Then open http://localhost:16686 in your browser"
else
log_warning "Jaeger Query service not found"
fi
# Display final information
echo ""
echo -e "${GREEN}=== Installation Complete ===${NC}"
echo -e "${GREEN}✓${NC} Istio service mesh installed"
echo -e "${GREEN}✓${NC} Jaeger distributed tracing deployed"
echo -e "${GREEN}✓${NC} Namespace '$KUBERNETES_NAMESPACE' configured with sidecar injection"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Deploy your applications to the '$KUBERNETES_NAMESPACE' namespace"
echo "2. Access Jaeger UI: kubectl port-forward -n jaeger-system service/jaeger-prod-query 16686:16686"
echo "3. Configure your applications to send traces (automatic with Istio sidecar)"
echo ""
echo -e "${BLUE}Useful commands:${NC}"
echo "- Check Jaeger pods: kubectl get pods -n jaeger-system"
echo "- Check Istio injection: kubectl get namespace $KUBERNETES_NAMESPACE --show-labels"
echo "- View Jaeger logs: kubectl logs -n jaeger-system deployment/jaeger-prod-collector"
Review the script before running. Execute with: bash install.sh