Auto-Instrumentation Injection via the Operator in OpenTelemetry
Learn how the OpenTelemetry Operator quietly attaches tracing and metrics agents to your Kubernetes workloads at startup, using nothing more than a shared configuration resource and a single annotation, with no code changes and no image rebuilds.
Imagine an airline with five hundred aircraft already in service. The safety board announces that every plane must carry a flight recorder. One option is to send each aircraft back to the factory, open it up and rebuild part of it. Another option is to station a skilled mechanic at the hangar door who fits a standard recorder into every plane that rolls through, following one checklist, in a few minutes, without disturbing the airline’s own engineers. The planes still fly exactly as before. They simply start recording.
Adding observability to software running in containers poses the same choice. Rewriting every application to emit traces is slow and expensive. Auto-instrumentation injection is the hangar mechanic: a component called the OpenTelemetry Operator watches for new workloads on Kubernetes and, when a workload asks for it, fits a standard instrumentation agent into it as it starts. This tutorial explains what that means, how it works step by step, how to run it safely across a large organization, and where it can go wrong.
1Why Instrumentation Is the Hard Part
Collecting and storing telemetry gets most of the attention, but none of it exists until the application itself produces it.
The cold-start problem of observability
An observability platform is only as useful as the data that enters it. Before a trace can be stored, searched or sampled, some piece of code inside the application must notice that a request arrived, measure how long it took, and record which database or remote service it called. That act of adding measurement to software is called instrumentation.
In a company with a handful of services, developers can add instrumentation by hand. In a company with hundreds of services, written in several languages by many teams over many years, that approach stalls. Some teams are too busy. Some applications are old and nobody dares touch them. Some are built by outside vendors, and the source code is not even available. The result is patchy visibility, with exactly the blind spots that hurt most during an incident.
Think of installing water meters in a city. If every household must buy, fit and read its own meter, many will never bother and the city cannot see its real consumption. If the water company fits a standard meter at each connection, the city gets a complete and consistent picture, and households do nothing.
What teams usually face
Too many codebases
Touching hundreds of repositories to add the same library and the same settings is slow and easy to get wrong.
Mixed languages
Each language has its own libraries and conventions, so a single how-to guide never fits every team.
Untouchable software
Legacy and third-party applications cannot be changed quickly, or at all, yet they sit in the middle of critical flows.
Configuration drift
Every team picks its own endpoint, sampling rate and naming, which makes the data hard to compare.
Slow adoption
When every step needs a sprint of developer time, observability keeps losing against feature work.
Beginners often assume instrumentation must be written line by line inside business code. For a large share of everyday needs, such as incoming web requests, outgoing calls and database queries, a standard agent can add measurement with no developer effort at all. Hand-written instrumentation is for the business-specific parts only.
2Instrumentation Foundations: Manual and Automatic
To understand injection, first understand what is being injected and how it works inside a running program.
Manual instrumentation
In manual instrumentation, developers add OpenTelemetry API calls to their own code. They decide where a span begins and ends, what name it gets, and which business details are attached, such as an order type or a customer tier. This gives the richest and most meaningful data, but it costs developer time and must be maintained as code changes.
Automatic instrumentation
In automatic instrumentation, a prebuilt piece of software attaches to the application and adds measurement around well-known libraries and frameworks without any change to the application’s source. When the application handles a web request using a popular framework, the agent notices, starts a span, records standard details and ends the span. The same happens for outgoing web calls, database drivers, message queues and many other common building blocks.
Manual instrumentation is writing your own diary entry after each meeting, including what mattered to you. Automatic instrumentation is a calendar app that logs every meeting’s time, place and attendees by itself. The diary is more insightful, but the calendar covers everything, every time, without effort. The best practice is to use both.
How agents attach, language by language
The trick of automatic instrumentation is that it differs by language, because each runtime offers a different hook for attaching extra behavior.
| Runtime | Attachment idea | Plain-language picture |
|---|---|---|
| Java | A Java agent file is loaded at startup and rewrites class behavior as classes are loaded | An editor who adds footnotes to every page of a book as it is being printed |
| Node.js | A preload module runs before the application and wraps supported libraries as they are loaded | A receptionist who tags every visitor before they enter the building |
| Python | A startup hook patches supported libraries when the interpreter begins | Putting a thin sleeve over each tool before the worker picks it up |
| .NET | A profiler component attached through runtime settings modifies code as it is compiled for execution | A translator who adds notes while converting the text |
| Go | An external helper observes the running program from outside using kernel-level tracing, because Go is compiled ahead of time | A security camera watching a sealed room instead of changing it |
The common challenge: getting the agent into the container
Whatever the runtime, the agent has to be present inside the container when the application starts, and the runtime has to be told to load it. Traditionally this meant editing the application’s container image to add the agent files and changing its startup command. That is exactly the kind of per-application work that does not scale. This is the gap that injection through the Operator fills: it performs both steps, delivering the agent files and switching on the runtime hook, automatically at pod creation.
Every auto-instrumentation setup has just two jobs: get the agent files next to the application, and tell the runtime to load them. Everything the Operator does in this tutorial is a clever, automatic way of doing those two jobs on Kubernetes.
flowchart LR
subgraph Manual["Manual path"]
M1["Edit application code"] --> M2["Rebuild image"]
M2 --> M3["Redeploy"]
end
subgraph Injected["Injection path"]
I1["Add one annotation"] --> I2["Operator fits agent at pod start"]
I2 --> I3["Application runs instrumented"]
end
3Kubernetes Concepts You Need First
Injection is built from ordinary Kubernetes features, so a short tour of those features makes everything later easy to follow.
Pods and containers
Kubernetes is a system that runs applications packaged as containers across a cluster of machines. A pod is the smallest unit it schedules. A pod holds one or more containers that share a network address and can share storage. Usually one container is the application, and others are helpers.
Init containers
An init container is a special container that runs to completion before the main containers start. Kubernetes runs init containers one after another, and the application only starts once all of them have finished successfully. They are used for preparation work, such as copying files or waiting for a dependency.
An init container is the stagehand who sets the props before the actors walk on stage. By the time the performance begins, everything is in place, and the stagehand has already left.
Shared volumes
A volume is a storage area that can be attached to containers in a pod. An empty directory volume starts empty when the pod starts and lives as long as the pod lives. Several containers in the same pod can mount it, so what one container writes, another can read. This is the pass-through shelf between the init container and the application.
Environment variables
An environment variable is a named setting handed to a process when it starts. Runtimes read certain variables to decide how to behave. For example, a Java runtime reads a variable that lists extra startup options, and a Node.js runtime reads one that lists modules to load first. By setting such variables, you can change a runtime’s behavior without touching the program.
Annotations and labels
Every Kubernetes object can carry labels and annotations, which are key and value pairs attached to its metadata. Labels are mainly used for selecting and grouping. Annotations are free-form notes that tools can read to decide what to do. Injection uses annotations as the opt-in signal.
Custom resources and operators
Kubernetes can be extended with new kinds of objects called custom resources. An operator is a program that runs in the cluster, watches certain custom resources and does the work of making reality match what they describe. It encodes the knowledge of a human operator, such as “when someone asks for this, create these things”.
Admission webhooks
Before Kubernetes stores a new object, it passes the request through admission control. A mutating admission webhook is a web service that Kubernetes calls during that step, which may modify the object before it is saved. This is the exact mechanism that lets the Operator change a pod’s definition on the fly, before the pod ever starts.
A mutating webhook is a customs officer at the airport who checks every suitcase on the way through and, following rules, may add a label or a sealed pouch before it continues to the plane. The traveller never sees the officer, but the suitcase arrives slightly different.
Annotation
The pod’s note saying “please instrument me” and which language to use.
Webhook
The interceptor that edits the pod definition at creation time.
Init container
The one-time helper that copies the agent files into a shared volume.
Shared volume
The shelf where the agent files wait for the application.
Environment variables
The switches that tell the runtime to load the agent and where to send data.
4The OpenTelemetry Operator
The Operator is the Kubernetes-native manager of OpenTelemetry components and the engine behind injection.
What it is
The OpenTelemetry Operator is an open-source Kubernetes operator maintained by the OpenTelemetry project. It installs into a cluster as a deployment and registers new custom resource types. It has two main responsibilities that matter to most users. The first is to manage OpenTelemetry Collectors, in modes such as sidecar, node-level agent, standard deployment and stateful set. The second, the subject of this tutorial, is to inject automatic instrumentation into application pods.
The Operator is the building manager of an apartment block. Residents describe what they need through a standard request form, and the manager sorts out the work. For instrumentation, the manager walks past every new apartment and, if the resident ticked the box, fits the standard safety device.
The resource types it provides
Collector resource
Describes a Collector you want, including its mode and pipeline configuration. The Operator creates and maintains the underlying objects.
Instrumentation resource
Describes how auto-instrumentation should behave: where to send data, which sampling and propagation to use, which agent images to inject.
Target Allocator
Shares out metric scrape targets fairly among Collector instances.
Agent-management bridge
Connects Collectors to a fleet-management server speaking the open agent management protocol.
Inside the Operator
The Operator has a controller, which continuously reconciles the desired state described by custom resources with what actually exists. It also hosts webhooks. One validates and defaults the custom resources when users create them. Another, the pod mutating webhook, is called whenever any pod is about to be created in the cluster and decides whether to add instrumentation. Because that webhook must be reachable and trusted by the cluster’s control plane, the Operator also deals with certificates, typically obtained from a certificate manager or generated by itself, depending on how it is installed.
flowchart TB
U["Platform engineer"] -->|"Creates"| IR["Instrumentation resource"]
U -->|"Creates"| CR["Collector resource"]
subgraph OP["OpenTelemetry Operator"]
CTRL["Controller"]
WH["Pod mutating webhook"]
end
CR --> CTRL
CTRL --> COL["Collector pods"]
IR --> WH
DEV["Application team"] -->|"Deploys annotated workload"| API["Kubernetes API server"]
API -->|"Pod creation request"| WH
WH -->|"Modified pod"| API
Notice how responsibilities split naturally. The platform team owns the Instrumentation resource and the Collectors. Application teams own only a small annotation on their own workloads. Neither group needs the other’s expertise to do its part.
5What Auto-Instrumentation Injection Really Means
Now the two halves can be joined: the idea of automatic agents plus the machinery of the Operator.
A precise definition
Auto-instrumentation injection via the Operator is the process by which the OpenTelemetry Operator, at the moment a pod is created, modifies the pod’s definition so that a language-specific instrumentation agent is delivered into the pod and activated, based on a shared configuration resource and an opt-in annotation on the workload. The application’s source code and container image are not changed. Only the running pod is.
Three words in that definition deserve attention. Shared configuration means the settings live in one Instrumentation resource that many workloads reuse. Opt-in annotation means nothing happens unless a workload asks. Moment a pod is created means injection is a start-time event: running pods are not touched, which has big consequences for rollouts later.
Think of a pizza shop with a standard topping card taped to the counter. Each customer’s order slip simply says “use the house card”. The kitchen reads the card and adds the same toppings every time. If the house card is changed, new orders get new toppings, but pizzas already in the oven stay as they were.
The Instrumentation resource, piece by piece
The Instrumentation resource is the house card. It is a namespaced Kubernetes object, and it describes how injection should behave for pods that refer to it. Its main parts are as follows.
Exporter endpoint
The address that injected agents send telemetry to, normally a Collector service or a node-level agent.
Propagators
The formats used to pass trace context between services, so that a trace can continue across service boundaries.
Sampler
The rule that decides which traces the agents keep, such as a fixed percentage that respects the caller’s decision.
Resource attributes
Extra labels attached to all data from instrumented pods, such as environment or team.
Language sections
For each supported language, the agent image to use, extra environment variables and resource limits for the init container.
Common environment
Environment variables that apply to every language, useful for shared settings.
One resource, many workloads
A key benefit is reuse. A platform team can create one Instrumentation resource per namespace, or one per environment, and every annotated workload that refers to it receives identical, vetted settings. Changing a sampling rate or an endpoint becomes a single edit rather than hundreds of code changes.
Editing the Instrumentation resource does not change pods that are already running, because injection happens only when a pod is created. To apply a new setting to a workload, its pods must be recreated, for example through a normal rolling restart.
6Annotations: How a Workload Opts In
The annotation is the only thing an application team has to know, so it must be understood exactly.
One annotation per language
The opt-in signal is an annotation whose name includes the language to inject. The annotation names share a common prefix and end with the language, for example one for Java, one for Node.js, one for Python, one for .NET, one for Go, one for Apache HTTP Server and one for Nginx. There is also a variant that injects only the configuration for the SDK, with no agent, which is useful when an application already contains OpenTelemetry libraries and just needs consistent settings.
What the value can say
| Value | Meaning |
|---|---|
| true | Use the Instrumentation resource in the same namespace as the pod |
| A resource name | Use the named Instrumentation resource in the pod’s own namespace |
| namespace and name together | Use an Instrumentation resource from another namespace, which suits shared platform-owned settings |
| false | Do not instrument, even if a broader setting would otherwise apply |
Where the annotation must be placed
This is the single most common beginner mistake. Pods are usually created by higher-level objects such as deployments, which hold a template for their pods. The annotation must be placed on the pod template inside the deployment, not on the deployment object itself. The webhook only ever sees the pod being created, so an annotation placed on the outer object is invisible to it.
If you want every cake from a bakery recipe to have a candle, you write “add candle” on the recipe card that the cakes are made from, not on the bakery’s front door sign. The oven staff only read the recipe card.
Namespace-level opt-in
An annotation can also be placed on a namespace, in which case it applies to pods created in that namespace. This is attractive for platforms, because a team can say “everything in this namespace is a Java service, instrument it”. The trade-off is less precision, since every pod in the namespace is affected, including helpers that are not written in that language. Use it only where the namespace is genuinely uniform.
Pods with several containers
When a pod has more than one container, the Operator by default instruments the first container. A separate annotation lets you list which containers should be instrumented, and these can be of different languages if language-specific settings are provided accordingly. Sidecars such as service-mesh proxies are usually not meant to be instrumented, so naming the target container explicitly avoids mistakes.
Extra annotations for special cases
Go needs the executable path
Because Go instrumentation works from the outside, the injected helper must be told which running program to watch. An additional annotation provides the path of the target executable inside the container.
Python on lightweight Linux images
Some Python container images use a different system library than the common one. An extra annotation lets you tell the Operator which platform to match, so that the correct native components are delivered.
Per-workload resource attributes
Recent Operator releases let pods carry annotations or labels that set resource attributes such as the service name, so that teams can adjust identity without touching the shared Instrumentation resource. Check the current documentation for the exact keys supported by your version.
Annotation names and supported languages have grown over time, and some languages are switched on by default while others must be enabled through Operator startup options. Always confirm the exact names and defaults for the Operator version you run, using the official documentation as the source of truth.
7Internal Working: What Happens Step by Step
Following one pod from creation to first trace makes the whole mechanism concrete.
The journey of an annotated pod
A pod creation request arrives
A deployment, or another controller, asks the Kubernetes API server to create a pod. The request carries the pod definition copied from the template, including the opt-in annotation.
The API server calls the webhook
During admission, the API server sends the proposed pod to every registered mutating webhook, including the Operator’s pod webhook.
The Operator decides
The webhook reads the annotations, finds the referenced Instrumentation resource, checks that the requested language is enabled, and identifies which container should be instrumented. If anything required is missing, it leaves the pod unchanged.
The pod definition is rewritten
The webhook adds an init container using the agent image for that language, adds a shared empty directory volume, mounts the volume into both the init container and the application container, and sets environment variables on the application container.
The modified pod is stored and scheduled
The API server saves the changed definition and the scheduler places the pod on a node. From here, Kubernetes treats it like any other pod.
The init container copies the agent
When the pod starts, the init container runs first, copies the agent files from its image into the shared volume, and exits.
The application starts with the agent loaded
The application container starts, and the injected environment variables cause its runtime to load the agent from the shared volume before or alongside the application code.
Telemetry flows out
As requests arrive, the agent creates spans and metrics and sends them to the configured endpoint, typically a Collector, using the OpenTelemetry Protocol.
sequenceDiagram
participant D as Deployment controller
participant A as API server
participant W as Operator webhook
participant N as Node
participant I as Init container
participant P as Application container
participant C as Collector
D->>A: Create pod with opt-in annotation
A->>W: Admission review for the pod
W->>W: Read annotation and Instrumentation resource
W-->>A: Patch adds init container, volume and env vars
A->>N: Schedule the modified pod
N->>I: Run init container
I->>I: Copy agent files to shared volume
I-->>N: Exit successfully
N->>P: Start application with agent settings
P->>C: Send spans and metrics over OTLP
What changes inside the pod
It helps to compare the pod before and after. Before mutation it has the application container and maybe nothing else. After mutation it gains an init container, a shared volume with mounts, and extra environment variables. The application’s image, command and code are untouched, which is why the approach works for software you cannot rebuild.
flowchart LR
subgraph Before["Pod as submitted"]
B1["Application container"]
end
subgraph After["Pod after mutation"]
A1["Init container with agent image"]
A2["Shared empty directory volume"]
A3["Application container with agent settings"]
A1 -->|"Copies agent files"| A2
A2 -->|"Mounted read by"| A3
end
Before -->|"Webhook rewrites"| After
Imagine a courier delivering a parcel. Before the parcel leaves the depot, a worker slips a tracking sticker and a small beacon into the outer packaging. The recipient’s item inside is unchanged, yet the parcel now reports its position all the way to the door.
To confirm injection worked, inspect a freshly created pod. You should see an extra init container named for the language, a shared volume, and environment variables with the OpenTelemetry prefix on the application container. If those are absent, the webhook did not mutate the pod, and the troubleshooting chapter will help.
8Language-by-Language Mechanics
The injected pieces look similar from outside, but each language switches its agent on in a different way.
| Language | What the init container delivers | How the runtime is told to load it | Main caveat |
|---|---|---|---|
| Java | The Java agent file | A startup-options environment variable that points the Java runtime at the agent | Startup time and memory rise; existing options must be preserved |
| Node.js | Instrumentation packages for supported libraries | A runtime-options environment variable that preloads the instrumentation module | Supported libraries and module systems vary by version |
| Python | Instrumentation packages and bootstrap files | Search-path settings that make the interpreter load them at startup | Image platform and library compatibility must match |
| .NET | Profiler and managed instrumentation files | Runtime profiler settings that enable the profiler and point to its files | Needs a compatible runtime and operating system image |
| Go | A privileged helper running as an extra container beside the application | Process sharing and a target executable path, using kernel-level tracing | Needs elevated permissions; generally treated as less mature |
| Apache HTTP Server | An instrumentation module | Server configuration changes that load the module | Applies to the web server layer, not application code behind it |
| Nginx | An instrumentation module | Server configuration changes that load the module | Same: instruments the proxy, not the services behind it |
Java: the most mature path
Java has the longest history and the richest coverage of frameworks and libraries. The injected agent is loaded by the Java runtime through its standard startup-options variable. The Operator adds its own option to whatever the container already specifies, so existing tuning is normally preserved. Teams should still measure startup time and memory, because bytecode rewriting is not free.
Node.js and Python: lightweight attachment
These runtimes load instrumentation as ordinary modules at startup. The init container supplies those modules and the environment variables arrange for them to run first. Compatibility with specific framework versions matters, so teams should check the published list of supported libraries rather than assuming everything is covered.
.NET: profiler-based
The .NET path uses a profiler component that the runtime loads when special settings are present. Those settings include paths into the shared volume. Operating-system flavor matters, as there are separate builds for common Linux variants, so match the agent to the base image your applications use.
Go: a different world
Because Go programs are compiled into a single executable with little room for runtime hooks, the Operator’s Go support uses an external observer based on kernel-level tracing technology. The Operator adds an extra container that shares the process space with the application and needs elevated permissions. That is a significant security consideration, and the feature is generally considered less mature than the others. Many organizations use manual instrumentation for Go services, or adopt this feature only in controlled environments after careful review.
For most languages, injection is like fitting a gadget into a toy that has an open battery compartment. For Go, the toy is sealed, so the Operator places a camera outside the room and watches through the window. The camera is powerful but needs special access, which is why it is used with extra care.
Web servers: instrumenting the front door
The Apache and Nginx options place a module into the web server so that it records requests entering and leaving. They are valuable for gaining visibility at the edge of a system or in a gateway tier. Remember that they instrument the server itself and not the applications behind it.
If an application already carries a vendor agent or already includes its own OpenTelemetry setup, injecting another agent can create duplicate spans, conflicting settings or even startup failures. Decide on one approach per workload. The SDK-only injection mode exists for the case where the application already has the libraries and only needs shared configuration.
9What Gets Configured Automatically
Besides loading the agent, the Operator fills in the settings that normally cause the most mistakes.
Identity: who produced this data
The Operator derives a service name from pod metadata, using details such as the owning workload’s name, so that data is attributable without the application declaring it. It also adds Kubernetes context as resource attributes: the namespace, the pod name, the node, the container and information about the owning workload. These attributes are what let a trace be traced back to a specific deployment and pod during an incident.
It is like a mailroom stamping every outgoing letter with the sender’s department, floor and desk number. The author only wrote the letter, but anyone who receives it can find where it came from.
Destination and protocol
The endpoint from the Instrumentation resource is injected as an environment variable that the agent reads. Each language’s agent uses either a binary streaming style or an HTTP style of the OpenTelemetry Protocol, and the default port and protocol can differ by language and by Operator version. Platform teams must make sure the chosen endpoint listens on the protocol and port that the injected agents will use.
Context propagation and sampling
Propagators decide how trace context is carried in request headers between services. Setting them centrally means every instrumented service speaks the same convention, so traces stitch together across teams. The sampler rule, usually a percentage that honors the upstream caller’s decision, is also applied uniformly, which gives predictable data volume.
Who wins when settings collide
Settings can come from several places: the Instrumentation resource, environment variables already present in the application container, pod-level annotations and the agent’s own defaults. In general, a variable explicitly defined in the application container tends to take precedence over one the Operator would add, and language-specific values override common ones. Because exact precedence rules can vary between versions and settings, test the combination you actually use before relying on an override.
| Setting area | Typical source | Who should own it |
|---|---|---|
| Exporter endpoint | Instrumentation resource | Platform team |
| Propagators and sampler | Instrumentation resource | Platform team, with agreed exceptions |
| Service name | Derived from workload metadata, overridable | Application team within naming rules |
| Business attributes such as team or tier | Resource attributes setting or pod metadata | Application team, within a data contract |
| Agent version | Agent image reference | Platform team |
Because service names are derived automatically, a sloppy workload name becomes a sloppy service name in every dashboard. Agree on workload naming rules before you roll out at scale, since renaming a service later breaks historical comparisons.
10Connecting Injected Agents to the Collector Pipeline
Injection decides how data is produced. A Collector pipeline decides what happens to it next, and the two must fit together.
Where should the agents send data?
Injected agents need a destination that is stable, nearby and fast. Three common choices exist, each matching a deployment shape for Collectors.
Choice 1 — A node-level agent Collector
Each node runs a Collector, and injected agents send to the one on their own node. Traffic stays local, delivery is quick, and the Collector adds node and cluster context. This is the typical default for platforms.
Choice 2 — A sidecar Collector
A Collector runs inside each pod beside the application, which the Operator can also inject. This offers strong isolation and per-application tuning, at a cost in resources for every pod.
Choice 3 — A central gateway service
Agents send directly to a shared gateway through a stable service address. This is simplest to configure but adds network distance and makes the gateway a heavier dependency for every application.
flowchart LR
subgraph Pod["Application pod"]
APP["Application with injected agent"]
end
APP -->|"OTLP"| AGENT["Node-level Collector"]
AGENT -->|"OTLP"| GW["Gateway Collector pool"]
GW --> TR["Trace store"]
GW --> ME["Metrics store"]
GW --> LO["Log store"]
IR["Instrumentation resource"] -.->|"Supplies endpoint setting"| APP
Keeping the address stable
The endpoint in the Instrumentation resource is effectively a contract. If a platform team re-shapes its Collector tiers, the address that applications use should not change, otherwise every pod must be recreated to pick up the new value. Using a stable service name, or a node-local address, protects application teams from internal changes.
Logs and metrics from injected workloads
Auto-instrumentation chiefly produces traces and, for many languages, runtime and framework metrics. Logs are usually collected separately, by reading container output through a node-level Collector. A well-designed platform makes the three line up by giving them the same Kubernetes resource attributes, so that an engineer can jump from a slow trace to the metrics and logs of the very same pod.
If the endpoint expects one transport style and the injected agent uses another, or if encryption is required but the agent is not configured for it, data is silently lost or connections fail repeatedly. Verify protocol, port and security settings for every language you inject.
11Installation and Prerequisites
Injection depends on cluster-level plumbing, so a handful of prerequisites decide whether it works at all.
Provide certificates for the webhook
Kubernetes only calls webhooks over secure connections it trusts. The Operator can obtain certificates from a certificate manager running in the cluster, or in some installation methods generate its own. Choose the approach that matches your cluster’s standards for rotation and trust.
Install the Operator
Use a supported method such as the project’s packaged manifests or its Helm chart, pinned to a specific version. Review the permissions it requests and where it runs.
Enable the languages you need
Some languages are enabled by default, while others are opt-in through Operator startup settings. Enable only what you plan to support, since each enabled language is more surface to maintain and secure.
Ensure the control plane can reach the webhook
In clusters with strict network rules, such as private clusters managed by cloud providers, the control plane may need an explicit firewall or network-policy allowance to reach the webhook port. This is a very common cause of silent failure.
Create the Collectors and the Instrumentation resource
Deploy the Collector tiers first, then the Instrumentation resource pointing at them, so injected pods always have somewhere to send data from their very first start.
Decide where agent images come from
The default agent images are published by the project in a public registry. Many organizations mirror them into a private registry, scan them and pin versions, then refer to those copies from the Instrumentation resource.
Webhook failure behavior
Every webhook has a policy for what Kubernetes should do if the webhook cannot be reached. If the policy blocks pod creation on failure, an unavailable Operator could stop applications from starting. If it ignores failure, pods start but are silently uninstrumented. Understand which behavior your installation uses and decide deliberately, because it trades safety of deployments against completeness of telemetry.
Imagine an airport security checkpoint with a power cut. One policy says “close the gate until the scanner returns”, which keeps everyone safe but stops all flights. The other says “let passengers through unchecked”, which keeps flights moving but loses the safety check. Neither is wrong, but the choice must be made on purpose.
Stand up the Operator in a disposable cluster first. Create one annotated sample workload per language you need and confirm the pod changes, the init container behavior and the first spans before touching a shared environment.
12Rollout and Lifecycle Management
Because injection happens at pod creation, every change travels through restarts, and restarts need planning.
Turning it on for a workload
Adding the annotation to the pod template triggers the normal rolling update of the deployment, since a template change means new pods. As each new pod is created, it is instrumented. The old pods continue to run, uninstrumented, until they are replaced. No special action is needed beyond the usual rollout, but the change should still be treated as a deployment with its own risk.
Changing the shared settings
Changing an Instrumentation resource affects only pods created afterward. To apply a new endpoint or sampling rule across a fleet, workloads must be restarted gradually. Platform teams therefore schedule fleet-wide restarts in controlled waves rather than all at once, watching error rates and resource use as each wave completes.
Upgrading agent versions
Agent versions are chosen by the image reference in the Instrumentation resource. A new agent can change behavior: new spans, renamed attributes, different overhead. Treat agent upgrades like application releases. Test them in a staging environment with realistic traffic, then roll out by environment or by team, and keep the ability to return to the previous image.
flowchart LR
A["Propose agent or setting change"] --> B["Test in staging with real traffic"]
B --> C["Canary on a few workloads"]
C --> D{"Healthy?"}
D -->|"Yes"| E["Restart in waves"]
D -->|"No"| F["Revert image or resource"]
E --> G["Monitor drops, errors and overhead"]
F --> A
Opting out and emergency stops
Provide a simple way to switch injection off for a workload that misbehaves: setting the annotation value to false, or removing it, then restarting. For emergencies, the platform team can also disable a language or scale down the Operator’s webhook, remembering the failure-policy consequences from the previous chapter. Document these steps in a runbook so nobody discovers them during an outage.
Because injection depends on pod recreation, workloads that restart rarely will keep old settings for a very long time. Fleets can quietly drift into dozens of agent versions. Track which agent image each running pod uses, and enforce a maximum age.
13Platform Governance and Golden Paths
Injection becomes a true platform feature when onboarding is nearly effortless and guardrails are automatic.
The golden path
A golden path is the easiest, best-supported route to do something. For injection, the golden path might be a starter template for new services that already carries the right annotation, or a deployment package that adds it based on a single option such as the service language. When the easy route is also the correct route, adoption follows without pressure.
Automating the annotation
Policy engines for Kubernetes can add annotations to pods or workloads automatically, according to rules such as namespace, label or team. This lets a platform team enable injection for a whole class of workloads without asking each team to edit manifests. The usual caution applies: automatic enablement should be gradual, announced and reversible.
Per-namespace Instrumentation
Each environment or team gets a resource with appropriate sampling and endpoints, with production tuned more carefully than development.
Allowed languages list
Only languages with tested, supported agents are enabled, and risky options such as Go’s privileged helper are limited to approved areas.
Naming and attribute contract
Rules for workload names and required resource attributes keep data consistent across teams.
Image allowlist
Only mirrored, scanned and approved agent images may be referenced, closing a supply chain gap.
Opt-out with a reason
Teams can opt out when injection causes problems, but the platform records why and follows up.
Coverage reporting
Dashboards show which workloads are instrumented, by which agent version, and which have drifted or opted out.
Cost awareness
Auto-instrumentation can dramatically increase data volume overnight, since every service starts producing spans for every request. Combine injection with sensible default sampling and with filtering in the Collector tier, and show each team its volume. Surprise bills are the fastest way to lose organizational support for a platform.
A healthy ownership split
The platform team owns the Operator, the Collectors, the Instrumentation resources, the agent images and the policies. Application teams own the annotation on their workloads, their service names within the contract and any manual instrumentation for business detail. Both teams can state in one sentence what the other is responsible for.
14Security and Compliance
Injection modifies running workloads automatically, so it deserves the same scrutiny as any component with that much power.
The Operator is a privileged actor
The Operator’s webhook can alter any pod it is allowed to see. It therefore sits in a position of high trust. Limit who can modify the Operator’s deployment and its custom resources, restrict which namespaces it acts on where possible, and keep its version current. A compromised webhook could add harmful containers to every new pod in the cluster, so it deserves protection comparable to other cluster-wide controllers.
The customs officer from earlier has authority to open and modify every suitcase. That is useful only if the officer is thoroughly vetted, supervised and cannot be impersonated. The same is true of the webhook.
Supply chain of agent images
The init container runs an image, and that image’s contents execute inside your application’s pod. Treat agent images as software dependencies: mirror them to a registry you control, scan them for known vulnerabilities, pin them by exact version or digest, and update them on a schedule. Never let the Instrumentation resource point at an unreviewed image.
Permissions and pod security
For most languages, the injected init container needs only ordinary permissions and write access to a shared volume. Clusters with strict pod security rules should confirm that the injected additions, including the extra volume and container, comply with those rules. The Go option is the notable exception because it needs elevated permissions and shared process visibility, so it should be allowed only where those privileges are explicitly accepted.
What the agent may capture
Agents record details such as web addresses, headers and, for some database libraries, parts of queries. Some of that may contain sensitive values. Review the defaults for each language and agent version, disable capturing of sensitive headers or statements where needed, and rely on central masking in the Collector pipeline as a second line of defense. Compliance questions about what leaves a workload are easier to answer when settings are controlled in one shared resource.
The injected agents send telemetry across the cluster network. Use encrypted connections to Collectors where your policy requires it, and apply network policies so that only intended pods can reach Collector endpoints. Telemetry often reveals internal structure, which is valuable to an attacker.
Audit and change control
Keep Instrumentation resources, Operator configuration and policies in version control, require review for changes and record who changed what. When auditors ask how a service came to emit certain data, the answer should be a short walk through a repository history rather than a search through people’s memories.
15Performance and Resource Impact
Automatic instrumentation is free of developer effort, not free of cost, and the cost should be measured rather than assumed.
Where the overhead appears
Startup time
Loading an agent and rewriting classes or patching libraries takes time, which can be noticeable for large applications and short-lived jobs.
Memory
Agents add their own data structures and buffers, so memory limits tuned before injection may become too tight.
CPU per request
Creating spans, copying attributes and exporting data consumes some processing on every request, which is small individually but adds up under heavy load.
Network traffic
More telemetry leaves each pod, so bandwidth and Collector load grow with traffic.
Pod start delay
The init container must finish before the application begins, which adds a small delay to every pod start, including during scaling events.
Sizing the init container
The init container runs briefly, but Kubernetes still counts its resource requests when scheduling the pod. The Instrumentation resource lets you set resource requests and limits for the init container per language. Keep them modest, yet not so tight that copying fails or is slow on busy nodes.
Measure before and after
Compare latency percentiles, error rates, memory and CPU for the same workload before and after injection, at realistic load. Do this in a staging environment first, and again for a canary in production. Where overhead is too high, use sampling to reduce exported volume, disable instrumentation for noisy libraries where the agent supports that, or consider manual instrumentation of only the most important paths.
A fitness tracker adds a little weight to your wrist. Most people never notice, but a marathon runner might. You test it on your own run before deciding whether to wear it on race day.
After adding an agent, review memory limits for instrumented workloads. A container that was fine at its old limit can be restarted repeatedly by the system for exceeding memory once the agent is loaded, which looks like an application bug but is really a sizing gap.
16Advantages, Disadvantages and Trade-offs
Injection is powerful but not magic. A clear view of both sides prevents disappointment.
Advantages
- Instruments applications with no code change and no image rebuild
- Works for legacy and third-party software you cannot modify
- One shared resource controls endpoint, sampling and propagation for many workloads
- Adds consistent Kubernetes context to every span automatically
- Lets platform teams roll out observability without a sprint of developer time
- Easy to opt in per workload and easy to switch off
Disadvantages / Trade-offs
- Covers only supported libraries and frameworks, so custom code appears as gaps
- Adds startup time, memory and CPU overhead
- Applies only at pod creation, so changes need restarts
- The webhook is a privileged cluster component that must be secured and kept reachable
- Behavior and supported options vary by Operator and agent version
- Some languages, notably Go, rely on less mature or more privileged approaches
Automatic versus manual, side by side
| Question | Injected auto-instrumentation | Manual instrumentation |
|---|---|---|
| Developer effort | Very low | Moderate to high |
| Coverage of common frameworks | Broad and immediate | Only what developers add |
| Business meaning in spans | Generic | Rich and specific |
| Control over detail and overhead | Limited to settings | Full control |
| Works on code you cannot change | Yes | No |
| Consistency across teams | High, by shared settings | Depends on each team |
The best answer is usually both
Most mature organizations use injection to get broad, uniform baseline coverage quickly, then add manual instrumentation for the handful of business operations where generic spans are not meaningful enough. The two work together, since manual spans created through the OpenTelemetry API attach to the same traces that the agent creates.
17Design Patterns and Anti-Patterns
The same lessons come up in almost every adoption; here they are in one place.
Patterns that work
Platform-owned Instrumentation
One vetted resource per environment, owned by the platform team, referenced by workloads across namespaces.
Node-local destination
Injected agents send to a Collector on their own node, keeping traffic short and reliable.
Pilot, then widen
Start with a few friendly services, measure overhead and quality, then expand in waves.
Mirrored, pinned images
Agent images come from a private registry, scanned and pinned, never from an unreviewed source.
Auto plus manual
Use injection for the baseline and manual spans for the business-critical steps.
Coverage dashboard
Track which workloads are instrumented, with which agent version and with what drift.
Anti-patterns to avoid
Problem
The team adds the opt-in annotation to the deployment object’s own metadata rather than to its pod template, then reports that injection is broken.
Why It’s Harmful
The webhook never sees the annotation, so nothing happens and nobody knows why. Time is lost debugging the Operator when the manifest is at fault.
Correct Approach
Place the annotation on the pod template, add a check in review that confirms the location, and verify a freshly created pod for the injected init container.
Problem
Injection is enabled for every namespace overnight, with full sampling and no warning to teams.
Why It’s Harmful
Data volume and cost jump suddenly, startup times and memory use change in production without preparation, and teams lose trust in the platform.
Correct Approach
Roll out gradually, with conservative default sampling, announced waves, per-team volume reports and a documented way to opt out.
Problem
Applications that already include a vendor agent or their own OpenTelemetry setup are also injected.
Why It’s Harmful
Duplicate spans, conflicting configuration and occasional startup failures appear, and every dashboard double counts requests.
Correct Approach
Choose one instrumentation approach per workload. For applications with their own libraries, use the SDK-only mode for consistent settings, or skip injection.
Problem
Agent images are pulled from the public registry at the latest version, with no pinning and no scanning.
Why It’s Harmful
A surprise change in agent behavior can alter every service at the next restart. An unreviewed image runs inside every instrumented pod, which is a supply chain risk.
Correct Approach
Mirror, scan and pin images, test updates in staging and promote them deliberately.
Problem
The webhook is installed without anyone deciding what should happen if it becomes unreachable, and without alerts on its health.
Why It’s Harmful
Either application deployments are blocked during an Operator outage, or workloads quietly start without instrumentation and blind spots appear exactly when systems are stressed.
Correct Approach
Choose the failure policy on purpose, run multiple Operator replicas where supported, monitor webhook availability and report coverage so silent gaps are noticed.
18Troubleshooting Guide
When injection does not work, the cause is almost always one of a short list. Work from the outside in.
flowchart TD
S["Pod has no agent or no telemetry"] --> Q1{"Init container and env vars present?"}
Q1 -->|"No"| Q2{"Annotation on pod template?"}
Q2 -->|"No"| F1["Move annotation to pod template and restart"]
Q2 -->|"Yes"| Q3{"Language enabled and Instrumentation found?"}
Q3 -->|"No"| F2["Enable language, fix name or namespace reference"]
Q3 -->|"Yes"| F3["Check webhook reachability, certificates and logs"]
Q1 -->|"Yes"| Q4{"Application starts normally?"}
Q4 -->|"No"| F4["Check memory limits, conflicts and agent compatibility"]
Q4 -->|"Yes"| Q5{"Collector receives data?"}
Q5 -->|"No"| F5["Check endpoint, protocol, port and network policy"]
Q5 -->|"Yes"| F6["Check sampling, filters and backend routing"]
Symptom and cause table
| Symptom | Likely cause | What to check |
|---|---|---|
| New pods show no extra init container | Annotation on the wrong object, or webhook not called | Annotation location, webhook logs, API server reachability to webhook |
| Init container appears but pod will not start | Image cannot be pulled, or init step fails | Registry access, image name, init container logs, resource limits |
| Application crashes or restarts after injection | Memory limit too low, or conflict with an existing agent | Memory use, restart reasons, existing startup options |
| Instrumentation resource not applied | Wrong name or namespace in the annotation | Resource existence, namespace, spelling |
| Spans arrive twice | Two instrumentation sources active | Existing vendor agents, in-code SDK setup |
| Data never arrives at the Collector | Wrong endpoint, protocol mismatch or blocked traffic | Environment variables, Collector listeners, network policies |
| Service names look wrong | Derived from poorly named workloads | Workload names, overrides, resource attribute settings |
| Some pods instrumented, others not | Old pods predate the annotation or setting | Pod age, rollout status, restart policy |
Diagnosing a missing delivery works the same way. First check whether the parcel was even handed to the courier, then whether the courier had the right address, then whether the destination building was open. Walking through the chain in order is faster than guessing at the end.
Before anything else, look at a newly created pod to see whether it was mutated, confirm the annotation sits on the pod template, and read the Operator’s logs for admission decisions. These three steps resolve a large share of reports.
19Real-World Scenarios
These are composite scenarios drawn from common patterns, shown to illustrate how the ideas combine in practice.
Scenario 1 — Instant visibility for an inherited estate
A company acquires another business whose services have no tracing, and the original developers have left. The platform team annotates the Java and Node.js workloads in a staging namespace, confirms traces appear, and rolls the change into production in waves. Within days, the architecture can be seen for the first time, without touching a line of the acquired code.
Scenario 2 — Uniform standards across forty teams
Forty teams each configured tracing differently, with different sampling and naming. The platform replaces those with one Instrumentation resource per environment, moves teams to SDK-only or full injection, and retires dozens of private configurations. Traces now line up across teams because propagation and naming finally match.
Scenario 3 — A cautious regulated rollout
A financial firm must prove that sensitive values never leave workloads. The platform pins mirrored agent images, disables capture of selected headers and statements through shared settings, encrypts traffic to Collectors and stores every change in reviewed repositories. Auditors receive a clear trail from setting to behavior.
Scenario 4 — Finding a hidden slow dependency
Customers report intermittent slowness. After injection, traces reveal that one rarely noticed internal service makes repeated calls to a slow reporting database. The fix is made in minutes once the evidence exists, where previously weeks of guessing had passed.
Scenario 5 — Memory pressure after rollout
After injecting a Java agent, several services begin restarting under load. The cause turns out to be memory limits sized without the agent. The platform team raises limits for instrumented workloads, adds a pre-rollout check that compares memory before and after, and updates its onboarding guide.
Scenario 6 — Handling the language that does not fit
Go services make up a large part of a platform, but the privileged helper is not acceptable under the company’s security rules. The team keeps injection for other languages, adopts manual instrumentation with a shared internal library for Go, and documents the decision so nobody revisits it without new facts.
20A Maturity Roadmap and Best Practices
A staged path keeps each step useful, and a short list of habits keeps the whole effort calm.
Stage 1 — Prove it in a test cluster
Install the Operator, enable one or two languages, and instrument sample workloads. Learn how pods change and where data lands.
Stage 2 — Wire it to the pipeline
Point the Instrumentation resource at a node-local Collector, add enrichment and masking in the pipeline, and confirm end-to-end flow.
Stage 3 — Pilot with volunteers
Instrument a small set of willing services, measure overhead and data quality, and fix surprises in the platform, not in the applications.
Stage 4 — Harden
Mirror and pin images, set the webhook failure policy deliberately, add monitoring for the Operator and write runbooks.
Stage 5 — Scale through golden paths
Offer templates and policy-driven enablement, publish coverage and volume reports, and roll out in waves by team.
Stage 6 — Refine and complement
Tune sampling and settings from evidence, add manual instrumentation where business meaning is missing and keep agents on a regular upgrade schedule.
flowchart LR
A["Stage 1 Prove"] --> B["Stage 2 Wire to pipeline"]
B --> C["Stage 3 Pilot"]
C --> D["Stage 4 Harden"]
D --> E["Stage 5 Scale"]
E --> F["Stage 6 Refine"]
Best Practices
- Keep annotations on pod templates and verify with a fresh pod
- Own Instrumentation resources in the platform team, one per environment
- Send to a stable, nearby Collector address
- Mirror, scan and pin agent images
- Measure latency, memory and CPU before and after
- Roll out in waves and report volume and coverage
- Choose one instrumentation approach per workload
- Add manual spans for business-critical steps
Common Mistakes
- Expecting running pods to change when the shared resource changes
- Leaving memory limits unchanged after adding an agent
- Enabling every language when only a few are needed
- Forgetting network and firewall access to the webhook
- Using the latest agent image without testing
- Naming workloads carelessly and inheriting poor service names
- Treating the Go option like the other languages
- Never checking which pods are actually instrumented
The Operator and its agents evolve quickly. Supported languages, annotation names, defaults and stability levels change between releases. Read release notes before upgrades, and treat the official OpenTelemetry documentation as the source of truth for details that may have changed since this tutorial was written.
21Frequently Asked Questions
Quick answers to the questions that come up first.
No. The Operator modifies the pod definition at creation time, adding an init container, a shared volume and environment variables. The application’s image and source stay exactly as they were.
The most common reasons are that the annotation is on the deployment instead of the pod template, the language is not enabled in the Operator, the referenced Instrumentation resource cannot be found, or the webhook is unreachable. Existing pods also do not change until they are recreated.
No. Injection happens at pod creation only. Restart the workload, ideally through a controlled rolling restart, so that new pods pick up the new settings.
Yes. By default the first container is targeted, and a separate annotation lets you name the containers to instrument. This is useful for pods that include proxies or helper containers.
Agent injection delivers instrumentation files and activates them. SDK-only injection just sets the shared configuration values, such as endpoint and sampling, for applications that already include OpenTelemetry libraries in their own code.
It adds some startup time, memory and CPU overhead, usually modest but real. Measure it in staging and in a canary, adjust memory limits and sampling, and decide based on evidence rather than assumption.
No. Go uses an external, kernel-level approach that needs elevated permissions and a target executable path, and it is generally considered less mature. Many organizations use manual instrumentation for Go or limit the feature to controlled environments.
It depends on the webhook’s failure policy. Pod creation may be blocked, or pods may start without instrumentation. Know which your installation uses, and monitor the Operator so that a failure is noticed quickly.
Not safely on the same workload. Running two instrumentation sources can create duplicate data and conflicts. Choose one approach per workload, or use SDK-only settings if the application already contains OpenTelemetry libraries.
Injection produces telemetry inside workloads and sends it to an endpoint defined in the Instrumentation resource. A Collector pipeline then enriches, filters, samples and routes that data. They are complementary halves of an observability platform.
In the official OpenTelemetry Operator documentation and release notes. Names, defaults and maturity levels change between versions, so always confirm them for the version you run.
22Summary and Key Takeaways
Auto-instrumentation injection via the Operator turns observability from a per-application project into a platform capability. A mutating webhook rewrites each annotated pod as it is created, an init container delivers the language agent, shared settings from one Instrumentation resource tell it where to send data, and the application starts producing traces without a single code change. Like the mechanic at the hangar door in the opening picture, the Operator fits the same standard recorder to every aircraft that rolls through, without disturbing the people who built them.
The approach is strongest when it is treated as a managed service: gradual rollouts, pinned images, sized resources, monitored webhooks and clear ownership. It does not replace thoughtful manual instrumentation, but it gives every team a broad, consistent baseline on day one and frees developers to spend their effort where business meaning lives.
Key Takeaways
- Injection edits the pod, not the application — a mutating webhook adds an init container, a shared volume and environment variables at pod creation.
- The annotation is the opt-in — it must sit on the pod template, and its value selects which Instrumentation resource to use.
- One Instrumentation resource serves many workloads — endpoint, propagators, sampler and agent images are managed centrally by the platform team.
- Changes apply only to new pods — plan restarts in waves, track agent versions and enforce a maximum age.
- Languages differ — Java, Node.js, Python and .NET load agents through runtime settings, while Go uses a privileged external helper that needs special care.
- Treat it as privileged infrastructure — secure the webhook, mirror and pin agent images, choose the failure policy deliberately and monitor coverage.
- Combine automatic and manual — injection gives broad baseline visibility, and manual spans add the business detail that generic agents cannot know.