Jaeger v2 with OpenTelemetry on Kubernetes

Introduction
Jaeger v1 is scheduled for end-of-life on December 31, 2025. On November 12, 2024, Jaeger v2 was released as a major upgrade, built on the OpenTelemetry Collector framework.
This guide provides step-by-step instructions on setting up Jaeger v2 with OpenTelemetry on Kubernetes.
For more details on Jaeger v2, refer to the official release notes: Jaeger v2 Released
Pre-requisites
-
kubectl
-
Helm
-
cert-manager
-
OpenTelemetryOperator
-
Prometheus Operator
Jaeger v1 working with OpenTelemetry
Jaeger v1 can work with OpenTelemetry Collector, as illustrated below:

However, in Jaeger v1, the collector is simply one of the exporters within the OpenTelemetry Collector. Jaeger v1 itself is not based on the OpenTelemetry Collector, whereas Jaeger v2 is fully integrated with it.
Deploying Jaeger v2 Operator
Jaeger v2 is designed to be deployed on Kubernetes using the OpenTelemetry Operator, benefiting both Jaeger and OpenTelemetry users.
As the Jaeger V2 is released, it is decided that Jaeger V2 will deployed on Kubernetes using OpenTelemetry Operator. This will benefit both the users of Jaeger and OpenTelemetry. To use Jaeger V2 with OpenTelemetry Operator, the steps are as follows:
To install Jaeger v2, you need to deploy the following components:
-
cert-manager
-
OpenTelemetry Operator
Install cert-manager
Run the following command to install cert-manager:
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml
Verify that all resources are in a ready state within the cert-manager namespace.
Install OpenTelemetry Operator
Install the OpenTelemetry Operator by running the command below:
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
Verify that all resources are in a ready state within the opentelemetry-operator-system namespace.
Installing Jaeger Operator v2 with In Memory Storage
For testing purposes, Jaeger v2 can be installed with in-memory storage, eliminating the need for an external database.
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: jaeger-inmemory-instance (1)
namespace: o11y (2)
spec:
image: jaegertracing/jaeger:latest
ports:
- name: jaeger
port: 16686 (3)
config:
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
exporters: [jaeger_storage_exporter]
extensions:
jaeger_query:
storage:
traces: memstore
jaeger_storage:
backends:
memstore:
memory:
max_traces: 100000
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
jaeger_storage_exporter:
trace_storage: memstore
1 | The name of the OpenTelemetryCollector instance. Make sure that the suffix '-collector' is added to the name. So the name of Kubernetes service and deployment will be jaeger-inmemory-instance-collector. |
2 | The namespace where the OpenTelemetryCollector instance will be deployed. |
3 | The port where the Jaeger UI will be exposed. |
Apply the configuration:
$ kubectl apply -f jaeger-inmemory-instance.yaml
Check the deployed resources:
$ kubectl get all -n o11y -o name
To access the Jaeger UI, port-forward the service:
$ kubectl port-forward svc/jaeger-inmemory-instance-collector 16686:16686 -n o11y
Then open a browser and navigate to http://localhost:16686.

Installing Jaeger v2 with Cassandra Storage
For production environments, it is recommended to use persistent storage such as Cassandra. Follow the steps below to install Jaeger with Cassandra storage.
Install Cassandra
Create the necessary configuration files:
-
cassandra-credentials.yaml - This file contains the Cassandra credentials.
-
cassandra-initdb-configmap.yaml - This file contains the Cassandra initdb configmap.
-
cassandra-values.yaml - This file contains the custom Cassandra values.
apiVersion: v1
data:
password: Y2hhbmdlbWU= $ (1)
kind: Secret
metadata:
name: cassandra-credentials
namespace: o11y
1 | The password for the Cassandra database. |
apiVersion: v1
data:
(1)
create-keyspace.cql: |-
CREATE KEYSPACE IF NOT EXISTS jaeger_tracing
WITH REPLICATION = {
'class': 'NetworkTopologyStrategy',
'replication_factor': 2
};
kind: ConfigMap
metadata:
name: cassandra-initdb-configmap
namespace: o11y
1 | The CQL script to create the keyspace for the Jaeger tracing. |
Apply the configurations:
$ kubectl apply -f cassandra-credentials.yaml
$ kubectl apply -f cassandra-initdb-configmap.yaml
The cassandra-credentials secret and cassandra-initdb-configmap configmap are created and used in the Cassandra Helm chart.
dbUser:
user: cassandra
existingSecret:
name: cassandra-credentials (1)
keyMapping:
cassandra-password: password (2)
initDBConfigMap: "cassandra-initdb-configmap" (3)
replicaCount: 3
1 | The name of the secret that contains the Cassandra credentials. |
2 | The key in the secret that contains the Cassandra password. |
3 | The name of the configmap that contains the CQL script to create the keyspace for the Jaeger tracing. |
Install Cassandra using Helm:
#$ helm upgrade --install cassandra ./cassandra-12.1.1.tgz -f cassandra-values.yaml --namespace o11y --create-namespace
$ helm upgrade --install cassandra bitnami/cassandra -f cassandra-values.yaml --namespace o11y --create-namespace --version 12.1.1
Install Jaeger Operator v2 with Cassandra Storage
Create otel-collector.yaml
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: otel (1)
namespace: o11y
spec:
image: jaegertracing/jaeger:latest
ports:
- name: jaeger
port: 16686
config:
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
exporters: [jaeger_storage_exporter]
extensions:
jaeger_query:
storage:
traces: cassandra_storage
jaeger_storage:
backends:
cassandra_storage:
cassandra:
schema:
keyspace: "jaeger_tracing" (2)
create: "${env:CASSANDRA_CREATE_SCHEMA:-true}"
connection:
auth:
basic:
username: "${env:CASSANDRA_USERNAME:-cassandra}" (3)
password: "${env:CASSANDRA_PASSWORD:-changeme}" (4)
tls:
insecure: true
servers: ["cassandra:9042"] (5)
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
jaeger_storage_exporter:
trace_storage: cassandra_storage
1 | The name of the OpenTelemetryCollector instance. Make sure that the suffix '-collector' is added to the name. So the name of Kubernetes service and deployment will be otel-collector. |
2 | The keyspace for the Jaeger tracing. |
3 | The username for the Cassandra database. |
4 | The password for the Cassandra database. |
5 | The servers for the Cassandra database. |
Apply the configuration:
$ kubectl apply -f otel-collector.yaml
To access the Jaeger UI, port-forward the service:
$ kubectl port-forward svc/otel-collector 16686:16686 -n o11y
And call the endpoint below to generate traces:
$ kubectl -n o11y port-forward svc/o11y-otel-spring-example 8080:8080 $ curl http://localhost:8080/otel | jq
Then open a browser and navigate to http://localhost:16686.


Conclusion
In this guide, we showed you how to install Jaeger v2 with OpenTelemetry on Kubernetes. We installed Jaeger with in-memory storage and Cassandra storage. We also showed you how to send traces from the o11y-otel-spring-example application to Jaeger.
In this guide, we demonstrated how to install Jaeger v2 with OpenTelemetry on Kubernetes using both in-memory and Cassandra storage. We also tested trace generation using a sample application.
For further details, visit: