Why OpenTelemetry Beats Vendor-Locked APM Agents

If you’ve ever tried to swap out your APM vendor, you know the pain. Proprietary agents from Datadog, Dynatrace, or New Relic weave themselves deep into your deployment pipeline — different binary agents for each language, vendor-specific configuration, custom SDKs in your application code. Switching means ripping it all out and starting over.
OpenTelemetry offers a fundamentally different approach: one open standard to instrument everything, with the freedom to send telemetry data wherever you want.
The Problem with Vendor-Locked Agents
Traditional APM vendors ship their own proprietary agents. Here’s what that means in practice:
- Vendor lock-in. Your instrumentation code, dashboards, and alerting rules are all tied to one vendor. Migrating away is a multi-sprint project.
- Cost escalation. Once you’re locked in, vendors know it. Contract renewals come with price hikes because switching costs are enormous.
- Inconsistent APIs. Each vendor has a different agent for Java, Python, Node.js, Go, .NET. Configuration flags, environment variables, and capabilities vary across languages — even within the same vendor.
- Black-box agents. Proprietary agents are closed source. When something goes wrong (and it will — memory leaks, thread contention, compatibility issues), you’re at the mercy of vendor support timelines.
Why OpenTelemetry Is the Better Path
OpenTelemetry (OTel) is a CNCF project that provides a single, vendor-neutral API and SDK for collecting traces, metrics, and logs. It has become the second most active CNCF project after Kubernetes.
One API across all languages. Whether your services are written in Java, Go, Python, or TypeScript — the instrumentation API is consistent. Configuration patterns transfer across your entire stack.
Separation of instrumentation from backend. This is the key architectural insight: OTel decouples how you collect telemetry from where you send it. Instrument once with the OTel SDK, then route data to Grafana, Jaeger, Datadog, Honeycomb, or any OTLP-compatible backend — without touching application code.
Community-driven, massive ecosystem. Hundreds of auto-instrumentation libraries cover popular frameworks (Spring Boot, Express, Django, gRPC). The community moves fast, and you’re never waiting on a single vendor to add support.
Auto-Instrumentation in Kubernetes with the OTel Operator
Manually adding OTel SDKs to every service works, but it doesn’t scale across a large microservices fleet. The OpenTelemetry Operator for Kubernetes solves this with the Instrumentation CRD — a declarative way to inject auto-instrumentation into pods without modifying application code.
The Instrumentation CRD
The Instrumentation custom resource defines how auto-instrumentation should be configured for a namespace or set of workloads:
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: my-app
spec:
exporter:
endpoint: http://otel-collector.observability:4317
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.25"
java:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
nodejs:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latestKey configuration fields:
exporter.endpoint— where traces go (typically your OTel Collector’s OTLP gRPC endpoint)propagators— context propagation format (W3C TraceContext is the standard)sampler— controls what percentage of traces are captured (25% in this example)- Language-specific images — the init containers that inject the auto-instrumentation agent
Injecting Auto-Instrumentation into Pods
Once the Instrumentation resource exists, you opt in individual workloads with a single annotation:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-java-service
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: app
image: my-java-service:latestThe operator watches for this annotation and mutates the pod spec via a webhook — adding an init container that copies the OTel Java agent JAR, then setting JAVA_TOOL_OPTIONS to load it. Your application code doesn’t change at all.
Available annotations for different languages:
| Annotation | Language |
|---|---|
instrumentation.opentelemetry.io/inject-java | Java |
instrumentation.opentelemetry.io/inject-python | Python |
instrumentation.opentelemetry.io/inject-nodejs | Node.js |
instrumentation.opentelemetry.io/inject-dotnet | .NET |
instrumentation.opentelemetry.io/inject-go | Go |
How Traces Flow
Here’s the end-to-end path once auto-instrumentation is active:
- App starts — the injected OTel agent hooks into framework entry points (HTTP handlers, database drivers, gRPC clients)
- Request arrives — the agent creates a span, extracts trace context from incoming headers
- OTel SDK processes the span, applies sampling, and exports via OTLP
- OTel Collector receives spans, applies processing (batching, attribute enrichment, filtering)
- Backend (Grafana Tempo, Jaeger, vendor) stores and visualizes the trace
Collector Configuration Basics
The OTel Collector is the central hub in your observability pipeline. It uses a pipeline model with three stages:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 512
exporters:
otlp:
endpoint: tempo.observability:4317
tls:
insecure: true
debug:
verbosity: basic
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, debug]Receivers accept telemetry data (OTLP over gRPC/HTTP is the standard). Processors transform data in-flight — batching for efficiency, memory limiting for safety, attribute filtering for cost control. Exporters send data to one or more backends — you can fan out the same data to multiple destinations.
Deployment Topology
Two common patterns for running the Collector in Kubernetes:
- DaemonSet — one Collector per node. Low latency, good for high-volume workloads. Each pod sends to its local node’s Collector.
- Deployment (with a Service) — a pool of Collector replicas behind a Kubernetes Service. Simpler to manage, easier to scale. Better for most teams starting out.
Many production setups combine both: a lightweight DaemonSet agent that forwards to a central Deployment-based Collector gateway for processing and export.
Getting Started
If you’re currently running proprietary APM agents, here’s a pragmatic migration path:
- Deploy the OTel Collector in your cluster (start with a Deployment)
- Install the OTel Operator and create an
Instrumentationresource - Annotate one service to test auto-instrumentation
- Configure the Collector to export to your current APM vendor (most support OTLP now)
- Gradually migrate more services, then evaluate whether to switch backends
The beauty of this approach: you can adopt OpenTelemetry incrementally, keep your existing backend during the transition, and only switch when you’re ready — because your instrumentation is now vendor-neutral.
At SaaSForge, we use OpenTelemetry across all our products and help teams adopt it through our observability consulting. If you’re planning an OTel migration, get in touch.