diff --git a/comp/core/tagger/taggerimpl/collectors/workloadmeta_extract.go b/comp/core/tagger/taggerimpl/collectors/workloadmeta_extract.go index 09979ee6014a5..45570f2599e0f 100644 --- a/comp/core/tagger/taggerimpl/collectors/workloadmeta_extract.go +++ b/comp/core/tagger/taggerimpl/collectors/workloadmeta_extract.go @@ -49,6 +49,12 @@ const ( envVarVersion = "DD_VERSION" envVarService = "DD_SERVICE" + // OpenTelemetry SDK - Environment variables + // https://opentelemetry.io/docs/languages/sdk-configuration/general + // https://opentelemetry.io/docs/specs/semconv/resource/ + envVarOtelService = "OTEL_SERVICE_NAME" + envVarOtelResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES" + // Docker label keys dockerLabelEnv = "com.datadoghq.tags.env" dockerLabelVersion = "com.datadoghq.tags.version" @@ -66,6 +72,16 @@ var ( envVarService: tagKeyService, } + otelStandardEnvKeys = map[string]string{ + envVarOtelService: tagKeyService, + } + + otelResourceAttributesMapping = map[string]string{ + "service.name": tagKeyService, + "service.version": tagKeyVersion, + "deployment.environment": tagKeyEnv, + } + lowCardOrchestratorEnvKeys = map[string]string{ "MARATHON_APP_ID": "marathon_app", @@ -212,6 +228,9 @@ func (c *WorkloadMetaCollector) handleContainer(ev workloadmeta.Event) []*types. // standard tags from environment c.extractFromMapWithFn(container.EnvVars, standardEnvKeys, tags.AddStandard) + // standard tags in OpenTelemetry SDK format from environment + c.addOpenTelemetryStandardTags(container, tags) + // orchestrator tags from environment c.extractFromMapWithFn(container.EnvVars, lowCardOrchestratorEnvKeys, tags.AddLow) c.extractFromMapWithFn(container.EnvVars, orchCardOrchestratorEnvKeys, tags.AddOrchestrator) @@ -650,6 +669,9 @@ func (c *WorkloadMetaCollector) extractTagsFromPodContainer(pod *workloadmeta.Ku // enrich with standard tags from environment variables c.extractFromMapWithFn(container.EnvVars, standardEnvKeys, tags.AddStandard) + // standard tags in OpenTelemetry SDK format from environment + c.addOpenTelemetryStandardTags(container, tags) + // container-specific tags provided through pod annotation annotation := fmt.Sprintf(podContainerTagsAnnotationFormat, containerName) c.extractTagsFromJSONInMap(annotation, pod.Annotations, tags) @@ -743,6 +765,18 @@ func (c *WorkloadMetaCollector) extractTagsFromJSONInMap(key string, input map[s } } +func (c *WorkloadMetaCollector) addOpenTelemetryStandardTags(container *workloadmeta.Container, tags *taglist.TagList) { + if otelResourceAttributes, ok := container.EnvVars[envVarOtelResourceAttributes]; ok { + for _, pair := range strings.Split(otelResourceAttributes, ",") { + fields := strings.SplitN(pair, "=", 2) + if tag, ok := otelResourceAttributesMapping[fields[0]]; ok { + tags.AddStandard(tag, fields[1]) + } + } + } + c.extractFromMapWithFn(container.EnvVars, otelStandardEnvKeys, tags.AddStandard) +} + func buildTaggerEntityID(entityID workloadmeta.EntityID) string { switch entityID.Kind { case workloadmeta.KindContainer: diff --git a/comp/core/tagger/taggerimpl/collectors/workloadmeta_test.go b/comp/core/tagger/taggerimpl/collectors/workloadmeta_test.go index cf05ffc883b9e..5258cb03445c8 100644 --- a/comp/core/tagger/taggerimpl/collectors/workloadmeta_test.go +++ b/comp/core/tagger/taggerimpl/collectors/workloadmeta_test.go @@ -28,6 +28,7 @@ import ( func TestHandleKubePod(t *testing.T) { const ( fullyFleshedContainerID = "foobarquux" + otelEnvContainerID = "otelcontainer" noEnvContainerID = "foobarbaz" containerName = "agent" runtimeContainerName = "k8s_datadog-agent_agent" @@ -84,6 +85,20 @@ func TestHandleKubePod(t *testing.T) { "DD_VERSION": version, }, }) + store.Set(&workloadmeta.Container{ + EntityID: workloadmeta.EntityID{ + Kind: workloadmeta.KindContainer, + ID: otelEnvContainerID, + }, + EntityMeta: workloadmeta.EntityMeta{ + Name: runtimeContainerName, + }, + Image: image, + EnvVars: map[string]string{ + "OTEL_SERVICE_NAME": svc, + "OTEL_RESOURCE_ATTRIBUTES": fmt.Sprintf("service.name=%s,service.version=%s,deployment.environment=%s", svc, version, env), + }, + }) store.Set(&workloadmeta.Container{ EntityID: workloadmeta.EntityID{ Kind: workloadmeta.KindContainer, @@ -279,6 +294,57 @@ func TestHandleKubePod(t *testing.T) { }, }, }, + { + name: "pod with fully formed container, standard tags from env with opentelemetry sdk", + pod: workloadmeta.KubernetesPod{ + EntityID: podEntityID, + EntityMeta: workloadmeta.EntityMeta{ + Name: podName, + Namespace: podNamespace, + }, + Containers: []workloadmeta.OrchestratorContainer{ + { + ID: otelEnvContainerID, + Name: containerName, + Image: image, + }, + }, + }, + expected: []*types.TagInfo{ + { + Source: podSource, + Entity: podTaggerEntityID, + HighCardTags: []string{}, + OrchestratorCardTags: []string{ + fmt.Sprintf("pod_name:%s", podName), + }, + LowCardTags: []string{ + fmt.Sprintf("kube_namespace:%s", podNamespace), + }, + StandardTags: []string{}, + }, + { + Source: podSource, + Entity: fmt.Sprintf("container_id://%s", otelEnvContainerID), + HighCardTags: []string{ + fmt.Sprintf("container_id:%s", otelEnvContainerID), + fmt.Sprintf("display_container_name:%s_%s", runtimeContainerName, podName), + }, + OrchestratorCardTags: []string{ + fmt.Sprintf("pod_name:%s", podName), + }, + LowCardTags: append([]string{ + fmt.Sprintf("kube_namespace:%s", podNamespace), + fmt.Sprintf("kube_container_name:%s", containerName), + "image_id:datadog/agent@sha256:a63d3f66fb2f69d955d4f2ca0b229385537a77872ffc04290acae65aed5317d2", + "image_name:datadog/agent", + "image_tag:latest", + "short_image:agent", + }, standardTags...), + StandardTags: standardTags, + }, + }, + }, { name: "pod with container, standard tags from labels", pod: workloadmeta.KubernetesPod{ @@ -990,6 +1056,42 @@ func TestHandleContainer(t *testing.T) { }, }, }, + { + name: "tags from environment with opentelemetry sdk", + container: workloadmeta.Container{ + EntityID: entityID, + EntityMeta: workloadmeta.EntityMeta{ + Name: containerName, + }, + EnvVars: map[string]string{ + // env as tags + "TEAM": "container-integrations", + "TIER": "node", + + // otel standard tags + "OTEL_SERVICE_NAME": svc, + "OTEL_RESOURCE_ATTRIBUTES": fmt.Sprintf("service.name=%s,service.version=%s,deployment.environment=%s", svc, version, env), + }, + }, + envAsTags: map[string]string{ + "team": "owner_team", + }, + expected: []*types.TagInfo{ + { + Source: containerSource, + Entity: taggerEntityID, + HighCardTags: []string{ + fmt.Sprintf("container_name:%s", containerName), + fmt.Sprintf("container_id:%s", entityID.ID), + }, + OrchestratorCardTags: []string{}, + LowCardTags: append([]string{ + "owner_team:container-integrations", + }, standardTags...), + StandardTags: standardTags, + }, + }, + }, { name: "tags from labels", container: workloadmeta.Container{ diff --git a/pkg/util/containers/env_vars_filter.go b/pkg/util/containers/env_vars_filter.go index bad4a1ac2f1c2..3fb4bdaa1d466 100644 --- a/pkg/util/containers/env_vars_filter.go +++ b/pkg/util/containers/env_vars_filter.go @@ -14,22 +14,23 @@ import ( var ( defaultEnvVarsIncludeList = []string{ - "DD_ENV", - "DD_VERSION", - "DD_SERVICE", "CHRONOS_JOB_NAME", "CHRONOS_JOB_OWNER", - "NOMAD_TASK_NAME", - "NOMAD_JOB_NAME", - "NOMAD_GROUP_NAME", - "NOMAD_NAMESPACE", - "NOMAD_DC", - "MESOS_TASK_ID", + "DD_ENV", + "DD_SERVICE", + "DD_VERSION", + "DOCKER_DD_AGENT", // included to be able to detect agent containers "ECS_CONTAINER_METADATA_URI", "ECS_CONTAINER_METADATA_URI_V4", - "DOCKER_DD_AGENT", // included to be able to detect agent containers - // Included to ease unit tests without requiring a mock - "TEST_ENV", + "MESOS_TASK_ID", + "NOMAD_DC", + "NOMAD_GROUP_NAME", + "NOMAD_JOB_NAME", + "NOMAD_NAMESPACE", + "NOMAD_TASK_NAME", + "OTEL_RESOURCE_ATTRIBUTES", + "OTEL_SERVICE_NAME", + "TEST_ENV", // Included to ease unit tests without requiring a mock } envFilterOnce sync.Once diff --git a/releasenotes/notes/opentelemetry-resource-attributes-ust-e379136c3a5c8046.yaml b/releasenotes/notes/opentelemetry-resource-attributes-ust-e379136c3a5c8046.yaml new file mode 100644 index 0000000000000..055e07c0d1480 --- /dev/null +++ b/releasenotes/notes/opentelemetry-resource-attributes-ust-e379136c3a5c8046.yaml @@ -0,0 +1,12 @@ +# Each section from every release note are combined when the +# CHANGELOG.rst is rendered. So the text needs to be worded so that +# it does not depend on any information only available in another +# section. This may mean repeating some details, but each section +# must be readable independently of the other. +# +# Each section note must be formatted as reStructuredText. +--- +features: + - | + Implement OpenTelemetry SDK Resource Attributes as Unified Service Tags. +