Products Consulting About Blog Contact Us Česky
arrow_back Back to blog

Why OpenTelemetry Beats Vendor-Locked APM Agents

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:

YAML
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:latest

Key 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:

YAML
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:latest

The 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:

AnnotationLanguage
instrumentation.opentelemetry.io/inject-javaJava
instrumentation.opentelemetry.io/inject-pythonPython
instrumentation.opentelemetry.io/inject-nodejsNode.js
instrumentation.opentelemetry.io/inject-dotnet.NET
instrumentation.opentelemetry.io/inject-goGo

How Traces Flow

Here’s the end-to-end path once auto-instrumentation is active:

  1. App starts — the injected OTel agent hooks into framework entry points (HTTP handlers, database drivers, gRPC clients)
  2. Request arrives — the agent creates a span, extracts trace context from incoming headers
  3. OTel SDK processes the span, applies sampling, and exports via OTLP
  4. OTel Collector receives spans, applies processing (batching, attribute enrichment, filtering)
  5. 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:

YAML
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:

  1. Deploy the OTel Collector in your cluster (start with a Deployment)
  2. Install the OTel Operator and create an Instrumentation resource
  3. Annotate one service to test auto-instrumentation
  4. Configure the Collector to export to your current APM vendor (most support OTLP now)
  5. 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.