diff --git a/.chloggen/extract-resources-ns.yaml b/.chloggen/extract-resources-ns.yaml new file mode 100755 index 0000000000..aa5515c57a --- /dev/null +++ b/.chloggen/extract-resources-ns.yaml @@ -0,0 +1,13 @@ +change_type: enhancement + +component: instrumentation + +note: "introduced ability to set Otel resource attributes based on annotations for instrumentation" + +issues: + - 2181 + + +subtext: | + + resource.opentelemetry.io/your-key: "your-value" diff --git a/README.md b/README.md index 45d86dbb69..f535097baf 100644 --- a/README.md +++ b/README.md @@ -712,6 +712,25 @@ spec: EOF ``` +### Setting instrumentation resource attributes via namespace annotations + +This example shows a pod configuration with OpenTelemetry annotations using the `resource.opentelemetry.io/` prefix. These annotations can be used to add resource attributes to data produced by OpenTelemetry instrumentation. + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: example-pod + annotations: + resource.opentelemetry.io/service.name: "my-service" + resource.opentelemetry.io/service.version: "1.0.0" + resource.opentelemetry.io/environment: "production" +spec: + containers: + - name: main-container + image: your-image:tag + ``` + ## Compatibility matrix ### OpenTelemetry Operator vs. OpenTelemetry Collector diff --git a/pkg/constants/env.go b/pkg/constants/env.go index ed6d5e0f2e..45d0a82982 100644 --- a/pkg/constants/env.go +++ b/pkg/constants/env.go @@ -31,11 +31,12 @@ const ( AnnotationDefaultAutoInstrumentationApacheHttpd = InstrumentationPrefix + "default-auto-instrumentation-apache-httpd-image" AnnotationDefaultAutoInstrumentationNginx = InstrumentationPrefix + "default-auto-instrumentation-nginx-image" - EnvPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME" - EnvPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID" - EnvPodIP = "OTEL_POD_IP" - EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME" - EnvNodeIP = "OTEL_NODE_IP" + EnvPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME" + EnvPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID" + EnvPodIP = "OTEL_POD_IP" + EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME" + EnvNodeIP = "OTEL_NODE_IP" + OtelAnnotationNamespace = "resource.opentelemetry.io/" FlagCRMetrics = "enable-cr-metrics" FlagApacheHttpd = "enable-apache-httpd-instrumentation" diff --git a/pkg/instrumentation/sdk.go b/pkg/instrumentation/sdk.go index 3bda97aa38..ed6f70663d 100644 --- a/pkg/instrumentation/sdk.go +++ b/pkg/instrumentation/sdk.go @@ -479,6 +479,15 @@ func (i *sdkInjector) createResourceMap(ctx context.Context, otelinst v1alpha1.I res[string(k)] = v } } + + for k, v := range pod.GetAnnotations() { + if strings.HasPrefix(k, constants.OtelAnnotationNamespace) { + key := strings.TrimSpace(strings.TrimPrefix(k, constants.OtelAnnotationNamespace)) + if _, ok := res[key]; !ok { + res[key] = v + } + } + } return res } diff --git a/pkg/instrumentation/sdk_test.go b/pkg/instrumentation/sdk_test.go index 9c8a6f3e1b..b93b15af47 100644 --- a/pkg/instrumentation/sdk_test.go +++ b/pkg/instrumentation/sdk_test.go @@ -484,6 +484,112 @@ func TestSDKInjection(t *testing.T) { }, }, }, + { + name: "Resource attribute propagate", + inst: v1alpha1.Instrumentation{ + Spec: v1alpha1.InstrumentationSpec{ + Exporter: v1alpha1.Exporter{ + Endpoint: "https://collector:4317", + }, + Resource: v1alpha1.Resource{ + Attributes: map[string]string{ + "fromcr": "val", + }, + }, + Propagators: []v1alpha1.Propagator{"jaeger"}, + Sampler: v1alpha1.Sampler{ + Type: "parentbased_traceidratio", + Argument: "0.25", + }, + }, + }, + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "resource.opentelemetry.io/fromtest": "val", + "resource.opentelemetry.io/foo": "test", + }, + Namespace: "project1", + Name: "app", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "app:latest", + Env: []corev1.EnvVar{ + { + Name: "OTEL_SERVICE_NAME", + Value: "explicitly_set", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "explicitly_set", + }, + { + Name: "OTEL_PROPAGATORS", + Value: "b3", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "always_on", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "foo=bar,k8s.container.name=other,service.version=explicitly_set,", + }, + }, + }, + }, + }, + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "project1", + Name: "app", + Annotations: map[string]string{ + "resource.opentelemetry.io/fromtest": "val", + "resource.opentelemetry.io/foo": "test", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "app:latest", + Env: []corev1.EnvVar{ + { + Name: "OTEL_SERVICE_NAME", + Value: "explicitly_set", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "explicitly_set", + }, + { + Name: "OTEL_PROPAGATORS", + Value: "b3", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "always_on", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "foo=bar,k8s.container.name=other,service.version=explicitly_set,foo=test,fromcr=val,fromtest=val,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=app", + }, + }, + }, + }, + }, + }, + }, } for _, test := range tests {