Skip to content

Commit f720257

Browse files
committed
Add sdk-only support for opentelemetry-autoinstrumentation
Signed-off-by: Israel Blancas <iblancasa@gmail.com>
1 parent 69b3284 commit f720257

7 files changed

Lines changed: 155 additions & 3 deletions

File tree

otel-integration/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## OpenTelemetry-Integration
44

5+
### v0.0.326 / 2026-07-13
6+
7+
- [Chore] Bump OpenTelemetry Operator chart dependency to 0.119.0
8+
- [Feat] Add SDK-only injection support for applications with manual instrumentation
9+
510
### v0.0.325 / 2026-07-07
611

712
- [Chore] Bump chart dependency to opentelemetry-collector 0.134.4

otel-integration/k8s-helm/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
apiVersion: v2
22
name: otel-integration
33
description: OpenTelemetry Integration
4-
version: 0.0.325
4+
version: 0.0.326
55
keywords:
66
- OpenTelemetry Collector
77
- OpenTelemetry Agent
@@ -16,7 +16,7 @@ dependencies:
1616
condition: opentelemetry-agent.enabled
1717
- name: opentelemetry-operator
1818
alias: opentelemetry-autoinstrumentation
19-
version: "0.115.1"
19+
version: "0.119.0"
2020
repository: https://open-telemetry.github.io/opentelemetry-helm-charts
2121
condition: opentelemetry-autoinstrumentation.enabled
2222
- name: opentelemetry-collector

otel-integration/k8s-helm/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,30 @@ Supported annotations:
205205
instrumentation.opentelemetry.io/inject-java: "true"
206206
instrumentation.opentelemetry.io/inject-python: "true"
207207
instrumentation.opentelemetry.io/inject-dotnet: "true"
208+
instrumentation.opentelemetry.io/inject-sdk: "true"
208209
```
209210
210211
Java and .NET use OTLP/gRPC to `http://$(OTEL_NODE_IP):4317`. Python uses OTLP HTTP/protobuf to `http://$(OTEL_NODE_IP):4318`.
211212

213+
#### SDK-only Injection
214+
215+
The `inject-sdk` annotation enables SDK-only injection mode for applications that are already manually instrumented or cannot be auto-instrumented with language-specific agents. This mode injects only OpenTelemetry SDK environment variables without adding init containers or modifying the application binary.
216+
217+
Use this mode when:
218+
- Your application is already instrumented with OpenTelemetry SDK
219+
- You want centralized configuration of SDK behavior via Kubernetes annotations
220+
- Language-specific auto-instrumentation is not available or compatible
221+
222+
Example pod annotation:
223+
224+
```yaml
225+
metadata:
226+
annotations:
227+
instrumentation.opentelemetry.io/inject-sdk: "true"
228+
```
229+
230+
The injected environment variables will configure the SDK to send telemetry to the `opentelemetry-agent` DaemonSet on the same node using `http://$(OTEL_NODE_IP):4317`.
231+
212232
> [!IMPORTANT]
213233
>
214234
> Do not enable `opentelemetry-autoinstrumentation` in a cluster that already has another OpenTelemetry Operator webhook installed, unless the webhook names and selectors are configured to avoid collisions.

otel-integration/k8s-helm/e2e-test/instrumentation_webhook_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,110 @@ func eventually(timeout, interval time.Duration, check func() (bool, error)) err
494494
}
495495
return fmt.Errorf("condition not met within %s", timeout)
496496
}
497+
498+
func TestE2E_SDKInjection(t *testing.T) {
499+
kubeconfigPath := testKubeConfig
500+
if kubeConfigFromEnv := os.Getenv(kubeConfigEnvVar); kubeConfigFromEnv != "" {
501+
kubeconfigPath = kubeConfigFromEnv
502+
}
503+
504+
k8sClient, err := xk8stest.NewK8sClient(kubeconfigPath)
505+
require.NoError(t, err)
506+
507+
require.NoError(t, waitForInstrumentationWebhookManager(k8sClient))
508+
509+
ns := fmt.Sprintf("sdk-injection-e2e-%s", uuid.NewString()[:8])
510+
nsObj := &unstructured.Unstructured{Object: map[string]any{
511+
"apiVersion": "v1",
512+
"kind": "Namespace",
513+
"metadata": map[string]any{
514+
"name": ns,
515+
},
516+
}}
517+
_, err = k8sClient.DynamicClient.Resource(corev1.SchemeGroupVersion.WithResource("namespaces")).Create(context.Background(), nsObj, metav1.CreateOptions{})
518+
require.NoError(t, err)
519+
t.Cleanup(func() {
520+
_ = xk8stest.DeleteObject(k8sClient, nsObj)
521+
})
522+
523+
tracesConsumer := new(consumertest.TracesSink)
524+
shutdownSink := StartUpSinks(t, ReceiverSinks{
525+
Traces: &TraceSinkConfig{
526+
Consumer: tracesConsumer,
527+
Ports: &ReceiverPorts{
528+
Grpc: 4321,
529+
},
530+
},
531+
})
532+
defer shutdownSink()
533+
534+
tests := []struct {
535+
name string
536+
image string
537+
port int
538+
path string
539+
command []string
540+
expectedEnvs []string
541+
noExpectedInit bool
542+
}{
543+
{
544+
name: "sdk-only-python",
545+
image: "ghcr.io/open-telemetry/opentelemetry-operator/e2e-test-app-python:main",
546+
port: 8080,
547+
path: "/",
548+
expectedEnvs: []string{
549+
"OTEL_EXPORTER_OTLP_ENDPOINT",
550+
"OTEL_NODE_IP",
551+
"OTEL_TRACES_SAMPLER",
552+
"OTEL_LOGS_EXPORTER",
553+
},
554+
noExpectedInit: true,
555+
},
556+
{
557+
name: "sdk-only-java",
558+
image: "ghcr.io/open-telemetry/opentelemetry-operator/e2e-test-app-java:main",
559+
port: 8080,
560+
path: "/",
561+
expectedEnvs: []string{
562+
"OTEL_EXPORTER_OTLP_ENDPOINT",
563+
"OTEL_NODE_IP",
564+
"OTEL_TRACES_SAMPLER",
565+
"OTEL_LOGS_EXPORTER",
566+
},
567+
noExpectedInit: true,
568+
},
569+
}
570+
571+
for _, tc := range tests {
572+
t.Run(tc.name, func(t *testing.T) {
573+
deployment := instrumentationWebhookDeployment(tc.name, ns, "", tc.image, tc.port, tc.path, tc.command, nil, "")
574+
created, err := k8sClient.DynamicClient.Resource(appsV1Deployments()).Namespace(ns).Create(context.Background(), deployment, metav1.CreateOptions{})
575+
require.NoError(t, err)
576+
t.Cleanup(func() {
577+
_ = xk8stest.DeleteObject(k8sClient, created)
578+
})
579+
580+
baselinePod := waitForDeploymentReadyPod(t, k8sClient, ns, created.GetName(), "")
581+
curlPod(t, k8sClient, ns, baselinePod.GetName(), tc.port, tc.path)
582+
requireNoTraceForPod(t, tracesConsumer, baselinePod.GetName(), 15*time.Second)
583+
584+
err = injectDeploymentInstrumentation(k8sClient, ns, created.GetName(), "instrumentation.opentelemetry.io/inject-sdk")
585+
require.NoError(t, err)
586+
587+
instrumentedPod := waitForDeploymentReadyPod(t, k8sClient, ns, created.GetName(), baselinePod.GetName())
588+
589+
if tc.noExpectedInit {
590+
initContainers, found, _ := unstructured.NestedSlice(instrumentedPod.Object, "spec", "initContainers")
591+
if found && len(initContainers) > 0 {
592+
t.Errorf("SDK-only injection should not add init containers, but found %d init containers", len(initContainers))
593+
}
594+
}
595+
596+
for _, envName := range tc.expectedEnvs {
597+
require.Truef(t, containerHasEnv(instrumentedPod, "app", envName), "expected env %q on container 'app' in pod %s", envName, tc.name)
598+
}
599+
600+
t.Logf("SDK-only injection verified for %s - environment variables injected without init containers", tc.name)
601+
})
602+
}
603+
}

otel-integration/k8s-helm/e2e-test/run-all.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ TestE2E_DeltaToCumulativePreset|linux|component=agent-collector||./values.yaml .
9191
TestE2E_SpanMetricsConnector|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-span-metrics.yaml
9292
TestE2E_SpanSanitization|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-test.yaml
9393
TestE2E_InstrumentationWebhookNoCRDs|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-test.yaml ./e2e-test/testdata/values-e2e-instrumentation-webhook.yaml
94+
TestE2E_SDKInjection|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-test.yaml ./e2e-test/testdata/values-e2e-sdk-injection.yaml
9495
EOF
9596
}
9697

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
opentelemetry-autoinstrumentation:
2+
enabled: true
3+
4+
opentelemetry-agent:
5+
config:
6+
service:
7+
pipelines:
8+
traces:
9+
exporters:
10+
- otlp/traces
11+
- debug

otel-integration/k8s-helm/values.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ global:
55
defaultSubsystemName: "integration"
66
logLevel: "info"
77
collectionInterval: "30s"
8-
version: "0.0.325"
8+
version: "0.0.326"
99
deploymentEnvironmentName: ""
1010

1111
extensions:
@@ -84,6 +84,14 @@ opentelemetry-autoinstrumentation:
8484
value: http://$(OTEL_NODE_IP):4317
8585
- name: OTEL_EXPORTER_OTLP_PROTOCOL
8686
value: grpc
87+
# SDK-only injection mode for applications that already have instrumentation
88+
# or cannot be auto-instrumented.
89+
sdk:
90+
env:
91+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
92+
value: http://$(OTEL_NODE_IP):4317
93+
- name: OTEL_EXPORTER_OTLP_PROTOCOL
94+
value: grpc
8795
# Webhook manager runs only on Linux nodes.
8896
nodeSelector:
8997
kubernetes.io/os: linux

0 commit comments

Comments
 (0)