Build Daily

Tinley Park · October 29, 2026
toolOpenTelemetry (CNCF)applied

OpenTelemetry Operator

The OpenTelemetry project's Kubernetes operator. It runs OpenTelemetry Collectors from an OpenTelemetryCollector custom resource (as a Deployment, DaemonSet, StatefulSet or injected sidecar) and injects zero-code auto-instrumentation into pods for Java, Node.js, Python, .NET, Go, Apache HTTPD and Nginx through a mutating admission webhook driven by an Instrumentation resource and pod annotations. Apache 2.0 and free; the operator version tracks the Collector version, and the webhook needs a TLS certificate, by default from cert-manager. It fits clusters that want traces from many services without code changes in each one.

Updated October 17, 2026

The OpenTelemetry Operator is a Kubernetes operator that manages two things: OpenTelemetry Collector instances and the auto-instrumentation of application pods. A cluster that runs it can get traces, metrics and logs out of services without a code change in each one, because the operator adds the language agent to a pod when the pod is created. The official docs and the repository docs own the full CRD reference.

What it is

A controller plus an admission webhook, written in Go, maintained by the OpenTelemetry project under the CNCF. The licence is Apache 2.0. It is the Kubernetes member of the OpenTelemetry family, next to the Collector, the language SDKs and the zero-code instrumentation agents that it packages.

The current release is v0.161.0 (2026-10-13). Releases come out about every two to four weeks. The operator follows the Collector's version up to the minor number: operator v0.161.0 deploys Collector v0.161.0 by default, and the patch number belongs to the operator alone. The v0.161.0 release bundles Java agent 2.32.0, .NET 1.17.0, Node.js 0.78.0, Python 0.66b1 and Go 0.24.0 instrumentation images. The compatibility matrix lists Kubernetes v1.25 to v1.37 and cert-manager v1 for this release.

The operator owns four custom resources:

Resource API version What it declares
OpenTelemetryCollector opentelemetry.io/v1beta1 A Collector: mode, image, replicas and the Collector config
Instrumentation opentelemetry.io/v1alpha1 SDK settings for injected agents: exporter endpoint, propagators, sampler, per-language images
TargetAllocator opentelemetry.io/v1alpha1 Spreads Prometheus scrape targets across Collector replicas
OpAMPBridge opentelemetry.io/v1alpha1 Reports Collector state to an OpAMP server and takes remote config

Recent breaking changes worth knowing before an upgrade:

  • v0.161.0 removed the --create-rbac-permissions flag (deprecated since v0.88.0); RBAC creation is now only autodetected. Standalone OpAMP Bridge agents get new instance IDs, and the bridge needs get on the kube-system namespace.
  • v0.160.0 turned the generated operator, Collector and Target Allocator NetworkPolicies off by default, because they could block API server access on some clusters. Turn them back on with --feature-gates=+operator.networkpolicy,+operand.networkpolicy or spec.networkPolicy.enabled: true.
  • v0.159.0 made none an explicit Target Allocator filter strategy and rejects unknown filter_strategy values at start-up.

How it works

The operator has two jobs that run on different triggers. The controller watches OpenTelemetryCollector resources and reconciles them into ordinary Kubernetes objects: a workload, a ConfigMap with the Collector config, Services for each receiver port it finds, and a ServiceAccount. The mutating webhook sees every pod creation. When a pod or its namespace carries an instrumentation.opentelemetry.io/inject-<language> annotation, the webhook adds an init container that copies the language agent into a shared emptyDir volume, mounts that volume in the app container and sets the OTEL_* environment variables from the matching Instrumentation resource. The agent then sends telemetry over OTLP to a Collector, which exports it to a backend such as Tempo.

How the OpenTelemetry Operator instruments a pod and ships its traces

Step 1 of 3. The API server sends the new pod to the operator's mutating webhook before it stores the pod. The annotation must be on the pod template, not on the Deployment's own metadata.

Two paths: injection at pod creation, and trace export from the running pod through a managed Collector to Tempo.

The injection path runs once per pod, at creation; pods that already run keep their old spec until they restart. The export path runs for as long as the pod lives. The webhook is on the critical path for both the Collector resources and every annotated pod, so its certificate and availability matter (see Gotchas).

Collector deployment modes

Mode Workload Typical use
deployment (default) Deployment A central gateway that receives OTLP from many apps
daemonset DaemonSet One Collector per node, for node-local logs, host metrics or kubelet data
statefulset StatefulSet Stable pod names, for Prometheus scraping with the Target Allocator
sidecar Container in the app pod Injected by the sidecar.opentelemetry.io/inject annotation; one Collector per pod

When to use it

Reach for it when:

  • Many services need traces and changing the code of each one is not practical.
  • You want Collectors declared as Kubernetes resources and kept in the same Git workflow as the rest of the cluster.
  • You need sidecar Collectors per pod, or Prometheus scraping sharded across Collector replicas with the Target Allocator.
  • The services use Java, Node.js, Python or .NET, where zero-code agents cover the common frameworks well.

Skip it when:

  • One Collector is enough. The plain opentelemetry-collector Helm chart deploys it with no webhook and no CRDs.
  • The services already use an OpenTelemetry SDK in code and only need an endpoint.
  • The collector of record is Grafana Alloy and you do not need injection. Alloy already runs the upstream otelcol.* components.
  • The cluster cannot accept a mutating webhook with failurePolicy: Fail on its pod path.

At a glance

Core concepts

  • Reconcile, do not template. An OpenTelemetryCollector is the source of truth; the operator owns the workload, ConfigMap and Services and overwrites manual edits.
  • Annotation-driven injection. instrumentation.opentelemetry.io/inject-java: "true" (or -nodejs, -python, -dotnet, -go, -apache-httpd, -nginx) on a pod template or namespace. The most specific annotation wins.
  • Version lock-step. The operator upgrades managed Collectors to its own minor version.
  • Language gates. Java, Node.js, Python, .NET and Apache HTTPD injection are on by default. Go and Nginx are off until --enable-go-instrumentation or --enable-nginx-instrumentation is set.
  • Config is passed through. The operator reads the Collector config to find ports and the metrics endpoint, but it does not validate all of it. An invalid config can still create pods that crash.

Key facts

Aspect Value
Current release v0.161.0 (2026-10-13)
Helm chart open-telemetry/opentelemetry-operator 0.126.0, appVersion 0.161.0
Kubernetes v1.25 to v1.37 for v0.161.0
Webhook TLS cert-manager v1 by default; Helm-generated self-signed cert as the alternative
Default Collector image opentelemetry-collector-k8s distribution
Node OS Linux only
Licence Apache 2.0

Comparison

Aspect OpenTelemetry Operator Collector Helm chart Grafana Alloy
Unit of config CRDs reconciled by a controller Helm values Alloy config syntax, Helm values
Auto-instrumentation Yes, via webhook No No
Sidecar Collectors Yes No No
Extra moving parts Webhook, cert, CRDs None None
Best fit Many services, zero-code tracing One gateway or agent Logs and metrics to the Grafana stack

How to integrate

Install cert-manager first, then the operator from the official chart:

helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
  --namespace opentelemetry-operator-system --create-namespace \
  --version 0.126.0 \
  --set "manager.collectorImage.repository=ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-k8s"

Without cert-manager, let Helm generate the webhook certificate:

# values.yaml
admissionWebhooks:
  certManager:
    enabled: false
  autoGenerateCert:
    enabled: true
manager:
  extraArgs:
    - --enable-go-instrumentation=true   # only if Go injection is needed

A gateway Collector that receives OTLP and forwards traces to Tempo:

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: gateway
  namespace: observability
spec:
  mode: deployment
  config:
    receivers:
      otlp:
        protocols:
          grpc: { endpoint: 0.0.0.0:4317 }
          http: { endpoint: 0.0.0.0:4318 }
    processors:
      memory_limiter: { check_interval: 1s, limit_percentage: 75, spike_limit_percentage: 15 }
      batch: {}
    exporters:
      otlp:
        endpoint: tempo-distributor.tempo.svc:4317   # adjust to the Tempo service
        tls: { insecure: true }
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [otlp]

An Instrumentation resource, created before the workloads it instruments:

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: default
  namespace: my-app
spec:
  exporter:
    endpoint: http://gateway-collector.observability.svc:4317
  propagators: [tracecontext, baggage]
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"
  python:
    env:
      - name: OTEL_EXPORTER_OTLP_ENDPOINT
        value: http://gateway-collector.observability.svc:4318

Then annotate the pod template and restart the workload:

spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-python: "true"
kubectl rollout restart deploy/my-api -n my-app
kubectl get otelinst,otelcol -A

In the GL stack

The operator runs at v0.161.0 on the beta cluster only. It is a trial: it runs there first, and it goes to the production cluster only after it proves itself on beta. On beta it manages the OpenTelemetry Collectors and the auto-instrumentation of workloads. Traces go to Tempo, the trace store on the management cluster next to Loki, and Grafana on the management cluster reads them through its Tempo data source next to Prometheus and Loki.

This keeps tracing apart from the rest of the observability layer. Logs come from Grafana Alloy on every cluster and metrics come from Prometheus agents that remote-write to the hub on the management cluster; neither depends on the operator. cert-manager already runs on every cluster for in-cluster certificates, so the webhook's certificate dependency adds no new component. Keeping the trial on beta limits the effect of a broken webhook or a bad agent image to the beta environment, which the beta branch feeds before anything reaches main.

Gotchas

  • Create the Instrumentation before the workload. The webhook acts only at pod creation. Pods created before the resource existed, or before the annotation was added, run without the agent until they restart.
  • Annotate the pod template. The annotation goes on spec.template.metadata.annotations. On the Deployment's own metadata it does nothing.
  • Port 4317 versus 4318. Python, .NET and Go agents export over HTTP/protobuf by default. If spec.exporter.endpoint points at 4317, set OTEL_EXPORTER_OTLP_ENDPOINT to the 4318 address for those languages, as the official example does.
  • The webhook can block pods. The chart sets failurePolicy: Fail. If the operator pod or its certificate is broken, creating annotated resources fails. Watch the operator Deployment like any other part of the control path.
  • Go needs privilege. Go injection is off by default, needs instrumentation.opentelemetry.io/otel-go-auto-target-exe set to the binary path, runs with privileged: true and runAsUser: 0, and supports only the first container in a pod.
  • Multi-container pods. Injection targets the first container unless instrumentation.opentelemetry.io/container-names lists the others.
  • musl images. Python on Alpine-based images needs instrumentation.opentelemetry.io/otel-python-platform: "musl".
  • CRDs on upgrade. The chart uses templated CRDs and does not support --skip-crds; use crds.create=false to manage them elsewhere.
  • Move off v1alpha1 Collectors. Old v1alpha1 OpenTelemetryCollector resources still convert to v1beta1, but support is planned for removal. Re-apply manifests as v1beta1 and patch the CRD's storedVersions.
  • Private GKE. The control plane must reach port 9443 on the nodes for the webhook.

Risks

  • Licence: Apache 2.0 under the CNCF. There is no separate commercial edition.
  • Project health: active, with releases every few weeks and named maintainers across several vendors. The release pace also means frequent breaking notes, as the last three releases show; read each changelog.
  • Pre-1.0 APIs: the operator is still v0.x, and Instrumentation, TargetAllocator and OpAMPBridge are v1alpha1. Field changes between minors are possible.
  • Lock-in: low. The output is OTLP, and the Collector config is the upstream format, so the backend and even the operator can be swapped.
  • Security: the webhook mutates pods cluster-wide, and Go injection runs a privileged container. Limit which namespaces use injection, keep the operator's RBAC under review, and scan the agent images like any other image.

Alternatives

  • OpenTelemetry Collector Helm chart — pick it for one gateway or agent Collector with no CRDs or webhook.
  • Grafana Alloy — pick it when the backends are Loki, Prometheus and Tempo and the work is collection, not injection.
  • OpenTelemetry eBPF Instrumentation — pick it for zero-code HTTP and gRPC spans from the kernel, with no change to the app process.
  • SDKs in code — pick them when teams own their services and want full control over spans and attributes.

Related