Skip to content

Commit 0ca7cd3

Browse files
feat: add support for multi container instrumentation pod (#1901)
* feat(annotations): add new instrumentation specific annotations * feat(dotnet): add instrumentation specific volume and initcont * feat(go): update containers names specific annotation * feat(injection): improve check for instrumentation status * feat(java): add instrumentation specific volume and initcont * feat(nodejs): add instrumentation specific volume and initcont * feat(python): add instrumentation specific volume and initcont * feat(mutator): add possibility to specify instrumentation specific containers * feat(sdkinjection): add possibility to instrument language specific containers * chore(e2e): update tests * chore: update go.sum * chore(e2e): bring back container-names annotation * chore(e2e): add multi-instrumentation tests * feat(instr): add multi-instrumentation support feature gate * chore(e2e): add multi-instrumentation tests to actions * chore(go): update container-names annotation check * chore(e2e): fix tests * chore: add changelog * chore(e2e): add and fix tests * chore(go): fix go tests * chore(mutator): add helpers and tests * feat(instr): improve multi instrumentation and specific cases handling * chore(helper): sort duplicates - fix tests * chore(feature): add from version * chore(instr): move funcs to languageInstrumentations struct * chore(instr): avoid reflection * feat(docs): add information about multi-instrumentation support * Update pkg/instrumentation/podmutator.go Co-authored-by: Tyler Helmuth <12352919+TylerHelmuth@users.noreply.github.com> * chore(instr): add new cases * chore(instr): log error for invalid instr conf * chore(instr): ret err for duplicates, improve conditions * chore(go): improve containers check condition * feat(sdk): fix initcontainer caps * feat(sdk): fix securitycontext injection * feat: update featuregate version * feat: update fg registration version * chore: update tests with vol sizelimit * feat: update readme * fix: dotnet tests * fix dotnet e2e test * fix: sort only if duplicates * chore: featuregate update fromversion --------- Co-authored-by: Tyler Helmuth <12352919+TylerHelmuth@users.noreply.github.com>
1 parent 8f0c9f9 commit 0ca7cd3

File tree

51 files changed

+5005
-807
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+5005
-807
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. operator, target allocator, github action)
5+
component: operator
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: "Add support for multi instrumentation"
9+
10+
# One or more tracking issues related to the change
11+
issues: [1717]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

.github/workflows/e2e.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- e2e-upgrade
3030
- e2e-prometheuscr
3131
- e2e-autoscale
32+
- e2e-multi-instrumentation
3233

3334
steps:
3435
- name: Set up Go

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,14 @@ e2e-log-operator:
207207
kubectl get pod -n opentelemetry-operator-system | grep "opentelemetry-operator" | awk '{print $$1}' | xargs -I {} kubectl logs -n opentelemetry-operator-system {} manager
208208
kubectl get deploy -A
209209

210+
# end-to-tests for multi-instrumentation
211+
.PHONY: e2e-multi-instrumentation
212+
e2e-multi-instrumentation:
213+
$(KUTTL) test --config kuttl-test-multi-instr.yaml
214+
210215
.PHONY: prepare-e2e
211216
prepare-e2e: kuttl set-image-controller container container-target-allocator container-operator-opamp-bridge start-kind cert-manager install-metrics-server load-image-all deploy
212-
TARGETALLOCATOR_IMG=$(TARGETALLOCATOR_IMG) SED_BIN="$(SED)" ./hack/modify-test-images.sh
217+
TARGETALLOCATOR_IMG=$(TARGETALLOCATOR_IMG) OPERATOR_IMG=$(IMG) SED_BIN="$(SED)" ./hack/modify-test-images.sh
213218

214219
.PHONY: enable-prometheus-feature-flag
215220
enable-prometheus-feature-flag:

README.md

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ The possible values for the annotation can be
302302
* `"my-other-namespace/my-instrumentation"` - name and namespace of `Instrumentation` CR instance in another namespace.
303303
* `"false"` - do not inject
304304

305-
#### Multi-container pods
305+
#### Multi-container pods with single instrumentation
306306

307307
If nothing else is specified, instrumentation is performed on the first container available in the pod spec.
308308
In some cases (for example in the case of the injection of an Istio sidecar) it becomes necessary to specify on which container(s) this injection must be performed.
@@ -342,6 +342,90 @@ In the above case, `myapp` and `myapp2` containers will be instrumented, `myapp3
342342

343343
> 🚨 **NOTE**: Go auto-instrumentation **does not** support multicontainer pods. When injecting Go auto-instrumentation the first pod should be the only pod you want instrumented.
344344

345+
#### Multi-container pods with multiple instrumentations
346+
347+
Works only when `operator.autoinstrumentation.multi-instrumentation` feature is `enabled`.
348+
349+
Annotations defining which language instrumentation will be injected are required. When feature is enabled, specific for Instrumentation language containers annotations are used:
350+
351+
Java:
352+
```bash
353+
instrumentation.opentelemetry.io/java-container-names: "java1,java2"
354+
```
355+
356+
NodeJS:
357+
```bash
358+
instrumentation.opentelemetry.io/nodejs-container-names: "nodejs1,nodejs2"
359+
```
360+
361+
Python:
362+
```bash
363+
instrumentation.opentelemetry.io/python-container-names: "python1,python3"
364+
```
365+
366+
DotNet:
367+
```bash
368+
instrumentation.opentelemetry.io/dotnet-container-names: "dotnet1,dotnet2"
369+
```
370+
371+
Go:
372+
```bash
373+
instrumentation.opentelemetry.io/go-container-names: "go1"
374+
```
375+
376+
ApacheHttpD:
377+
```bash
378+
instrumentation.opentelemetry.io/apache-httpd-container-names: "apache1,apache2"
379+
```
380+
381+
SDK:
382+
```bash
383+
instrumentation.opentelemetry.io/sdk-container-names: "app1,app2"
384+
```
385+
386+
If language instrumentation specific container names are not specified, instrumentation is performed on the first container available in the pod spec (only if single instrumentation injection is configured).
387+
388+
In some cases containers in the pod are using different technologies. It becomes necessary to specify language instrumentation for container(s) on which this injection must be performed.
389+
390+
For this, we will use language instrumentation specific container names annotation for which we will indicate one or more container names (`.spec.containers.name`) on which the injection must be made:
391+
392+
```yaml
393+
apiVersion: apps/v1
394+
kind: Deployment
395+
metadata:
396+
name: my-deployment-with-multi-containers-multi-instrumentations
397+
spec:
398+
selector:
399+
matchLabels:
400+
app: my-pod-with-multi-containers-multi-instrumentations
401+
replicas: 1
402+
template:
403+
metadata:
404+
labels:
405+
app: my-pod-with-multi-containers-multi-instrumentations
406+
annotations:
407+
instrumentation.opentelemetry.io/inject-java: "true"
408+
instrumentation.opentelemetry.io/java-container-names: "myapp,myapp2"
409+
instrumentation.opentelemetry.io/inject-python: "true"
410+
instrumentation.opentelemetry.io/python-container-names: "myapp3"
411+
spec:
412+
containers:
413+
- name: myapp
414+
image: myImage1
415+
- name: myapp2
416+
image: myImage2
417+
- name: myapp3
418+
image: myImage3
419+
```
420+
421+
In the above case, `myapp` and `myapp2` containers will be instrumented using Java and `myapp3` using Python instrumentation.
422+
423+
**NOTE**: Go auto-instrumentation **does not** support multicontainer pods. When injecting Go auto-instrumentation the first container should be the only you want to instrument.
424+
425+
**NOTE**: This type of instrumentation **does not** allow to instrument a container with multiple language instrumentations.
426+
427+
**NOTE**: `instrumentation.opentelemetry.io/container-names` annotation is not used for this feature.
428+
345429
#### Use customized or vendor instrumentation
346430

347431
By default, the operator uses upstream auto-instrumentation libraries. Custom auto-instrumentation can be configured by
@@ -403,8 +487,8 @@ instrumentation.opentelemetry.io/inject-sdk: "true"
403487
The operator allows specifying, via the feature gates, which languages the Instrumentation resource may instrument.
404488
These feature gates must be passed to the operator via the `--feature-gates` flag.
405489
The flag allows for a comma-delimited list of feature gate identifiers.
406-
Prefix a gate with '-' to disable support for the corresponding language.
407-
Prefixing a gate with '+' or no prefix will enable support for the corresponding language.
490+
Prefix a gate with '-' to disable support for the corresponding language or multi instrumentation feature.
491+
Prefixing a gate with '+' or no prefix will enable support for the corresponding language or multi instrumentation feature.
408492
If a language is enabled by default its gate only needs to be supplied when disabling the gate.
409493

410494
| Language | Gate | Default Value |
@@ -418,6 +502,10 @@ If a language is enabled by default its gate only needs to be supplied when disa
418502

419503
Language not specified in the table are always supported and cannot be disabled.
420504

505+
OpenTelemetry Operator allows to instrument multiple containers using multiple language specific instrumentations.
506+
These feature can be enabled using `operator.autoinstrumentation.multi-instrumentation` flag. By default flag is `disabled`.
507+
For more information about multi-instrumentation feature capabilities please see [Multi-container pods with multiple instrumentations](#Multi-container-pods-with-multiple-instrumentations).
508+
421509
### Target Allocator
422510

423511
The OpenTelemetry Operator comes with an optional component, the [Target Allocator](/cmd/otel-allocator/README.md) (TA). When creating an OpenTelemetryCollector Custom Resource (CR) and setting the TA as enabled, the Operator will create a new deployment and service to serve specific `http_sd_config` directives for each Collector pod as part of that CR. It will also change the Prometheus receiver configuration in the CR, so that it uses the [http_sd_config](https://prometheus.io/docs/prometheus/latest/http_sd/) from the TA. The following example shows how to get started with the Target Allocator:

hack/modify-test-images.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ SED_BIN=${SED_BIN:-sed}
55
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/smoke-targetallocator/*.yaml
66
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/targetallocator-features/00-install.yaml
77
${SED_BIN} -i "s#local/opentelemetry-operator-targetallocator:e2e#${TARGETALLOCATOR_IMG}#g" tests/e2e/prometheus-config-validation/*.yaml
8+
9+
${SED_BIN} -i "s#local/opentelemetry-operator:e2e#${OPERATOR_IMG}#g" tests/e2e-multi-instrumentation/*.yaml

kuttl-test-multi-instr.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: kuttl.dev/v1beta1
2+
kind: TestSuite
3+
artifactsDir: ./tests/_build/artifacts/
4+
commands:
5+
- command: kubectl apply -f ./tests/e2e-multi-instrumentation/manager_deployment_feature_gate.yaml
6+
- command: go run hack/check-operator-ready.go
7+
testDirs:
8+
- ./tests/e2e-multi-instrumentation/
9+
timeout: 150

pkg/featuregate/featuregate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ var (
6262
featuregate.WithRegisterFromVersion("v0.80.0"),
6363
)
6464

65+
EnableMultiInstrumentationSupport = featuregate.GlobalRegistry().MustRegister(
66+
"operator.autoinstrumentation.multi-instrumentation",
67+
featuregate.StageAlpha,
68+
featuregate.WithRegisterFromVersion("0.86.0"),
69+
featuregate.WithRegisterDescription("controls whether the operator supports multi instrumentation"))
70+
6571
// EnableTargetAllocatorRewrite is the feature gate that controls whether the collector's configuration should
6672
// automatically be rewritten when the target allocator is enabled.
6773
EnableTargetAllocatorRewrite = featuregate.GlobalRegistry().MustRegister(

pkg/instrumentation/annotation.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,23 @@ import (
2323
const (
2424
// annotationInjectJava indicates whether java auto-instrumentation should be injected or not.
2525
// Possible values are "true", "false" or "<Instrumentation>" name.
26-
annotationInjectJava = "instrumentation.opentelemetry.io/inject-java"
27-
annotationInjectNodeJS = "instrumentation.opentelemetry.io/inject-nodejs"
28-
annotationInjectPython = "instrumentation.opentelemetry.io/inject-python"
29-
annotationInjectDotNet = "instrumentation.opentelemetry.io/inject-dotnet"
30-
annotationDotNetRuntime = "instrumentation.opentelemetry.io/otel-dotnet-auto-runtime"
31-
annotationInjectGo = "instrumentation.opentelemetry.io/inject-go"
32-
annotationGoExecPath = "instrumentation.opentelemetry.io/otel-go-auto-target-exe"
33-
annotationInjectSdk = "instrumentation.opentelemetry.io/inject-sdk"
34-
annotationInjectContainerName = "instrumentation.opentelemetry.io/container-names"
35-
annotationInjectApacheHttpd = "instrumentation.opentelemetry.io/inject-apache-httpd"
26+
annotationInjectContainerName = "instrumentation.opentelemetry.io/container-names"
27+
annotationInjectJava = "instrumentation.opentelemetry.io/inject-java"
28+
annotationInjectJavaContainersName = "instrumentation.opentelemetry.io/java-container-names"
29+
annotationInjectNodeJS = "instrumentation.opentelemetry.io/inject-nodejs"
30+
annotationInjectNodeJSContainersName = "instrumentation.opentelemetry.io/nodejs-container-names"
31+
annotationInjectPython = "instrumentation.opentelemetry.io/inject-python"
32+
annotationInjectPythonContainersName = "instrumentation.opentelemetry.io/python-container-names"
33+
annotationInjectDotNet = "instrumentation.opentelemetry.io/inject-dotnet"
34+
annotationDotNetRuntime = "instrumentation.opentelemetry.io/otel-dotnet-auto-runtime"
35+
annotationInjectDotnetContainersName = "instrumentation.opentelemetry.io/dotnet-container-names"
36+
annotationInjectGo = "instrumentation.opentelemetry.io/inject-go"
37+
annotationInjectGoContainersName = "instrumentation.opentelemetry.io/go-container-names"
38+
annotationGoExecPath = "instrumentation.opentelemetry.io/otel-go-auto-target-exe"
39+
annotationInjectSdk = "instrumentation.opentelemetry.io/inject-sdk"
40+
annotationInjectSdkContainersName = "instrumentation.opentelemetry.io/sdk-container-names"
41+
annotationInjectApacheHttpd = "instrumentation.opentelemetry.io/inject-apache-httpd"
42+
annotationInjectApacheHttpdContainersName = "instrumentation.opentelemetry.io/apache-httpd-container-names"
3643
)
3744

3845
// annotationValue returns the effective annotationInjectJava value, based on the annotations from the pod and namespace.

pkg/instrumentation/dotnet.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ const (
3333
envDotNetOTelAutoHome = "OTEL_DOTNET_AUTO_HOME"
3434
dotNetCoreClrEnableProfilingEnabled = "1"
3535
dotNetCoreClrProfilerID = "{918728DD-259F-4A6A-AC2B-B85E1B658318}"
36-
dotNetCoreClrProfilerGlibcPath = "/otel-auto-instrumentation/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so"
37-
dotNetCoreClrProfilerMuslPath = "/otel-auto-instrumentation/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so"
38-
dotNetAdditionalDepsPath = "/otel-auto-instrumentation/AdditionalDeps"
39-
dotNetOTelAutoHomePath = "/otel-auto-instrumentation"
40-
dotNetSharedStorePath = "/otel-auto-instrumentation/store"
41-
dotNetStartupHookPath = "/otel-auto-instrumentation/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll"
36+
dotNetCoreClrProfilerGlibcPath = "/otel-auto-instrumentation-dotnet/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so"
37+
dotNetCoreClrProfilerMuslPath = "/otel-auto-instrumentation-dotnet/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so"
38+
dotNetAdditionalDepsPath = "/otel-auto-instrumentation-dotnet/AdditionalDeps"
39+
dotNetOTelAutoHomePath = "/otel-auto-instrumentation-dotnet"
40+
dotNetSharedStorePath = "/otel-auto-instrumentation-dotnet/store"
41+
dotNetStartupHookPath = "/otel-auto-instrumentation-dotnet/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll"
42+
dotnetVolumeName = volumeName + "-dotnet"
43+
dotnetInitContainerName = initContainerName + "-dotnet"
44+
dotnetInstrMountPath = "/otel-auto-instrumentation-dotnet"
4245
)
4346

4447
// Supported .NET runtime identifiers (https://learn.microsoft.com/en-us/dotnet/core/rid-catalog), can be set by instrumentation.opentelemetry.io/inject-dotnet.
@@ -108,28 +111,28 @@ func injectDotNetSDK(dotNetSpec v1alpha1.DotNet, pod corev1.Pod, index int) (cor
108111
setDotNetEnvVar(container, envDotNetSharedStore, dotNetSharedStorePath, concatEnvValues)
109112

110113
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
111-
Name: volumeName,
112-
MountPath: "/otel-auto-instrumentation",
114+
Name: dotnetVolumeName,
115+
MountPath: dotnetInstrMountPath,
113116
})
114117

115118
// We just inject Volumes and init containers for the first processed container.
116-
if isInitContainerMissing(pod) {
119+
if isInitContainerMissing(pod, dotnetInitContainerName) {
117120
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
118-
Name: volumeName,
121+
Name: dotnetVolumeName,
119122
VolumeSource: corev1.VolumeSource{
120123
EmptyDir: &corev1.EmptyDirVolumeSource{
121124
SizeLimit: volumeSize(dotNetSpec.VolumeSizeLimit),
122125
},
123126
}})
124127

125128
pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{
126-
Name: initContainerName,
129+
Name: dotnetInitContainerName,
127130
Image: dotNetSpec.Image,
128-
Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"},
131+
Command: []string{"cp", "-a", "/autoinstrumentation/.", dotnetInstrMountPath},
129132
Resources: dotNetSpec.Resources,
130133
VolumeMounts: []corev1.VolumeMount{{
131-
Name: volumeName,
132-
MountPath: "/otel-auto-instrumentation",
134+
Name: dotnetVolumeName,
135+
MountPath: dotnetInstrMountPath,
133136
}},
134137
})
135138
}

0 commit comments

Comments
 (0)