It is Friday, and a Collector configuration change or a new DQL query has to be signed off before Monday. Pointing it at production would be reckless; faking traffic by hand eats the day. An OpenTelemetry demo Kubernetes deployment removes the dilemma: the project ships « Astronomy Shop », a fictional shop split into microservices, with a Helm chart and a built-in load generator. This post follows the project’s Kubernetes deployment page to install it, then diverts part of its traces to Dynatrace SaaS.
The reliability angle is that it separates two questions people often blur: « does my pipeline carry traces correctly? » and « is my application properly instrumented? ». Here the instrumentation is already done, in twelve languages according to the documentation (.NET, C++, Elixir, Go, Java, JavaScript, TypeScript, Kotlin, PHP, Python, Ruby, Rust). Only the transport is left to put under test.
Full disclosure: I did not deploy the demo to write this. Commands and values come from the OpenTelemetry and Dynatrace documentation (read on 8 November 2026) and from the chart’s values.yaml; anything unverified is labelled as such.
When an OpenTelemetry demo Kubernetes cluster is the right tool
It is good for three things: testing a Collector, an exporter or a backend at no risk; learning to read traces on a system that is already instrumented; and practising diagnosis, thanks to feature flags that trigger prepared faults. It is not the way to learn instrumenting your own code; the article on the Operator and auto-instrumentation fits that better.
It has a price. The documentation asks for Kubernetes 1.24 or later, 6 GB of free memory and Helm 3.14 or later. The values.yaml I read also bundles Kafka, OpenSearch and Jaeger. Use a throwaway cluster, or at least a dedicated namespace.
What you need on the Dynatrace side
Everything below targets Dynatrace SaaS. I read no equivalent page for Dynatrace Managed, so I draw no conclusion for it. You need a platform token carrying the openpipeline:traces:ingest scope, sent as a Bearer in the Authorization header, as the OTLP export page describes. Create it just for this: you will destroy it at the end.
Getting the shop online in two commands
The base install needs almost nothing: declare the chart repository, then create the release.
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts helm install my-otel-demo open-telemetry/opentelemetry-demo Nothing is exposed outside the cluster. To look at the shop from your workstation, forward the port of the frontend-proxy service (change the namespace if it is not default):
kubectl --namespace default port-forward svc/frontend-proxy 8080:8080 On that port the documentation lists the shop at the root, the load generator under /loadgen/, Jaeger under /jaeger/ui/ and the flag interface under /feature. Remember Jaeger: it becomes your yardstick later.
The Helm array trap before you add an exporter
The bundled Collector is configured from your own values file, under opentelemetry-collector then config. Adding an exporter there is easy; the catch is merging. Helm merges objects but replaces arrays wholesale. As soon as you write the exporters list of the traces pipeline, it overwrites the chart’s: if you do not copy the original exporters back, you silently cut Jaeger and the span-derived metrics.
The demo page mentions keeping spanmetrics. The values.yaml I read (chart 0.42.6, application 3.1.0, main branch) names otlp_grpc/jaeger, debug and span_metrics in that pipeline. Names drift between versions, so before writing anything run helm show values open-telemetry/opentelemetry-demo and copy the list for your version.
Wiring in Dynatrace without exposing the token
Three addressing details cost people time. The SaaS base URL is https://{environment-id}.live.dynatrace.com/api/v2/otlp; if you take the ID from the address bar, drop .apps, because the documentation says the address with .apps fails. The otlphttp exporter appends /v1/traces itself, so give it the base URL only. And the transport is HTTP with Protobuf; Dynatrace does not accept gRPC directly.
So that the token ends up neither in the values file nor in Git, I keep it in a Kubernetes Secret that the Collector reads through environment variables (extraEnvs, provided by the Collector chart). The ${env:...} syntax and the Bearer header follow the example on the Dynatrace page about deploying the Collector. Here is the whole sequence, with example values:
And the dynatrace-values.yaml file that ties it together:
What I always re-read in that file:
- the list
[otlp_grpc/jaeger, debug, span_metrics, otlp_http/dynatrace]is the original plus a single addition, so Jaeger keeps receiving traces; - the entry point
${env:DT_ENDPOINT}stops at/api/v2/otlp; - the chart writes
otlp_http/...where the demo page writesotlphttp/...: stay with the style of your Collector version. I did not verify that both spellings are equivalent.
On a demo that is already running, helm upgrade my-otel-demo open-telemetry/opentelemetry-demo --values dynatrace-values.yaml applies the change (an ordinary Helm command, not on the page). Only traces go to Dynatrace; metrics and logs stay on their original backends.
Proof that traces arrive
Let the pods become ready, open the shop and give it a few minutes of synthetic load. With no trace in Dynatrace, start with the Collector pod’s logs (kubectl logs): a 401 or 403 points at the token or its scope, a 404 more likely at the URL.
Then, in a Notebook, a DQL query. The documentation shows that fetch spans opens access to all spans and that summarize aggregates them. The template below is modelled on its examples; I did not run it:
The dt.service.name field shows up in the official examples, but I do not know how it is populated for spans received over OTLP. Empty result? Display five raw spans, look at their fields, then pick the one to group by. To take queries further: advanced DQL.
Packing the test bench away
The demo page does not detail removal. With Helm, helm uninstall my-otel-demo removes the release and kubectl delete secret dynatrace-otlp the Secret. Then revoke the token created for the trial; it is the only lasting trace of this experiment.
What this demo does not prove
- RUM: according to Dynatrace, front-end OpenTelemetry data received over OTLP does not enable RUM features (user sessions, Core Web Vitals, Session Replay). Do not judge those features with this bench.
- Living with OneAgent: on a cluster that is already monitored, spans can double up; see duplicate spans.
- Unverified here: a real deployment, measured consumption, whether both exporter names work and how
dt.service.nameis filled.
Best practices
- A throwaway cluster or dedicated namespace, destroyed after use.
- URL and token in a Secret, never in Git.
- Token limited to
openpipeline:traces:ingest, revoked afterwards. - Jaeger kept in the pipeline to compare with Dynatrace.
- A realistic next step: a Kubernetes Collector to Dynatrace on real workloads.
Frequently asked questions
What is the minimum setup for the OpenTelemetry demo on Kubernetes?
Kubernetes 1.24 or later, 6 GB of free memory and Helm 3.14 or later, per the project’s documentation.
Can you export to Dynatrace over gRPC?
Not directly: Dynatrace expects Protobuf over HTTP, not JSON. If your sources speak gRPC, a Collector can convert before sending.
What about Dynatrace Managed?
I did not verify it: this post rests on the SaaS page and a platform token. For Managed, read the /managed/ pages of the documentation.
Conclusion
A values file, a Secret and one exporter are enough, according to the documentation, to feed Dynatrace SaaS with a demo shop’s traffic without losing Jaeger. Check your chart version’s exporter list before writing anything: that is where it breaks silently.
span.observer is an independent blog, not affiliated with Dynatrace LLC or the OpenTelemetry project. Trademarks belong to their owners. Tools change fast: check commands and options in the official documentation.
Originally published on span.observer. French version: https://span.observer/fr/demo-opentelemetry-kubernetes-dynatrace/.




