OpenTelemetry Collector on Kubernetes: Agent vs Gateway, With Configs That Work
Run a DaemonSet agent on every node and a small gateway Deployment behind it. Two Helm values files, the processor order that keeps the Collector alive, and the errors you will hit first.
Key takeaways
- Run the OpenTelemetry Collector on Kubernetes as a DaemonSet agent on every node that enriches telemetry with k8s_attributes, plus a gateway Deployment that batches, samples and exports.
- Install both with the official Helm chart, put memory_limiter first in every pipeline, and use the new otlp_grpc and k8s_attributes component names.
On this page
Run two tiers: an agent DaemonSet on every node that receives OTLP from local pods and adds Kubernetes metadata, and a gateway Deployment that batches, samples and exports to your backend. Install both with the open-telemetry/opentelemetry-collector Helm chart, and put memory_limiter first in every pipeline. Metadata at the edge, secrets and sampling in one place.
Verified against
OpenTelemetry Collector 0.164.0, Helm chart opentelemetry-collector 0.178.1, OpenTelemetry Operator 0.162.0, against the opentelemetry.io Kubernetes and deployment-pattern docs, the chart's values.yaml and the component READMEs. Checked October 2026.
Agent, gateway, or both
The Collector docs describe two deployment patterns, and on Kubernetes they map to two chart modes:
| Pattern | Chart mode | What it is good at | What it costs | Run it when |
|---|---|---|---|---|
| Agent only | daemonset | Node-local receive, cheap pod metadata lookup, host and kubelet metrics | Every node holds backend credentials and does its own export retries | Small clusters, one backend |
| Gateway only | deployment | One place for secrets, sampling and routing | Pod metadata by connection IP only; one hop for every span in the cluster | No per-node collection needed |
| Agent plus gateway | both | Enrichment at the edge, policy in the middle | Two configs to keep in sync | Anything production-sized |
We default to both. The agent is where k8s_attributes is cheapest: with a node filter, each agent watches only the pods on its own node. The gateway is where tail sampling has to live, because every span of a trace must reach the same instance, which the docs solve with the load_balancing exporter routing on traceID.
Install the agent DaemonSet
The chart refuses to render without two values. Leave either out and you get [ERROR] 'image.repository' must be set. or [ERROR] 'mode' must be set. The docs point at the Kubernetes distribution image, otel/opentelemetry-collector-k8s, which ships k8s_attributes, load_balancing and the tail sampling processor.
# otel-agent-values.yaml
mode: daemonset
image:
repository: otel/opentelemetry-collector-k8s
presets:
kubernetesAttributes:
enabled: true # adds RBAC and a node-filtered k8s_attributes processor
resources:
limits:
memory: 512Mi # the chart derives GOMEMLIMIT from this
config:
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
batch: {}
exporters:
otlp_grpc:
endpoint: otel-gateway-opentelemetry-collector.observability.svc.cluster.local:4317
tls:
insecure: true # in-cluster hop; use mTLS if your policy requires it
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8s_attributes, batch]
exporters: [otlp_grpc]
metrics:
receivers: [otlp]
processors: [memory_limiter, k8s_attributes, batch]
exporters: [otlp_grpc]
logs:
receivers: [otlp]
processors: [memory_limiter, k8s_attributes, batch]
exporters: [otlp_grpc]
Two details in that file matter more than they look. First, we list k8s_attributes ourselves. The preset prepends the processor to any pipeline that doesn't already name it, which would put it ahead of memory_limiter; naming it explicitly keeps the limiter first. In daemonset mode the preset also sets filter.node_from_env_var: K8S_NODE_NAME and injects that variable, so each agent queries only its own node's pods.
Second, the chart sets hostPort: 4317 on the OTLP port by default, so applications reach their node's agent through the node IP. Point the SDK at it with the downward API:
# deployment.yaml (app container)
env:
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://$(HOST_IP):4317"
Install the gateway Deployment
# otel-gateway-values.yaml
mode: deployment
replicaCount: 2
image:
repository: otel/opentelemetry-collector-k8s
resources:
limits:
memory: 2Gi
config:
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
batch: {}
exporters:
otlp_http:
endpoint: https://otlp.example-backend.com
headers:
authorization: "Bearer ${env:OTLP_TOKEN}"
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http]
$ helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
$ helm upgrade --install otel-gateway open-telemetry/opentelemetry-collector \
-n observability --create-namespace -f otel-gateway-values.yaml
$ helm upgrade --install otel-agent open-telemetry/opentelemetry-collector \
-n observability -f otel-agent-values.yaml
$ kubectl -n observability logs ds/otel-agent-opentelemetry-collector-agent | head
The gateway needs no k8s_attributes: the agents already attached k8s.pod.name, k8s.namespace.name and the deployment name. If you ever skip the agent tier, the processor README says a gateway can't see the original pod IP behind an agent, which is why agents in that setup run the processor in passthrough mode and forward the IP instead.
The processor order is the whole game
The memory_limiter README is explicit: put it first in every pipeline so it can push back on receivers before data piles up, and set GOMEMLIMIT to 80% of the container's hard limit. The chart does the second part for you (useGOMEMLIMIT: true by default). In containers, use limit_percentage rather than limit_mib so the limit follows the pod's memory limit. The README recommends a 1 second check_interval; the chart's default is 5 seconds, which is why we override it.
When the limiter trips you will see data refused due to high memory usage. That is the system working. The SDK or the upstream agent is supposed to retry; the README warns that data is permanently lost if the component in front of the limiter doesn't.
The errors you will hit first
unknown type: "otlp_grpc" for id: "otlp_grpc" (valid values: [...]): theotlpexporter's type has beenotlp_grpcsince core v0.144.0 (otlphttpis nowotlp_http), and contrib renamedk8sattributestok8s_attributesin v0.146.0 (loadbalancingis nowload_balancing), with the old names kept as deprecated aliases. An image older than that doesn't know the new names. Pin the image tag to the chart'sappVersion, or setrewriteDeprecatedComponentNames: falseon old images.service::pipelines::traces: references processor "k8s_attributes" which is not configured: you listed it in a pipeline without enabling the preset or defining it underprocessors.[ERROR] The opentelemetry-collector chart requires that the health_check extension...: you replacedservice.extensionsand droppedhealth_check; the probes depend on it.- Pod metadata missing on every span: the ServiceAccount can't list pods. Check the agent logs for a
forbiddenerror, and enable the preset instead of hand-writing the ClusterRole.
When something is defined but nothing flows, the troubleshooting docs list the usual cause first: a receiver configured but not enabled in any pipeline.
Where this breaks
Helm values merge maps but replace lists, so overriding receivers in a pipeline silently drops the chart's defaults (Jaeger, Zipkin, the self-scrape). That is usually what you want; know it is happening. The two-tier setup also doubles your upgrade surface, and the gateway is now on the critical path for all telemetry, so give it a PodDisruptionBudget and at least two replicas. If you need auto-instrumentation injected into pods, add the OpenTelemetry Operator, which requires cert-manager, on top of this; it doesn't replace the chart.
Questions people ask
Should the OpenTelemetry Collector run as a DaemonSet or Deployment?
Both, in most production clusters. A DaemonSet agent receives from local pods, adds Kubernetes metadata cheaply because it watches only its own node, and collects node metrics. A Deployment gateway holds backend credentials, runs tail sampling and handles retries in one place. Small clusters with one backend can run the DaemonSet alone and export directly.
Helm chart or OpenTelemetry Operator?
Use the Helm chart for the Collector itself; it is simpler to review and diff. Add the Operator when you want its OpenTelemetryCollector resource, sidecar injection, or auto-instrumentation of workloads through its Instrumentation resource. The Operator installs from a single manifest but needs cert-manager in the cluster first, which is an extra dependency to own.
Why does my collector say data refused due to high memory usage?
The memory_limiter processor hit its soft limit and is rejecting new data so the process doesn't get OOMKilled. Short spikes are normal. If it happens constantly, the Collector is undersized or the exporter is too slow: raise the pod's memory limit, add gateway replicas, or check that the backend isn't throttling you. Make sure SDKs retry so refused data isn't lost.
The call we'd make
Ship the agent DaemonSet plus a two-replica gateway, both from the official chart with the otel/opentelemetry-collector-k8s image, memory_limiter first and the new component names. Pin the image tag. Our OpenTelemetry tracing setup covers the SDK side, and if you are still deciding whether to run vendor agents at all, read OpenTelemetry vs vendor agents first. The caveat: two tiers is overkill below a handful of nodes.
Sources
Cite this article
Kiril Urbonas (2026). OpenTelemetry Collector on Kubernetes: Agent vs Gateway, With Configs That Work. DevOpsNess. https://www.devopsness.com/blog/opentelemetry-collector-kubernetes-setup
[OpenTelemetry Collector on Kubernetes: Agent vs Gateway, With Configs That Work](https://www.devopsness.com/blog/opentelemetry-collector-kubernetes-setup) (Kiril Urbonas, DevOpsNess, 2026-10-31)
More from DevOps
Explore more articles in this category
15 SRE Interview Questions With Answers, From SLOs to On-Call
SRE interviews test numbers and judgment. Fifteen questions on SLIs, error budgets, incident command, toil, capacity, on-call and postmortems, plus a systems-design scenario, each with the answer that scores and the one that loses the room.
Sign Container Images With Cosign: Keyless GitHub Actions Workflow, SBOM and In-Cluster Checks
One workflow that builds, signs keylessly with GitHub OIDC, attaches an SBOM and SLSA provenance, plus the Kyverno policy that refuses anything unsigned. Copy it, change three names, ship it.
Fix Prometheus High Cardinality: 6 Commands to Find and Drop Bad Series
When Prometheus runs out of memory, one label is almost always to blame. Six commands to find it, the relabel rules that drop it, and the scrape limits that stop it coming back.
You might have missed
Evergreen posts worth revisiting.