From e82f595ac887a5b8779e1b07a85fc82b5dba8718 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 25 Sep 2024 22:23:28 +0200 Subject: [PATCH] read resource attributes from annotations (#3204) * read resource attributes from annotations * use labels * changelog entry * fix rebase * change prefix to "resource.opentelemetry.io/" * change prefix to "resource.opentelemetry.io/" * fix rebase * update changelog and docs * changes from feedback * changes from feedback * generate * Update pkg/instrumentation/apachehttpd.go Co-authored-by: Jacob Aronoff * pr review * comments * lint * lint * Update README.md Co-authored-by: Jacob Aronoff * change priority to avoid breaking change * add e2e test * add e2e test * add e2e test * pr review * use pod methods * don't modify the existing chainsaw test * don't modify the existing chainsaw test --------- Co-authored-by: Jacob Aronoff --- .../resource-attribute-from-annotations.yaml | 24 ++ README.md | 58 ++- apis/v1alpha1/instrumentation_types.go | 13 + apis/v1alpha1/zz_generated.deepcopy.go | 16 + .../opentelemetry.io_instrumentations.yaml | 5 + .../opentelemetry.io_instrumentations.yaml | 5 + .../opentelemetry.io_instrumentations.yaml | 5 + docs/api.md | 38 ++ pkg/constants/env.go | 18 +- pkg/instrumentation/apachehttpd.go | 8 +- pkg/instrumentation/apachehttpd_test.go | 4 +- pkg/instrumentation/nginx.go | 8 +- pkg/instrumentation/nginx_test.go | 4 +- pkg/instrumentation/sdk.go | 84 ++++- pkg/instrumentation/sdk_test.go | 351 +++++++++++++++++- .../00-install-collector.yaml | 22 ++ .../00-install-instrumentation.yaml | 10 + .../01-assert.yaml | 47 +++ .../01-deployment.yaml | 22 ++ .../chainsaw-test.yaml | 24 ++ 20 files changed, 720 insertions(+), 46 deletions(-) create mode 100755 .chloggen/resource-attribute-from-annotations.yaml create mode 100644 tests/e2e/use-labels-for-resource-attributes/00-install-collector.yaml create mode 100644 tests/e2e/use-labels-for-resource-attributes/00-install-instrumentation.yaml create mode 100644 tests/e2e/use-labels-for-resource-attributes/01-assert.yaml create mode 100644 tests/e2e/use-labels-for-resource-attributes/01-deployment.yaml create mode 100644 tests/e2e/use-labels-for-resource-attributes/chainsaw-test.yaml diff --git a/.chloggen/resource-attribute-from-annotations.yaml b/.chloggen/resource-attribute-from-annotations.yaml new file mode 100755 index 0000000000..1ddf782c5d --- /dev/null +++ b/.chloggen/resource-attribute-from-annotations.yaml @@ -0,0 +1,24 @@ +change_type: enhancement + +component: auto-instrumentation + +note: Add support for k8s labels such as app.kubernetes.io/name for resource attributes + +issues: [3112] + +subtext: | + You can opt-in as follows: + ```yaml + apiVersion: opentelemetry.io/v1alpha1 + kind: Instrumentation + metadata: + name: my-instrumentation + spec: + defaults: + useLabelsForResourceAttributes: true + ``` + The following labels are supported: + - `app.kubernetes.io/name` becomes `service.name` + - `app.kubernetes.io/version` becomes `service.version` + - `app.kubernetes.io/part-of` becomes `service.namespace` + - `app.kubernetes.io/instance` becomes `service.instance.id` diff --git a/README.md b/README.md index bb8fc87192..f3485d7fac 100644 --- a/README.md +++ b/README.md @@ -717,7 +717,9 @@ spec: EOF ``` -### Setting instrumentation resource attributes via namespace annotations +## Configure resource attributes + +### Configure resource attributes with 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. @@ -734,7 +736,59 @@ spec: containers: - name: main-container image: your-image:tag - ``` +``` + +### Configure resource attributes with labels + +You can also use common labels to set resource attributes. + +The following labels are supported: +- `app.kubernetes.io/name` becomes `service.name` +- `app.kubernetes.io/version` becomes `service.version` +- `app.kubernetes.io/part-of` becomes `service.namespace` +- `app.kubernetes.io/instance` becomes `service.instance.id` + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: example-pod + labels: + app.kubernetes.io/name: "my-service" + app.kubernetes.io/version: "1.0.0" + app.kubernetes.io/part-of: "shop" + app.kubernetes.io/instance: "my-service-123" +spec: + containers: + - name: main-container + image: your-image:tag +``` + +This requires an explicit opt-in as follows: + +```yaml +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: my-instrumentation +spec: + defaults: + useLabelsForResourceAttributes: true +``` + +### Priority for setting resource attributes + +The priority for setting resource attributes is as follows (first found wins): + +1. Resource attributes set via `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME` environment variables +2. Resource attributes set via annotations (with the `resource.opentelemetry.io/` prefix) +3. Resource attributes set via labels (e.g. `app.kubernetes.io/name`) + if the `Instrumentation` CR has defaults.useLabelsForResourceAttributes=true (see above) +4. Resource attributes calculated from the pod's metadata (e.g. `k8s.pod.name`) +5. Resource attributes set via the `Instrumentation` CR (in the `spec.resource.resourceAttributes` section) + +This priority is applied for each resource attribute separately, so it is possible to set some attributes via +annotations and others via labels. ## Compatibility matrix diff --git a/apis/v1alpha1/instrumentation_types.go b/apis/v1alpha1/instrumentation_types.go index 8345d3c38a..2cccef7d6b 100644 --- a/apis/v1alpha1/instrumentation_types.go +++ b/apis/v1alpha1/instrumentation_types.go @@ -40,6 +40,9 @@ type InstrumentationSpec struct { // +optional Sampler `json:"sampler,omitempty"` + // Defaults defines default values for the instrumentation. + Defaults Defaults `json:"defaults,omitempty"` + // Env defines common env vars. There are four layers for env vars' definitions and // the precedence order is: `original container env vars` > `language specific env vars` > `common env vars` > `instrument spec configs' vars`. // If the former var had been defined, then the other vars would be ignored. @@ -114,6 +117,16 @@ type Sampler struct { Argument string `json:"argument,omitempty"` } +// Defaults defines default values for the instrumentation. +type Defaults struct { + // UseLabelsForResourceAttributes defines whether to use common labels for resource attributes: + // - `app.kubernetes.io/name` becomes `service.name` + // - `app.kubernetes.io/version` becomes `service.version` + // - `app.kubernetes.io/part-of` becomes `service.namespace` + // - `app.kubernetes.io/instance` becomes `service.instance.id` + UseLabelsForResourceAttributes bool `json:"useLabelsForResourceAttributes,omitempty"` +} + // Java defines Java SDK and instrumentation configuration. type Java struct { // Image is a container image with javaagent auto-instrumentation JAR. diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 3d410949f0..270c617e17 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -125,6 +125,21 @@ func (in *ConfigMapsSpec) DeepCopy() *ConfigMapsSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Defaults) DeepCopyInto(out *Defaults) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Defaults. +func (in *Defaults) DeepCopy() *Defaults { + if in == nil { + return nil + } + out := new(Defaults) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DotNet) DeepCopyInto(out *DotNet) { *out = *in @@ -316,6 +331,7 @@ func (in *InstrumentationSpec) DeepCopyInto(out *InstrumentationSpec) { copy(*out, *in) } out.Sampler = in.Sampler + out.Defaults = in.Defaults if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]v1.EnvVar, len(*in)) diff --git a/bundle/community/manifests/opentelemetry.io_instrumentations.yaml b/bundle/community/manifests/opentelemetry.io_instrumentations.yaml index 1aa55479f8..76f050bf0d 100644 --- a/bundle/community/manifests/opentelemetry.io_instrumentations.yaml +++ b/bundle/community/manifests/opentelemetry.io_instrumentations.yaml @@ -224,6 +224,11 @@ spec: pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object + defaults: + properties: + useLabelsForResourceAttributes: + type: boolean + type: object dotnet: properties: env: diff --git a/bundle/openshift/manifests/opentelemetry.io_instrumentations.yaml b/bundle/openshift/manifests/opentelemetry.io_instrumentations.yaml index 1aa55479f8..76f050bf0d 100644 --- a/bundle/openshift/manifests/opentelemetry.io_instrumentations.yaml +++ b/bundle/openshift/manifests/opentelemetry.io_instrumentations.yaml @@ -224,6 +224,11 @@ spec: pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object + defaults: + properties: + useLabelsForResourceAttributes: + type: boolean + type: object dotnet: properties: env: diff --git a/config/crd/bases/opentelemetry.io_instrumentations.yaml b/config/crd/bases/opentelemetry.io_instrumentations.yaml index 8005608fa5..19582f62c6 100644 --- a/config/crd/bases/opentelemetry.io_instrumentations.yaml +++ b/config/crd/bases/opentelemetry.io_instrumentations.yaml @@ -222,6 +222,11 @@ spec: pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object + defaults: + properties: + useLabelsForResourceAttributes: + type: boolean + type: object dotnet: properties: env: diff --git a/docs/api.md b/docs/api.md index 7e84600c32..24d16da3f4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -95,6 +95,13 @@ InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumen ApacheHttpd defines configuration for Apache HTTPD auto-instrumentation.
false + + defaults + object + + Defaults defines default values for the instrumentation.
+ + false dotnet object @@ -887,6 +894,37 @@ only the result of this request.
+### Instrumentation.spec.defaults +[↩ Parent](#instrumentationspec) + + + +Defaults defines default values for the instrumentation. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
useLabelsForResourceAttributesboolean + UseLabelsForResourceAttributes defines whether to use common labels for resource attributes: + - `app.kubernetes.io/name` becomes `service.name` + - `app.kubernetes.io/version` becomes `service.version` + - `app.kubernetes.io/part-of` becomes `service.namespace` + - `app.kubernetes.io/instance` becomes `service.instance.id`
+
false
+ + ### Instrumentation.spec.dotnet [↩ Parent](#instrumentationspec) diff --git a/pkg/constants/env.go b/pkg/constants/env.go index 45d0a82982..ac89f13e6d 100644 --- a/pkg/constants/env.go +++ b/pkg/constants/env.go @@ -31,12 +31,18 @@ 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" - OtelAnnotationNamespace = "resource.opentelemetry.io/" + LabelAppName = "app.kubernetes.io/name" + LabelAppInstance = "app.kubernetes.io/instance" + LabelAppVersion = "app.kubernetes.io/version" + LabelAppPartOf = "app.kubernetes.io/part-of" + + ResourceAttributeAnnotationPrefix = "resource.opentelemetry.io/" + + 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" FlagCRMetrics = "enable-cr-metrics" FlagApacheHttpd = "enable-apache-httpd-instrumentation" diff --git a/pkg/instrumentation/apachehttpd.go b/pkg/instrumentation/apachehttpd.go index 34925473bb..39a9e2d96f 100644 --- a/pkg/instrumentation/apachehttpd.go +++ b/pkg/instrumentation/apachehttpd.go @@ -59,7 +59,7 @@ const ( 6) Inject mounting of volumes / files into appropriate directories in application container */ -func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod corev1.Pod, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod { +func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod corev1.Pod, useLabelsForResourceAttributes bool, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod { // caller checks if there is at least one container container := &pod.Spec.Containers[index] @@ -162,7 +162,7 @@ func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod Env: []corev1.EnvVar{ { Name: apacheAttributesEnvVar, - Value: getApacheOtelConfig(pod, apacheSpec, index, otlpEndpoint, resourceMap), + Value: getApacheOtelConfig(pod, useLabelsForResourceAttributes, apacheSpec, index, otlpEndpoint, resourceMap), }, {Name: apacheServiceInstanceIdEnvVar, ValueFrom: &corev1.EnvVarSource{ @@ -201,7 +201,7 @@ func isApacheInitContainerMissing(pod corev1.Pod, containerName string) bool { // Calculate Apache HTTPD agent configuration file based on attributes provided by the injection rules // and by the pod values. -func getApacheOtelConfig(pod corev1.Pod, apacheSpec v1alpha1.ApacheHttpd, index int, otelEndpoint string, resourceMap map[string]string) string { +func getApacheOtelConfig(pod corev1.Pod, useLabelsForResourceAttributes bool, apacheSpec v1alpha1.ApacheHttpd, index int, otelEndpoint string, resourceMap map[string]string) string { template := ` #Load the Otel Webserver SDK LoadFile %[1]s/sdk_lib/lib/libopentelemetry_common.so @@ -222,7 +222,7 @@ LoadModule otel_apache_module %[1]s/WebServerModule/Apache/libmod_apache_otel%[2 if otelEndpoint == "" { otelEndpoint = "http://localhost:4317/" } - serviceName := chooseServiceName(pod, resourceMap, index) + serviceName := chooseServiceName(pod, useLabelsForResourceAttributes, resourceMap, index) serviceNamespace := pod.GetNamespace() if len(serviceNamespace) == 0 { serviceNamespace = resourceMap[string(semconv.K8SNamespaceNameKey)] diff --git a/pkg/instrumentation/apachehttpd_test.go b/pkg/instrumentation/apachehttpd_test.go index e938f40c70..3a93d7418d 100644 --- a/pkg/instrumentation/apachehttpd_test.go +++ b/pkg/instrumentation/apachehttpd_test.go @@ -417,7 +417,7 @@ func TestInjectApacheHttpdagent(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, 0, "http://otlp-endpoint:4317", resourceMap) + pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap) assert.Equal(t, test.expected, pod) }) } @@ -527,7 +527,7 @@ func TestInjectApacheHttpdagentUnknownNamespace(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, 0, "http://otlp-endpoint:4317", resourceMap) + pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap) assert.Equal(t, test.expected, pod) }) } diff --git a/pkg/instrumentation/nginx.go b/pkg/instrumentation/nginx.go index 350a3533b9..7bcc39d611 100644 --- a/pkg/instrumentation/nginx.go +++ b/pkg/instrumentation/nginx.go @@ -62,7 +62,7 @@ const ( 6) Inject mounting of volumes / files into appropriate directories in the application container */ -func injectNginxSDK(_ logr.Logger, nginxSpec v1alpha1.Nginx, pod corev1.Pod, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod { +func injectNginxSDK(_ logr.Logger, nginxSpec v1alpha1.Nginx, pod corev1.Pod, useLabelsForResourceAttributes bool, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod { // caller checks if there is at least one container container := &pod.Spec.Containers[index] @@ -217,7 +217,7 @@ mv ${NGINX_AGENT_CONF_DIR_FULL}/opentelemetry_agent.conf ${NGINX_AGENT_CONF_DIR Env: []corev1.EnvVar{ { Name: nginxAttributesEnvVar, - Value: getNginxOtelConfig(pod, nginxSpec, index, otlpEndpoint, resourceMap), + Value: getNginxOtelConfig(pod, useLabelsForResourceAttributes, nginxSpec, index, otlpEndpoint, resourceMap), }, { Name: "OTEL_NGINX_I13N_SCRIPT", @@ -277,12 +277,12 @@ func isNginxInitContainerMissing(pod corev1.Pod, containerName string) bool { // Calculate Nginx agent configuration file based on attributes provided by the injection rules // and by the pod values. -func getNginxOtelConfig(pod corev1.Pod, nginxSpec v1alpha1.Nginx, index int, otelEndpoint string, resourceMap map[string]string) string { +func getNginxOtelConfig(pod corev1.Pod, useLabelsForResourceAttributes bool, nginxSpec v1alpha1.Nginx, index int, otelEndpoint string, resourceMap map[string]string) string { if otelEndpoint == "" { otelEndpoint = "http://localhost:4317/" } - serviceName := chooseServiceName(pod, resourceMap, index) + serviceName := chooseServiceName(pod, useLabelsForResourceAttributes, resourceMap, index) serviceNamespace := pod.GetNamespace() if len(serviceNamespace) == 0 { serviceNamespace = resourceMap[string(semconv.K8SNamespaceNameKey)] diff --git a/pkg/instrumentation/nginx_test.go b/pkg/instrumentation/nginx_test.go index 65a9d5b8a0..b483c38cf4 100644 --- a/pkg/instrumentation/nginx_test.go +++ b/pkg/instrumentation/nginx_test.go @@ -477,7 +477,7 @@ func TestInjectNginxSDK(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, 0, "http://otlp-endpoint:4317", resourceMap) + pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap) assert.Equal(t, test.expected, pod) }) } @@ -600,7 +600,7 @@ func TestInjectNginxUnknownNamespace(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, 0, "http://otlp-endpoint:4317", resourceMap) + pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap) assert.Equal(t, test.expected, pod) }) } diff --git a/pkg/instrumentation/sdk.go b/pkg/instrumentation/sdk.go index c9ae3fb09b..a9f7d9bbfd 100644 --- a/pkg/instrumentation/sdk.go +++ b/pkg/instrumentation/sdk.go @@ -181,7 +181,8 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations index := getContainerIndex(container, pod) // Apache agent is configured via config files rather than env vars. // Therefore, service name, otlp endpoint and other attributes are passed to the agent injection method - pod = injectApacheHttpdagent(i.logger, otelinst.Spec.ApacheHttpd, pod, index, otelinst.Spec.Endpoint, i.createResourceMap(ctx, otelinst, ns, pod, index)) + useLabelsForResourceAttributes := otelinst.Spec.Defaults.UseLabelsForResourceAttributes + pod = injectApacheHttpdagent(i.logger, otelinst.Spec.ApacheHttpd, pod, useLabelsForResourceAttributes, index, otelinst.Spec.Endpoint, i.createResourceMap(ctx, otelinst, ns, pod, index)) pod = i.injectCommonEnvVar(otelinst, pod, index) pod = i.injectCommonSDKConfig(ctx, otelinst, ns, pod, index, index) pod = i.setInitContainerSecurityContext(pod, pod.Spec.Containers[index].SecurityContext, apacheAgentInitContainerName) @@ -201,7 +202,8 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations index := getContainerIndex(container, pod) // Nginx agent is configured via config files rather than env vars. // Therefore, service name, otlp endpoint and other attributes are passed to the agent injection method - pod = injectNginxSDK(i.logger, otelinst.Spec.Nginx, pod, index, otelinst.Spec.Endpoint, i.createResourceMap(ctx, otelinst, ns, pod, index)) + useLabelsForResourceAttributes := otelinst.Spec.Defaults.UseLabelsForResourceAttributes + pod = injectNginxSDK(i.logger, otelinst.Spec.Nginx, pod, useLabelsForResourceAttributes, index, otelinst.Spec.Endpoint, i.createResourceMap(ctx, otelinst, ns, pod, index)) pod = i.injectCommonEnvVar(otelinst, pod, index) pod = i.injectCommonSDKConfig(ctx, otelinst, ns, pod, index, index) } @@ -293,12 +295,13 @@ func (i *sdkInjector) injectCommonEnvVar(otelinst v1alpha1.Instrumentation, pod // and appIndex should represent the application being instrumented. func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alpha1.Instrumentation, ns corev1.Namespace, pod corev1.Pod, agentIndex int, appIndex int) corev1.Pod { container := &pod.Spec.Containers[agentIndex] + useLabelsForResourceAttributes := otelinst.Spec.Defaults.UseLabelsForResourceAttributes resourceMap := i.createResourceMap(ctx, otelinst, ns, pod, appIndex) idx := getIndexOfEnv(container.Env, constants.EnvOTELServiceName) if idx == -1 { container.Env = append(container.Env, corev1.EnvVar{ Name: constants.EnvOTELServiceName, - Value: chooseServiceName(pod, resourceMap, appIndex), + Value: chooseServiceName(pod, useLabelsForResourceAttributes, resourceMap, appIndex), }) } if otelinst.Spec.Exporter.Endpoint != "" { @@ -339,7 +342,7 @@ func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alph idx = getIndexOfEnv(container.Env, constants.EnvOTELResourceAttrs) if idx == -1 || !strings.Contains(container.Env[idx].Value, string(semconv.ServiceVersionKey)) { - vsn := chooseServiceVersion(pod, appIndex) + vsn := chooseServiceVersion(pod, useLabelsForResourceAttributes, appIndex) if vsn != "" { resourceMap[string(semconv.ServiceVersionKey)] = vsn } @@ -410,7 +413,15 @@ func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alph return pod } -func chooseServiceName(pod corev1.Pod, resources map[string]string, index int) string { +// chooseServiceName returns the service name to be used in the instrumentation. +// The precedence is as follows: +// 1. label or annotation with key "service.name" or "app.kubernetes.io/name". +// 2. k8s resource name (deployment, replicaset, statefulset, daemonset, cronjob, job, pod). +// 3. container name. +func chooseServiceName(pod corev1.Pod, useLabelsForResourceAttributes bool, resources map[string]string, index int) string { + if name := chooseLabelOrAnnotation(pod, useLabelsForResourceAttributes, semconv.ServiceNameKey, constants.LabelAppName); name != "" { + return name + } if name := resources[string(semconv.K8SDeploymentNameKey)]; name != "" { return name } @@ -435,8 +446,31 @@ func chooseServiceName(pod corev1.Pod, resources map[string]string, index int) s return pod.Spec.Containers[index].Name } -// obtains version by splitting image string on ":" and extracting final element from resulting array. -func chooseServiceVersion(pod corev1.Pod, index int) string { +// chooseLabelOrAnnotation returns the value of the label or annotation with the given key. +// The precedence is as follows: +// 1. annotation with key resource.opentelemetry.io/. +// 2. label with key labelKey. +func chooseLabelOrAnnotation(pod corev1.Pod, useLabelsForResourceAttributes bool, resource attribute.Key, labelKey string) string { + if v := pod.GetAnnotations()[(constants.ResourceAttributeAnnotationPrefix + string(resource))]; v != "" { + return v + } + if useLabelsForResourceAttributes { + if v := pod.GetLabels()[labelKey]; v != "" { + return v + } + } + return "" +} + +// chooseServiceVersion returns the service version to be used in the instrumentation. +// The precedence is as follows: +// 1. label or annotation with key "service.version" or "app.kubernetes.io/version". +// 2. image tag (by splitting image string on ":" and extracting final element from resulting array). +func chooseServiceVersion(pod corev1.Pod, useLabelsForResourceAttributes bool, index int) string { + v := chooseLabelOrAnnotation(pod, useLabelsForResourceAttributes, semconv.ServiceVersionKey, constants.LabelAppVersion) + if v != "" { + return v + } parts := strings.Split(pod.Spec.Containers[index].Image, ":") tag := parts[len(parts)-1] //guard statement to handle case where image name has a port number @@ -446,10 +480,17 @@ func chooseServiceVersion(pod corev1.Pod, index int) string { return tag } -// creates the service.instance.id following the semantic defined by -// https://github.com/open-telemetry/semantic-conventions/pull/312. -func createServiceInstanceId(namespaceName, podName, containerName string) string { - var serviceInstanceId string +// chooseServiceInstanceId returns the service.instance.id to be used in the instrumentation. +// The precedence is as follows: +// 1. annotation with key "service.instance.id" or "app.kubernetes.io/instance" +// 2. namespace name + pod name + container name +// (as defined by https://opentelemetry.io/docs/specs/semconv/resource/#service-experimental) +func createServiceInstanceId(pod corev1.Pod, useLabelsForResourceAttributes bool, namespaceName, podName, containerName string) string { + serviceInstanceId := chooseLabelOrAnnotation(pod, useLabelsForResourceAttributes, semconv.ServiceInstanceIDKey, constants.LabelAppInstance) + if serviceInstanceId != "" { + return serviceInstanceId + } + if namespaceName != "" && podName != "" && containerName != "" { resNames := []string{namespaceName, podName, containerName} serviceInstanceId = strings.Join(resNames, ".") @@ -475,11 +516,17 @@ func (i *sdkInjector) createResourceMap(ctx context.Context, otelinst v1alpha1.I } res := map[string]string{} + + // entries from the CRD have the lowest precedence - they are overridden by later values for k, v := range otelinst.Spec.Resource.Attributes { if !existingRes[k] { res[k] = v } } + + useLabelsForResourceAttributes := otelinst.Spec.Defaults.UseLabelsForResourceAttributes + + // k8s resources have a higher precedence than CRD entries k8sResources := map[attribute.Key]string{} k8sResources[semconv.K8SNamespaceNameKey] = ns.Name k8sResources[semconv.K8SContainerNameKey] = pod.Spec.Containers[index].Name @@ -488,22 +535,29 @@ func (i *sdkInjector) createResourceMap(ctx context.Context, otelinst v1alpha1.I k8sResources[semconv.K8SPodNameKey] = pod.Name k8sResources[semconv.K8SPodUIDKey] = string(pod.UID) k8sResources[semconv.K8SNodeNameKey] = pod.Spec.NodeName - k8sResources[semconv.ServiceInstanceIDKey] = createServiceInstanceId(ns.Name, fmt.Sprintf("$(%s)", constants.EnvPodName), pod.Spec.Containers[index].Name) + k8sResources[semconv.ServiceInstanceIDKey] = createServiceInstanceId(pod, useLabelsForResourceAttributes, ns.Name, fmt.Sprintf("$(%s)", constants.EnvPodName), pod.Spec.Containers[index].Name) i.addParentResourceLabels(ctx, otelinst.Spec.Resource.AddK8sUIDAttributes, ns, pod.ObjectMeta, k8sResources) + for k, v := range k8sResources { if !existingRes[string(k)] && v != "" { res[string(k)] = v } } + // attributes and labels from the pod have the highest precedence (except for values set in environment variables) 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 { + if strings.HasPrefix(k, constants.ResourceAttributeAnnotationPrefix) { + key := strings.TrimPrefix(k, constants.ResourceAttributeAnnotationPrefix) + if !existingRes[key] && key != string(semconv.ServiceNameKey) { res[key] = v } } } + partOf := chooseLabelOrAnnotation(pod, useLabelsForResourceAttributes, semconv.ServiceNamespaceKey, constants.LabelAppPartOf) + if partOf != "" && !existingRes[string(semconv.ServiceNamespaceKey)] { + res[string(semconv.ServiceNamespaceKey)] = partOf + } + return res } diff --git a/pkg/instrumentation/sdk_test.go b/pkg/instrumentation/sdk_test.go index 393f9e9fe2..c3abedac04 100644 --- a/pkg/instrumentation/sdk_test.go +++ b/pkg/instrumentation/sdk_test.go @@ -131,6 +131,9 @@ func TestSDKInjection(t *testing.T) { }, Resource: v1alpha1.Resource{ AddK8sUIDAttributes: true, + Attributes: map[string]string{ + "foo": "hidden", + }, }, Propagators: []v1alpha1.Propagator{"b3", "jaeger"}, Sampler: v1alpha1.Sampler{ @@ -152,6 +155,15 @@ func TestSDKInjection(t *testing.T) { APIVersion: "apps/v1", }, }, + Labels: map[string]string{ + "app.kubernetes.io/name": "app-name", + "app.kubernetes.io/instance": "app-id", + "app.kubernetes.io/version": "v1", + "app.kubernetes.io/part-of": "shop", + }, + Annotations: map[string]string{ + "resource.opentelemetry.io/foo": "bar", + }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -167,6 +179,15 @@ func TestSDKInjection(t *testing.T) { Namespace: "project1", Name: "app", UID: "pod-uid", + Labels: map[string]string{ + "app.kubernetes.io/name": "app-name", + "app.kubernetes.io/instance": "app-id", + "app.kubernetes.io/version": "v1", + "app.kubernetes.io/part-of": "shop", + }, + Annotations: map[string]string{ + "resource.opentelemetry.io/foo": "bar", + }, OwnerReferences: []metav1.OwnerReference{ { Kind: "ReplicaSet", @@ -220,7 +241,247 @@ func TestSDKInjection(t *testing.T) { }, { Name: "OTEL_RESOURCE_ATTRIBUTES", - Value: "k8s.container.name=application-name,k8s.deployment.name=my-deployment,k8s.deployment.uid=depuid,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME),k8s.pod.uid=pod-uid,k8s.replicaset.name=my-replicaset,k8s.replicaset.uid=rsuid,service.instance.id=project1.$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME).application-name,service.version=latest", + Value: "foo=bar,k8s.container.name=application-name,k8s.deployment.name=my-deployment,k8s.deployment.uid=depuid,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME),k8s.pod.uid=pod-uid,k8s.replicaset.name=my-replicaset,k8s.replicaset.uid=rsuid,service.instance.id=project1.$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME).application-name,service.version=latest", + }, + }, + }, + }, + }, + }, + }, + { + name: "Resource attribute from CRD", + inst: v1alpha1.Instrumentation{ + Spec: v1alpha1.InstrumentationSpec{ + Exporter: v1alpha1.Exporter{ + Endpoint: "https://collector:4317", + }, + Resource: v1alpha1.Resource{ + AddK8sUIDAttributes: true, + Attributes: map[string]string{ + "k8s.container.name": "explicit-container", + "k8s.deployment.name": "explicit-deployment", + "k8s.deployment.uid": "explicit-deployment-uid", + "k8s.namespace.name": "explicit-ns", + "k8s.node.name": "explicit-node", + "k8s.pod.name": "explicit-pod", + "k8s.pod.uid": "explicit-pod-uid", + "k8s.replicaset.name": "explicit-replicaset", + "k8s.replicaset.uid": "explicit-replicaset-uid", + "service.instance.id": "explicit-id", + "service.version": "explicit-version", + }, + }, + Propagators: []v1alpha1.Propagator{"b3", "jaeger"}, + Sampler: v1alpha1.Sampler{ + Type: "parentbased_traceidratio", + Argument: "0.25", + }, + }, + }, + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "project1", + Name: "app", + UID: "pod-uid", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "ReplicaSet", + Name: "my-replicaset", + UID: "rsuid", + APIVersion: "apps/v1", + }, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "application-name", + Image: "app:latest", + }, + }, + NodeName: "node-name", + }, + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "project1", + Name: "app", + UID: "pod-uid", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "ReplicaSet", + Name: "my-replicaset", + UID: "rsuid", + APIVersion: "apps/v1", + }, + }, + }, + Spec: corev1.PodSpec{ + NodeName: "node-name", + Containers: []corev1.Container{ + { + Name: "application-name", + Image: "app:latest", + Env: []corev1.EnvVar{ + { + Name: "OTEL_SERVICE_NAME", + Value: "my-deployment", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "https://collector:4317", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "OTEL_PROPAGATORS", + Value: "b3,jaeger", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "parentbased_traceidratio", + }, + { + Name: "OTEL_TRACES_SAMPLER_ARG", + Value: "0.25", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "k8s.container.name=application-name,k8s.deployment.name=my-deployment,k8s.deployment.uid=depuid,k8s.namespace.name=project1,k8s.node.name=node-name,k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME),k8s.pod.uid=pod-uid,k8s.replicaset.name=my-replicaset,k8s.replicaset.uid=rsuid,service.instance.id=project1.$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME).application-name,service.version=latest", + }, + }, + }, + }, + }, + }, + }, + { + name: "SDK env vars not defined - use labels for resource attributes", + inst: v1alpha1.Instrumentation{ + Spec: v1alpha1.InstrumentationSpec{ + Exporter: v1alpha1.Exporter{ + Endpoint: "https://collector:4317", + }, + Resource: v1alpha1.Resource{ + AddK8sUIDAttributes: true, + }, + Propagators: []v1alpha1.Propagator{"b3", "jaeger"}, + Sampler: v1alpha1.Sampler{ + Type: "parentbased_traceidratio", + Argument: "0.25", + }, + Defaults: v1alpha1.Defaults{ + UseLabelsForResourceAttributes: true, + }, + }, + }, + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "project1", + Name: "app", + UID: "pod-uid", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "ReplicaSet", + Name: "my-replicaset", + UID: "rsuid", + APIVersion: "apps/v1", + }, + }, + Labels: map[string]string{ + "app.kubernetes.io/name": "app-name", + "app.kubernetes.io/instance": "app-id", + "app.kubernetes.io/version": "v1", + "app.kubernetes.io/part-of": "shop", + }, + Annotations: map[string]string{ + "resource.opentelemetry.io/foo": "bar", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "application-name", + Image: "app:latest", + }, + }, + }, + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "project1", + Name: "app", + UID: "pod-uid", + Labels: map[string]string{ + "app.kubernetes.io/name": "app-name", + "app.kubernetes.io/instance": "app-id", + "app.kubernetes.io/version": "v1", + "app.kubernetes.io/part-of": "shop", + }, + Annotations: map[string]string{ + "resource.opentelemetry.io/foo": "bar", + }, + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "ReplicaSet", + Name: "my-replicaset", + UID: "rsuid", + APIVersion: "apps/v1", + }, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "application-name", + Image: "app:latest", + Env: []corev1.EnvVar{ + { + Name: "OTEL_SERVICE_NAME", + Value: "app-name", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "https://collector:4317", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "OTEL_PROPAGATORS", + Value: "b3,jaeger", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "parentbased_traceidratio", + }, + { + Name: "OTEL_TRACES_SAMPLER_ARG", + Value: "0.25", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "foo=bar,k8s.container.name=application-name,k8s.deployment.name=my-deployment,k8s.deployment.uid=depuid,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME),k8s.pod.uid=pod-uid,k8s.replicaset.name=my-replicaset,k8s.replicaset.uid=rsuid,service.instance.id=app-id,service.namespace=shop,service.version=v1", }, }, }, @@ -245,12 +506,21 @@ func TestSDKInjection(t *testing.T) { Type: "parentbased_traceidratio", Argument: "0.25", }, + Defaults: v1alpha1.Defaults{ + UseLabelsForResourceAttributes: true, + }, }, }, pod: corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "project1", Name: "app", + Labels: map[string]string{ + "app.kubernetes.io/name": "not-used", + "app.kubernetes.io/instance": "not-used", + "app.kubernetes.io/version": "not-used", + "app.kubernetes.io/part-of": "not-used", + }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -259,7 +529,7 @@ func TestSDKInjection(t *testing.T) { Env: []corev1.EnvVar{ { Name: "OTEL_SERVICE_NAME", - Value: "explicitly_set", + Value: "explicit-name", }, { Name: "OTEL_EXPORTER_OTLP_ENDPOINT", @@ -275,7 +545,7 @@ func TestSDKInjection(t *testing.T) { }, { Name: "OTEL_RESOURCE_ATTRIBUTES", - Value: "foo=bar,k8s.container.name=other,service.version=explicitly_set,", + Value: "foo=bar,k8s.container.name=other,service.version=explicit-version,service.namespace=explicit-ns,service.instance.id=explicit-id,", }, }, }, @@ -286,6 +556,12 @@ func TestSDKInjection(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "project1", Name: "app", + Labels: map[string]string{ + "app.kubernetes.io/name": "not-used", + "app.kubernetes.io/instance": "not-used", + "app.kubernetes.io/version": "not-used", + "app.kubernetes.io/part-of": "not-used", + }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -294,7 +570,7 @@ func TestSDKInjection(t *testing.T) { Env: []corev1.EnvVar{ { Name: "OTEL_SERVICE_NAME", - Value: "explicitly_set", + Value: "explicit-name", }, { Name: "OTEL_EXPORTER_OTLP_ENDPOINT", @@ -326,7 +602,7 @@ func TestSDKInjection(t *testing.T) { }, { Name: "OTEL_RESOURCE_ATTRIBUTES", - Value: "foo=bar,k8s.container.name=other,service.version=explicitly_set,fromcr=val,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", + Value: "foo=bar,k8s.container.name=other,service.version=explicit-version,service.namespace=explicit-ns,service.instance.id=explicit-id,fromcr=val,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", }, }, }, @@ -614,7 +890,7 @@ func TestSDKInjection(t *testing.T) { }, { 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=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", + Value: "foo=bar,k8s.container.name=other,service.version=explicitly_set,fromcr=val,fromtest=val,k8s.namespace.name=project1,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", }, }, }, @@ -2139,10 +2415,13 @@ func TestParentResourceLabels(t *testing.T) { func TestChooseServiceName(t *testing.T) { tests := []struct { - name string - resources map[string]string - index int - expectedServiceName string + name string + resources map[string]string + index int + expectedServiceName string + useLabelsForResourceAttributes bool + labelValue string + annotationValue string }{ { name: "first container", @@ -2164,6 +2443,48 @@ func TestChooseServiceName(t *testing.T) { index: 0, expectedServiceName: "my-pod", }, + { + name: "from pod label - useLabelsForResourceAttributes=false", + resources: map[string]string{ + string(semconv.K8SPodNameKey): "my-pod", + }, + index: 0, + labelValue: "annotation", + useLabelsForResourceAttributes: false, + expectedServiceName: "my-pod", + }, + { + name: "from pod label - useLabelsForResourceAttributes=true", + resources: map[string]string{ + string(semconv.K8SPodNameKey): "my-pod", + }, + index: 0, + labelValue: "label", + useLabelsForResourceAttributes: true, + expectedServiceName: "label", + }, + { + name: "from pod annotation - useLabelsForResourceAttributes=false", + resources: map[string]string{ + string(semconv.K8SPodNameKey): "my-pod", + }, + index: 0, + annotationValue: "annotation", + labelValue: "label", + useLabelsForResourceAttributes: false, + expectedServiceName: "annotation", + }, + { + name: "from pod annotation - useLabelsForResourceAttributes=true", + resources: map[string]string{ + string(semconv.K8SPodNameKey): "my-pod", + }, + index: 0, + annotationValue: "annotation", + labelValue: "label", + useLabelsForResourceAttributes: true, + expectedServiceName: "annotation", + }, { name: "from replicaset", resources: map[string]string{ @@ -2224,13 +2545,21 @@ func TestChooseServiceName(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { serviceName := chooseServiceName(corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/name": test.labelValue, + }, + Annotations: map[string]string{ + "resource.opentelemetry.io/service.name": test.annotationValue, + }, + }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ {Name: "1st"}, {Name: "2nd"}, }, }, - }, test.resources, test.index) + }, test.useLabelsForResourceAttributes, test.resources, test.index) assert.Equal(t, test.expectedServiceName, serviceName) }) diff --git a/tests/e2e/use-labels-for-resource-attributes/00-install-collector.yaml b/tests/e2e/use-labels-for-resource-attributes/00-install-collector.yaml new file mode 100644 index 0000000000..34a26ebb2c --- /dev/null +++ b/tests/e2e/use-labels-for-resource-attributes/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/use-labels-for-resource-attributes/00-install-instrumentation.yaml b/tests/e2e/use-labels-for-resource-attributes/00-install-instrumentation.yaml new file mode 100644 index 0000000000..d87179ecbf --- /dev/null +++ b/tests/e2e/use-labels-for-resource-attributes/00-install-instrumentation.yaml @@ -0,0 +1,10 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: sdk-only +spec: + exporter: + endpoint: http://localhost:4317 + defaults: + useLabelsForResourceAttributes: true + diff --git a/tests/e2e/use-labels-for-resource-attributes/01-assert.yaml b/tests/e2e/use-labels-for-resource-attributes/01-assert.yaml new file mode 100644 index 0000000000..4eca7b6973 --- /dev/null +++ b/tests/e2e/use-labels-for-resource-attributes/01-assert.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + instrumentation.opentelemetry.io/inject-sdk: "true" + sidecar.opentelemetry.io/inject: "true" + labels: + app: my-deploy +spec: + (containers[?name == 'myapp']): + - name: myapp + env: + - name: OTEL_NODE_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.hostIP + - name: OTEL_POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: OTEL_SERVICE_NAME + value: my-app + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - 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_RESOURCE_ATTRIBUTES + value: + ( contains(@, 'foo=bar') ): true + ( contains(@, 'k8s.container.name=myapp') ): true + ( contains(@, 'k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)') ): true + ( contains(@, 'k8s.replicaset.name=my-deploy-') ): true + ( contains(@, 'k8s.deployment.name=my-deploy') ): true + ( contains(@, concat('k8s.namespace.name=', $namespace)) ): true + ( contains(@, 'k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME)') ): true + ( contains(@, join('', ['service.instance.id=', $namespace, '.$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME).myapp']) ) ): true + ( contains(@, 'service.version=main') ): true diff --git a/tests/e2e/use-labels-for-resource-attributes/01-deployment.yaml b/tests/e2e/use-labels-for-resource-attributes/01-deployment.yaml new file mode 100644 index 0000000000..72a98c7e15 --- /dev/null +++ b/tests/e2e/use-labels-for-resource-attributes/01-deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy +spec: + selector: + matchLabels: + app: my-deploy + replicas: 1 + template: + metadata: + labels: + app: my-deploy + app.kubernetes.io/name: my-app + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-sdk: "true" + resource.opentelemetry.io/foo: "bar" + spec: + containers: + - name: myapp + image: ghcr.io/open-telemetry/opentelemetry-operator/e2e-test-app-python:main diff --git a/tests/e2e/use-labels-for-resource-attributes/chainsaw-test.yaml b/tests/e2e/use-labels-for-resource-attributes/chainsaw-test.yaml new file mode 100644 index 0000000000..c3670e292b --- /dev/null +++ b/tests/e2e/use-labels-for-resource-attributes/chainsaw-test.yaml @@ -0,0 +1,24 @@ +# 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: env-vars +spec: + steps: + - name: step-00 + try: + - apply: + file: 00-install-collector.yaml + - apply: + file: 00-install-instrumentation.yaml + # Deployment + - name: step-01 + try: + - apply: + file: 01-deployment.yaml + - assert: + file: 01-assert.yaml + catch: + - podLogs: + selector: app=my-deploy