Skip to main content
In this guide you’ll deploy an OpenTelemetry Collector into a Kubernetes cluster (using Helm) and instrument a sample application so it sends telemetry (traces, metrics, logs) to the collector running in-cluster. This walkthrough uses a Helm-based Collector deployment (DaemonSet in this example) and shows how to:
  • fetch and customize the Collector Helm chart values,
  • enable Kubernetes-specific presets (attributes, kubelet metrics, logs),
  • expose node-local OTLP endpoints via host ports,
  • deploy an instrumented application that sends OTLP to the node-local collector,
  • optionally forward traces to Jaeger for visualization.
This article demonstrates the direct Helm approach only (OpenTelemetry Operator is out of scope).

Environment

For this demo I created a local kind cluster with three nodes (one control-plane and two workers). Verify nodes with:
We use Helm to install the OpenTelemetry Collector chart. Add and verify the Helm repo:
This image shows a webpage from OpenTelemetry's documentation about Kubernetes integration, describing components like the OTLP Receiver, Kubernetes Attributes Processor, and Kubeletstats Receiver. It includes a sidebar menu with documentation sections and a dark theme interface.

1 — Prepare a local values.yaml

Fetch the chart’s default values into a local values.yaml so you can edit the Collector configuration (mode, image, presets, config):
Open values.yaml in your editor/IDE and edit the sections you need: mode, image, presets, and config (receivers/processors/exporters/pipelines). I prefer editing locally for clarity and versioning.
The image shows a Visual Studio Code workspace with a terminal open, displaying a Kubernetes configuration script related to OpenTelemetry collector settings.

2 — Presets: enable built-in Kubernetes integrations

The Helm chart exposes “presets” that add common Kubernetes-related components (kubernetesAttributes processor, kubeletstats receiver, host metrics, file log collection, etc.). Enabling a preset also adjusts RBAC, volumes, and mounts automatically. Example minimal preset snippets to include under presets: in values.yaml:
Enabling kubernetesAttributes enriches telemetry with pod, container, and node metadata (pod name, pod UID, node name, labels, etc.), which is highly valuable when troubleshooting or visualizing traces/metrics/logs.
The image shows a webpage from OpenTelemetry documentation, specifically about the Kubernetes Attributes Processor. It includes a table detailing deployment patterns and usability, along with a description of the processor's functionalities.
There are prebuilt receivers such as kubeletstats to collect node/pod/container metrics:
The image displays a webpage from OpenTelemetry documentation, detailing the configuration of Kubernetes receivers for telemetry data collection. It includes sections for "Kubeletstats Receiver" and "Filelog Receiver" with navigation links on the side.

3 — Mode: daemonset vs deployment (choose the right topology)

A Collector can run as a DaemonSet (node-local), Deployment (centralized), or StatefulSet. Choose based on collection needs, latency, and scaling.
ModeUse CaseProsCons
DaemonSetNode-local collection (host metrics, node-local OTLP)Low network hops, per-node collection, useful for node-local instrumentationMore pods to manage; storage for logs on nodes
DeploymentCentralized collection and processingEasier to scale collectors, centralized bufferingPotential higher latency and network hops, single ingestion point
DaemonSet ensures a collector runs on every node (useful to keep network hops low and to collect host-level metrics). Deployment gives centralized collectors and may be easier to scale for ingestion/backpressure.
For this demo we use mode: daemonset so apps can send OTLP to a collector running on the same node.

4 — Collector configuration (receivers / processors / exporters / pipelines)

Under config: in values.yaml define receivers, processors, exporters, extensions, and service pipelines. A simple example enabling OTLP receivers and a debug exporter:
Do not remove the health_check extension unless you update the liveness/readiness probes. Removing it can cause the Helm chart’s probes to fail and result in CrashLoopBackOff.

5 — Install the Collector with Helm

Install the customized chart:
Helm should report the release is deployed:
Check the DaemonSet and pods:

Troubleshooting CrashLoopBackOff

If Collector pods crash, inspect logs to find configuration errors:
Common example error when values.yaml contains an invalid key in exporter configuration:
Fix the config in values.yaml and update the release:
After the upgrade the DaemonSet should converge to the desired number of ready pods:

6 — How the DaemonSet exposes OTLP on node host ports

When using mode: daemonset, the chart config maps container ports to host ports (hostPort) so OTLP traffic sent to http://<node-ip>:4318 or gRPC to <node-ip>:4317 is delivered to the local collector pod. Inspect the DaemonSet YAML and node IPs:
Look for port mappings similar to:
Now OTLP HTTP to http://<node-ip>:4318/v1/traces or OTLP gRPC to <node-ip>:4317 will reach the node-local collector.

7 — Deploy a sample instrumented application

This sample app (sloppy-networks/traces-py) periodically generates a trace and exports it via OTLP HTTP to the collector. The app reads the collector host from an environment variable K8S_NODE_IP which we populate from the node’s IP using a fieldRef. Python tracer configuration excerpt:
Kubernetes Deployment app-deployment.yaml:
Apply the deployment and check which node the pod lands on:
Tail the collector logs on the node where the app landed to confirm telemetry arrives and is enriched with Kubernetes attributes:
Those attributes are supplied by the kubernetesAttributes preset enabled earlier.

8 — Forward traces to Jaeger (optional)

To visualize traces, deploy Jaeger and configure the Collector to export traces to Jaeger. Example Jaeger Deployment (all-in-one):
Internal ClusterIP Service for Jaeger:
Optional NodePort for accessing Jaeger UI from outside the cluster:
Apply Jaeger resources:
Update values.yaml for the Collector to include an exporter pointing to jaeger-internal:4317 (OTLP gRPC) or http://jaeger-internal:4318 (OTLP HTTP). Example config.exporters and pipeline snippet:
Upgrade the Helm release so the Collector uses the new exporter:
Collector pods will restart with the updated configuration. Confirm pods return to Running:
Access the Jaeger UI via NodePort (example: http://<node-ip>:30007) and you should see traces from the instrumented flight-service.

Summary checklist

  • Added the OpenTelemetry Helm repository and used helm show values to create a local values.yaml.
  • Enabled presets: kubernetesAttributes, kubeletMetrics, logsCollection to simplify Collector configuration and RBAC.
  • Deployed the Collector as a DaemonSet (node-local) with hostPort mappings for OTLP (4317/4318).
  • Deployed an instrumented Python app that reads the node IP via status.hostIP and sends OTLP to http://<node-ip>:4318/v1/traces.
  • (Optional) Deployed Jaeger and configured the Collector to export traces to a ClusterIP jaeger-internal service, then verified traces via Jaeger UI.
This demonstrates a Helm-based flow for deploying an OpenTelemetry Collector on Kubernetes and configuring applications to send telemetry to a node-local collector. For other deployment models and advanced lifecycle management, consider the OpenTelemetry Operator or managed observability backends.

Watch Video