diff --git a/.chloggen/3332-musl-python-autoinstrumentation.yaml b/.chloggen/3332-musl-python-autoinstrumentation.yaml new file mode 100644 index 0000000000..72fa4598ba --- /dev/null +++ b/.chloggen/3332-musl-python-autoinstrumentation.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: auto-instrumentation + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: add config for installing musl based auto-instrumentation for Python + +# One or more tracking issues related to the change +issues: [2264] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/README.md b/README.md index b18a3745b8..6244ab90cf 100644 --- a/README.md +++ b/README.md @@ -292,9 +292,12 @@ instrumentation.opentelemetry.io/inject-nodejs: "true" ``` Python: +Python auto-instrumentation also honors an annotation that will permit it to run it on images with a different C library than glibc. ```bash instrumentation.opentelemetry.io/inject-python: "true" +instrumentation.opentelemetry.io/otel-python-platform: "glibc" # for Linux glibc based images, this is the default value and can be omitted +instrumentation.opentelemetry.io/otel-python-platform: "musl" # for Linux musl based images ``` .NET: diff --git a/pkg/instrumentation/annotation.go b/pkg/instrumentation/annotation.go index 28ef7bf3d5..c415b22dbf 100644 --- a/pkg/instrumentation/annotation.go +++ b/pkg/instrumentation/annotation.go @@ -30,6 +30,7 @@ const ( annotationInjectNodeJSContainersName = "instrumentation.opentelemetry.io/nodejs-container-names" annotationInjectPython = "instrumentation.opentelemetry.io/inject-python" annotationInjectPythonContainersName = "instrumentation.opentelemetry.io/python-container-names" + annotationPythonPlatform = "instrumentation.opentelemetry.io/otel-python-platform" annotationInjectDotNet = "instrumentation.opentelemetry.io/inject-dotnet" annotationDotNetRuntime = "instrumentation.opentelemetry.io/otel-dotnet-auto-runtime" annotationInjectDotnetContainersName = "instrumentation.opentelemetry.io/dotnet-container-names" diff --git a/pkg/instrumentation/podmutator.go b/pkg/instrumentation/podmutator.go index 3c3a2f8e52..6e17f0fa49 100644 --- a/pkg/instrumentation/podmutator.go +++ b/pkg/instrumentation/podmutator.go @@ -321,6 +321,7 @@ func (pm *instPodMutator) Mutate(ctx context.Context, ns corev1.Namespace, pod c } if pm.config.EnablePythonAutoInstrumentation() || inst == nil { insts.Python.Instrumentation = inst + insts.Python.AdditionalAnnotations = map[string]string{annotationPythonPlatform: annotationValue(ns.ObjectMeta, pod.ObjectMeta, annotationPythonPlatform)} } else { logger.Error(nil, "support for Python auto instrumentation is not enabled") pm.Recorder.Event(pod.DeepCopy(), "Warning", "InstrumentationRequestRejected", "support for Python auto instrumentation is not enabled") diff --git a/pkg/instrumentation/python.go b/pkg/instrumentation/python.go index db1c9d6a2d..e39e757052 100644 --- a/pkg/instrumentation/python.go +++ b/pkg/instrumentation/python.go @@ -23,19 +23,23 @@ import ( ) const ( - envPythonPath = "PYTHONPATH" - envOtelTracesExporter = "OTEL_TRACES_EXPORTER" - envOtelMetricsExporter = "OTEL_METRICS_EXPORTER" - envOtelLogsExporter = "OTEL_LOGS_EXPORTER" - envOtelExporterOTLPProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL" - pythonPathPrefix = "/otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation" - pythonPathSuffix = "/otel-auto-instrumentation-python" - pythonInstrMountPath = "/otel-auto-instrumentation-python" - pythonVolumeName = volumeName + "-python" - pythonInitContainerName = initContainerName + "-python" + envPythonPath = "PYTHONPATH" + envOtelTracesExporter = "OTEL_TRACES_EXPORTER" + envOtelMetricsExporter = "OTEL_METRICS_EXPORTER" + envOtelLogsExporter = "OTEL_LOGS_EXPORTER" + envOtelExporterOTLPProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL" + glibcLinuxAutoInstrumentationSrc = "/autoinstrumentation/." + muslLinuxAutoInstrumentationSrc = "/autoinstrumentation-musl/." + pythonPathPrefix = "/otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation" + pythonPathSuffix = "/otel-auto-instrumentation-python" + pythonInstrMountPath = "/otel-auto-instrumentation-python" + pythonVolumeName = volumeName + "-python" + pythonInitContainerName = initContainerName + "-python" + glibcLinux = "glibc" + muslLinux = "musl" ) -func injectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int) (corev1.Pod, error) { +func injectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int, platform string) (corev1.Pod, error) { volume := instrVolume(pythonSpec.VolumeClaimTemplate, pythonVolumeName, pythonSpec.VolumeSizeLimit) // caller checks if there is at least one container. @@ -46,6 +50,16 @@ func injectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int) (cor return pod, err } + autoInstrumentationSrc := "" + switch platform { + case "", glibcLinux: + autoInstrumentationSrc = glibcLinuxAutoInstrumentationSrc + case muslLinux: + autoInstrumentationSrc = muslLinuxAutoInstrumentationSrc + default: + return pod, fmt.Errorf("provided instrumentation.opentelemetry.io/otel-python-platform annotation value '%s' is not supported", platform) + } + // inject Python instrumentation spec env vars. for _, env := range pythonSpec.Env { idx := getIndexOfEnv(container.Env, env.Name) @@ -111,7 +125,7 @@ func injectPythonSDK(pythonSpec v1alpha1.Python, pod corev1.Pod, index int) (cor pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{ Name: pythonInitContainerName, Image: pythonSpec.Image, - Command: []string{"cp", "-r", "/autoinstrumentation/.", pythonInstrMountPath}, + Command: []string{"cp", "-r", autoInstrumentationSrc, pythonInstrMountPath}, Resources: pythonSpec.Resources, VolumeMounts: []corev1.VolumeMount{{ Name: volume.Name, diff --git a/pkg/instrumentation/python_test.go b/pkg/instrumentation/python_test.go index 01fe9b1665..2347dc480b 100644 --- a/pkg/instrumentation/python_test.go +++ b/pkg/instrumentation/python_test.go @@ -29,6 +29,7 @@ func TestInjectPythonSDK(t *testing.T) { name string v1alpha1.Python pod corev1.Pod + platform string expected corev1.Pod err error }{ @@ -42,6 +43,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -118,6 +120,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -195,6 +198,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -271,6 +275,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -423,6 +428,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -499,6 +505,7 @@ func TestInjectPythonSDK(t *testing.T) { }, }, }, + platform: "glibc", expected: corev1.Pod{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -515,11 +522,171 @@ func TestInjectPythonSDK(t *testing.T) { }, err: fmt.Errorf("the container defines env var value via ValueFrom, envVar: %s", envPythonPath), }, + { + name: "musl platform defined", + Python: v1alpha1.Python{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {}, + }, + }, + }, + platform: "musl", + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: pythonVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: &defaultVolumeLimitSize, + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "opentelemetry-auto-instrumentation-python", + Image: "foo/bar:1", + Command: []string{"cp", "-r", "/autoinstrumentation-musl/.", "/otel-auto-instrumentation-python"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "opentelemetry-auto-instrumentation-python", + MountPath: "/otel-auto-instrumentation-python", + }}, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + { + Name: "opentelemetry-auto-instrumentation-python", + MountPath: "/otel-auto-instrumentation-python", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "PYTHONPATH", + Value: fmt.Sprintf("%s:%s", "/otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation", "/otel-auto-instrumentation-python"), + }, + { + Name: "OTEL_EXPORTER_OTLP_PROTOCOL", + Value: "http/protobuf", + }, + { + Name: "OTEL_TRACES_EXPORTER", + Value: "otlp", + }, + { + Name: "OTEL_METRICS_EXPORTER", + Value: "otlp", + }, + { + Name: "OTEL_LOGS_EXPORTER", + Value: "otlp", + }, + }, + }, + }, + }, + }, + err: nil, + }, + { + name: "platform not defined", + Python: v1alpha1.Python{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {}, + }, + }, + }, + platform: "", + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: pythonVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: &defaultVolumeLimitSize, + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: "opentelemetry-auto-instrumentation-python", + Image: "foo/bar:1", + Command: []string{"cp", "-r", "/autoinstrumentation/.", "/otel-auto-instrumentation-python"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "opentelemetry-auto-instrumentation-python", + MountPath: "/otel-auto-instrumentation-python", + }}, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + { + Name: "opentelemetry-auto-instrumentation-python", + MountPath: "/otel-auto-instrumentation-python", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "PYTHONPATH", + Value: fmt.Sprintf("%s:%s", "/otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation", "/otel-auto-instrumentation-python"), + }, + { + Name: "OTEL_EXPORTER_OTLP_PROTOCOL", + Value: "http/protobuf", + }, + { + Name: "OTEL_TRACES_EXPORTER", + Value: "otlp", + }, + { + Name: "OTEL_METRICS_EXPORTER", + Value: "otlp", + }, + { + Name: "OTEL_LOGS_EXPORTER", + Value: "otlp", + }, + }, + }, + }, + }, + }, + err: nil, + }, + { + name: "platform not supported", + Python: v1alpha1.Python{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {}, + }, + }, + }, + platform: "not-supported", + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {}, + }, + }, + }, + err: fmt.Errorf("provided instrumentation.opentelemetry.io/otel-python-platform annotation value 'not-supported' is not supported"), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pod, err := injectPythonSDK(test.Python, test.pod, 0) + pod, err := injectPythonSDK(test.Python, test.pod, 0, test.platform) assert.Equal(t, test.expected, pod) assert.Equal(t, test.err, err) }) diff --git a/pkg/instrumentation/sdk.go b/pkg/instrumentation/sdk.go index e47c358fb0..1ec6272836 100644 --- a/pkg/instrumentation/sdk.go +++ b/pkg/instrumentation/sdk.go @@ -105,7 +105,7 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations for _, container := range insts.Python.Containers { index := getContainerIndex(container, pod) - pod, err = injectPythonSDK(otelinst.Spec.Python, pod, index) + pod, err = injectPythonSDK(otelinst.Spec.Python, pod, index, insts.Python.AdditionalAnnotations[annotationPythonPlatform]) if err != nil { i.logger.Info("Skipping Python SDK injection", "reason", err.Error(), "container", pod.Spec.Containers[index].Name) } else { diff --git a/tests/e2e-instrumentation/instrumentation-python-musl/00-install-collector.yaml b/tests/e2e-instrumentation/instrumentation-python-musl/00-install-collector.yaml new file mode 100644 index 0000000000..34a26ebb2c --- /dev/null +++ b/tests/e2e-instrumentation/instrumentation-python-musl/00-install-collector.yaml @@ -0,0 +1,22 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: sidecar +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + + exporters: + debug: + + service: + pipelines: + traces: + receivers: [otlp] + exporters: [debug] + mode: sidecar diff --git a/tests/e2e-instrumentation/instrumentation-python-musl/00-install-instrumentation.yaml b/tests/e2e-instrumentation/instrumentation-python-musl/00-install-instrumentation.yaml new file mode 100644 index 0000000000..987cddaca6 --- /dev/null +++ b/tests/e2e-instrumentation/instrumentation-python-musl/00-install-instrumentation.yaml @@ -0,0 +1,30 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: python-musl +spec: + env: + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + exporter: + endpoint: http://localhost:4317 + propagators: + - jaeger + - b3 + sampler: + type: parentbased_traceidratio + argument: "0.25" + python: + env: + - name: OTEL_LOG_LEVEL + value: "debug" + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4318 diff --git a/tests/e2e-instrumentation/instrumentation-python-musl/01-assert.yaml b/tests/e2e-instrumentation/instrumentation-python-musl/01-assert.yaml new file mode 100644 index 0000000000..2485a7e6d7 --- /dev/null +++ b/tests/e2e-instrumentation/instrumentation-python-musl/01-assert.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + instrumentation.opentelemetry.io/inject-python: "true" + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/otel-python-platform: "musl" + labels: + app: my-python-musl +spec: + containers: + - env: + - name: OTEL_NODE_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: OTEL_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: OTEL_LOG_LEVEL + value: debug + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4318 + - name: PYTHONPATH + value: /otel-auto-instrumentation-python/opentelemetry/instrumentation/auto_instrumentation:/otel-auto-instrumentation-python + - name: OTEL_EXPORTER_OTLP_PROTOCOL + value: http/protobuf + - name: OTEL_METRICS_EXPORTER + value: otlp + - name: OTEL_LOGS_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + - name: OTEL_SERVICE_NAME + value: my-python-musl + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + - name: OTEL_PROPAGATORS + value: jaeger,b3 + - name: OTEL_RESOURCE_ATTRIBUTES + name: myapp + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + readOnly: true + - mountPath: /otel-auto-instrumentation-python + name: opentelemetry-auto-instrumentation-python + - args: + - --config=env:OTEL_CONFIG + name: otc-container + initContainers: + - name: opentelemetry-auto-instrumentation-python + command: + - cp + - -r + - /autoinstrumentation-musl/. + - /otel-auto-instrumentation-python +status: + containerStatuses: + - name: myapp + ready: true + started: true + - name: otc-container + ready: true + started: true + initContainerStatuses: + - name: opentelemetry-auto-instrumentation-python + ready: true + phase: Running diff --git a/tests/e2e-instrumentation/instrumentation-python-musl/01-install-app.yaml b/tests/e2e-instrumentation/instrumentation-python-musl/01-install-app.yaml new file mode 100644 index 0000000000..3dbca9a62f --- /dev/null +++ b/tests/e2e-instrumentation/instrumentation-python-musl/01-install-app.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-python-musl +spec: + selector: + matchLabels: + app: my-python-musl + replicas: 1 + template: + metadata: + labels: + app: my-python-musl + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-python: "true" + instrumentation.opentelemetry.io/otel-python-platform: "musl" + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 3000 + containers: + - name: myapp + image: ghcr.io/open-telemetry/opentelemetry-operator/e2e-test-app-python:main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] diff --git a/tests/e2e-instrumentation/instrumentation-python-musl/chainsaw-test.yaml b/tests/e2e-instrumentation/instrumentation-python-musl/chainsaw-test.yaml new file mode 100755 index 0000000000..89799f2367 --- /dev/null +++ b/tests/e2e-instrumentation/instrumentation-python-musl/chainsaw-test.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: instrumentation-python-musl +spec: + steps: + - name: step-00 + try: + # In OpenShift, when a namespace is created, all necessary SCC annotations are automatically added. However, if a namespace is created using a resource file with only selected SCCs, the other auto-added SCCs are not included. Therefore, the UID-range and supplemental groups SCC annotations must be set after the namespace is created. + - command: + entrypoint: kubectl + args: + - annotate + - namespace + - ${NAMESPACE} + - openshift.io/sa.scc.uid-range=1000/1000 + - --overwrite + - command: + entrypoint: kubectl + args: + - annotate + - namespace + - ${NAMESPACE} + - openshift.io/sa.scc.supplemental-groups=3000/3000 + - --overwrite + - apply: + file: 00-install-collector.yaml + - apply: + file: 00-install-instrumentation.yaml + - name: step-01 + try: + - apply: + file: 01-install-app.yaml + - assert: + file: 01-assert.yaml + catch: + - podLogs: + selector: app=my-python-musl