From 99fb6b62cea39c9bd8be4ab634bc896235926c9a Mon Sep 17 00:00:00 2001 From: Shivanshu Raj Shrivastava Date: Sat, 12 Aug 2023 23:18:15 +0530 Subject: [PATCH] [processor/k8sattributes] Add optional k8s.cluster.uid resource attribute (#23668) **Description:** Add k8s.cluster.uid to attribute to k8sattributes processor and disable it by default for backward compatibility. Users can set it to `true` to populate cluster uid as part of resource attributes. **Link to tracking Issue:** https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21974 --- .chloggen/k8smetadata.yaml | 27 +++++++++++++++++++ examples/kubernetes/otel-collector.yaml | 1 + processor/k8sattributesprocessor/config.go | 1 + .../k8sattributesprocessor/config_test.go | 2 +- processor/k8sattributesprocessor/e2e_test.go | 12 +++++++++ .../internal/kube/client.go | 24 ++++++++++++----- .../internal/kube/informer.go | 23 ++++++++++++++++ .../internal/kube/informer_test.go | 7 +++++ .../internal/kube/kube.go | 2 ++ .../internal/metadata/generated_config.go | 4 +++ .../metadata/generated_config_test.go | 2 ++ .../internal/metadata/generated_resource.go | 7 +++++ .../metadata/generated_resource_test.go | 8 +++++- .../internal/metadata/testdata/config.yaml | 4 +++ .../k8sattributesprocessor/metadata.yaml | 4 +++ processor/k8sattributesprocessor/options.go | 7 +++++ .../testdata/config.yaml | 1 + .../testdata/e2e/collector/configmap.yaml | 1 + 18 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 .chloggen/k8smetadata.yaml diff --git a/.chloggen/k8smetadata.yaml b/.chloggen/k8smetadata.yaml new file mode 100644 index 000000000000..c308b4813112 --- /dev/null +++ b/.chloggen/k8smetadata.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# 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. filelogreceiver) +component: k8sattributes + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Added k8s.cluster.uid to k8sattributes processor to add cluster uid + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [21974] + +# (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: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] \ No newline at end of file diff --git a/examples/kubernetes/otel-collector.yaml b/examples/kubernetes/otel-collector.yaml index 1d6a2612f33f..d70c6da7391b 100644 --- a/examples/kubernetes/otel-collector.yaml +++ b/examples/kubernetes/otel-collector.yaml @@ -95,6 +95,7 @@ data: - k8s.namespace.name - k8s.node.name - k8s.pod.start_time + - k8s.cluster.uid # Pod labels which can be fetched via K8sattributeprocessor labels: - tag_name: key1 diff --git a/processor/k8sattributesprocessor/config.go b/processor/k8sattributesprocessor/config.go index c6052c103156..ce194a13aa0a 100644 --- a/processor/k8sattributesprocessor/config.go +++ b/processor/k8sattributesprocessor/config.go @@ -66,6 +66,7 @@ type ExtractConfig struct { // k8s.statefulset.name, k8s.statefulset.uid, // k8s.container.name, container.image.name, // container.image.tag, container.id + // k8s.cluster.uid // // Specifying anything other than these values will result in an error. // By default, the following fields are extracted and added to spans, metrics and logs as attributes: diff --git a/processor/k8sattributesprocessor/config_test.go b/processor/k8sattributesprocessor/config_test.go index 1e2206a51275..e0b0be371b8e 100644 --- a/processor/k8sattributesprocessor/config_test.go +++ b/processor/k8sattributesprocessor/config_test.go @@ -37,7 +37,7 @@ func TestLoadConfig(t *testing.T) { APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeKubeConfig}, Passthrough: false, Extract: ExtractConfig{ - Metadata: []string{"k8s.pod.name", "k8s.pod.uid", "k8s.deployment.name", "k8s.namespace.name", "k8s.node.name", "k8s.pod.start_time"}, + Metadata: []string{"k8s.pod.name", "k8s.pod.uid", "k8s.deployment.name", "k8s.namespace.name", "k8s.node.name", "k8s.pod.start_time", "k8s.cluster.uid"}, Annotations: []FieldExtractConfig{ {TagName: "a1", Key: "annotation-one", From: "pod"}, {TagName: "a2", Key: "annotation-two", Regex: "field=(?P.+)", From: kube.MetadataFromPod}, diff --git a/processor/k8sattributesprocessor/e2e_test.go b/processor/k8sattributesprocessor/e2e_test.go index 35fd253e0768..11c7479f6e37 100644 --- a/processor/k8sattributesprocessor/e2e_test.go +++ b/processor/k8sattributesprocessor/e2e_test.go @@ -103,6 +103,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "job"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-job"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -124,6 +125,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "statefulset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-statefulset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -147,6 +149,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "deployment"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-deployment"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -168,6 +171,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "daemonset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-daemonset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -189,6 +193,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "job"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-job"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -210,6 +215,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "statefulset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-statefulset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -233,6 +239,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "deployment"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-deployment"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -254,6 +261,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "daemonset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-daemonset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -275,6 +283,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "job"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-job"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -296,6 +305,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "statefulset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-statefulset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -319,6 +329,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "deployment"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-deployment"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), @@ -340,6 +351,7 @@ func TestE2E(t *testing.T) { "k8s.annotations.workload": newExpectedValue(equal, "daemonset"), "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-daemonset"), "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "k8s.cluster.uid": newExpectedValue(exist, ""), "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), "container.image.tag": newExpectedValue(equal, "latest"), "container.id": newExpectedValue(exist, ""), diff --git a/processor/k8sattributesprocessor/internal/kube/client.go b/processor/k8sattributesprocessor/internal/kube/client.go index b5ee8aab71e0..019105eb2a0e 100644 --- a/processor/k8sattributesprocessor/internal/kube/client.go +++ b/processor/k8sattributesprocessor/internal/kube/client.go @@ -114,7 +114,15 @@ func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, } if newNamespaceInformer == nil { - newNamespaceInformer = newNamespaceSharedInformer + // if rules to extract metadata from namespace is configured use namespace shared informer containing + // all namespaces including kube-system which contains cluster uid information (kube-system-uid) + if c.extractNamespaceLabelsAnnotations() { + newNamespaceInformer = newNamespaceSharedInformer + } else { + // use kube-system shared informer to only watch kube-system namespace + // reducing overhead of watching all the namespaces + newNamespaceInformer = newKubeSystemSharedInformer + } } c.informer = newInformer(c.kc, c.Filters.Namespace, labelSelector, fieldSelector) @@ -132,11 +140,7 @@ func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, return nil, err } - if c.extractNamespaceLabelsAnnotations() { - c.namespaceInformer = newNamespaceInformer(c.kc) - } else { - c.namespaceInformer = NewNoOpInformer(c.kc) - } + c.namespaceInformer = newNamespaceInformer(c.kc) if rules.DeploymentName || rules.DeploymentUID { if newReplicaSetInformer == nil { @@ -433,6 +437,14 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string { tags[tagNodeName] = pod.Spec.NodeName } + if c.Rules.ClusterUID { + if val, ok := c.Namespaces["kube-system"]; ok { + tags[tagClusterUID] = val.NamespaceUID + } else { + c.logger.Debug("unable to find kube-system namespace, cluster uid will not be available") + } + } + for _, r := range c.Rules.Labels { r.extractFromPodMetadata(pod.Labels, tags, "k8s.pod.labels.%s") } diff --git a/processor/k8sattributesprocessor/internal/kube/informer.go b/processor/k8sattributesprocessor/internal/kube/informer.go index a8aaba82b3b0..db5cf220851c 100644 --- a/processor/k8sattributesprocessor/internal/kube/informer.go +++ b/processor/k8sattributesprocessor/internal/kube/informer.go @@ -17,6 +17,8 @@ import ( "k8s.io/client-go/tools/cache" ) +const kubeSystemNamespace = "kube-system" + // InformerProvider defines a function type that returns a new SharedInformer. It is used to // allow passing custom shared informers to the watch client. type InformerProvider func( @@ -73,6 +75,27 @@ func informerWatchFuncWithSelectors(client kubernetes.Interface, namespace strin } } +// newKubeSystemSharedInformer watches only kube-system namespace +func newKubeSystemSharedInformer( + client kubernetes.Interface, +) cache.SharedInformer { + informer := cache.NewSharedInformer( + &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + opts.FieldSelector = fields.OneTermEqualSelector("metadata.name", kubeSystemNamespace).String() + return client.CoreV1().Namespaces().List(context.Background(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + opts.FieldSelector = fields.OneTermEqualSelector("metadata.name", kubeSystemNamespace).String() + return client.CoreV1().Namespaces().Watch(context.Background(), opts) + }, + }, + &api_v1.Namespace{}, + watchSyncPeriod, + ) + return informer +} + func newNamespaceSharedInformer( client kubernetes.Interface, ) cache.SharedInformer { diff --git a/processor/k8sattributesprocessor/internal/kube/informer_test.go b/processor/k8sattributesprocessor/internal/kube/informer_test.go index 965e54d8b545..74931220dd81 100644 --- a/processor/k8sattributesprocessor/internal/kube/informer_test.go +++ b/processor/k8sattributesprocessor/internal/kube/informer_test.go @@ -33,6 +33,13 @@ func Test_newSharedNamespaceInformer(t *testing.T) { assert.NotNil(t, informer) } +func Test_newKubeSystemSharedInformer(t *testing.T) { + client, err := newFakeAPIClientset(k8sconfig.APIConfig{}) + require.NoError(t, err) + informer := newKubeSystemSharedInformer(client) + assert.NotNil(t, informer) +} + func Test_informerListFuncWithSelectors(t *testing.T) { ls, fs, err := selectorsFromFilters(Filters{ Fields: []FieldFilter{ diff --git a/processor/k8sattributesprocessor/internal/kube/kube.go b/processor/k8sattributesprocessor/internal/kube/kube.go index 202d8ea36339..6b046b599536 100644 --- a/processor/k8sattributesprocessor/internal/kube/kube.go +++ b/processor/k8sattributesprocessor/internal/kube/kube.go @@ -22,6 +22,7 @@ const ( tagNodeName = "k8s.node.name" tagStartTime = "k8s.pod.start_time" tagHostName = "k8s.pod.hostname" + tagClusterUID = "k8s.cluster.uid" // MetadataFromPod is used to specify to extract metadata/labels/annotations from pod MetadataFromPod = "pod" // MetadataFromNamespace is used to specify to extract metadata/labels/annotations from namespace @@ -203,6 +204,7 @@ type ExtractionRules struct { ContainerID bool ContainerImageName bool ContainerImageTag bool + ClusterUID bool Annotations []FieldExtractionRule Labels []FieldExtractionRule diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_config.go b/processor/k8sattributesprocessor/internal/metadata/generated_config.go index a34a0aa675c4..5a56421b322b 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_config.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_config.go @@ -12,6 +12,7 @@ type ResourceAttributesConfig struct { ContainerID ResourceAttributeConfig `mapstructure:"container.id"` ContainerImageName ResourceAttributeConfig `mapstructure:"container.image.name"` ContainerImageTag ResourceAttributeConfig `mapstructure:"container.image.tag"` + K8sClusterUID ResourceAttributeConfig `mapstructure:"k8s.cluster.uid"` K8sContainerName ResourceAttributeConfig `mapstructure:"k8s.container.name"` K8sCronjobName ResourceAttributeConfig `mapstructure:"k8s.cronjob.name"` K8sDaemonsetName ResourceAttributeConfig `mapstructure:"k8s.daemonset.name"` @@ -43,6 +44,9 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { ContainerImageTag: ResourceAttributeConfig{ Enabled: true, }, + K8sClusterUID: ResourceAttributeConfig{ + Enabled: false, + }, K8sContainerName: ResourceAttributeConfig{ Enabled: false, }, diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go b/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go index 7a268ba61fb0..b024a857c8d1 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go @@ -28,6 +28,7 @@ func TestResourceAttributesConfig(t *testing.T) { ContainerID: ResourceAttributeConfig{Enabled: true}, ContainerImageName: ResourceAttributeConfig{Enabled: true}, ContainerImageTag: ResourceAttributeConfig{Enabled: true}, + K8sClusterUID: ResourceAttributeConfig{Enabled: true}, K8sContainerName: ResourceAttributeConfig{Enabled: true}, K8sCronjobName: ResourceAttributeConfig{Enabled: true}, K8sDaemonsetName: ResourceAttributeConfig{Enabled: true}, @@ -54,6 +55,7 @@ func TestResourceAttributesConfig(t *testing.T) { ContainerID: ResourceAttributeConfig{Enabled: false}, ContainerImageName: ResourceAttributeConfig{Enabled: false}, ContainerImageTag: ResourceAttributeConfig{Enabled: false}, + K8sClusterUID: ResourceAttributeConfig{Enabled: false}, K8sContainerName: ResourceAttributeConfig{Enabled: false}, K8sCronjobName: ResourceAttributeConfig{Enabled: false}, K8sDaemonsetName: ResourceAttributeConfig{Enabled: false}, diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_resource.go b/processor/k8sattributesprocessor/internal/metadata/generated_resource.go index d8981673d707..2029665c5955 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_resource.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_resource.go @@ -42,6 +42,13 @@ func (rb *ResourceBuilder) SetContainerImageTag(val string) { } } +// SetK8sClusterUID sets provided value as "k8s.cluster.uid" attribute. +func (rb *ResourceBuilder) SetK8sClusterUID(val string) { + if rb.config.K8sClusterUID.Enabled { + rb.res.Attributes().PutStr("k8s.cluster.uid", val) + } +} + // SetK8sContainerName sets provided value as "k8s.container.name" attribute. func (rb *ResourceBuilder) SetK8sContainerName(val string) { if rb.config.K8sContainerName.Enabled { diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go b/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go index ea4280626065..2dba76c4622a 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go @@ -16,6 +16,7 @@ func TestResourceBuilder(t *testing.T) { rb.SetContainerID("container.id-val") rb.SetContainerImageName("container.image.name-val") rb.SetContainerImageTag("container.image.tag-val") + rb.SetK8sClusterUID("k8s.cluster.uid-val") rb.SetK8sContainerName("k8s.container.name-val") rb.SetK8sCronjobName("k8s.cronjob.name-val") rb.SetK8sDaemonsetName("k8s.daemonset.name-val") @@ -42,7 +43,7 @@ func TestResourceBuilder(t *testing.T) { case "default": assert.Equal(t, 8, res.Attributes().Len()) case "all_set": - assert.Equal(t, 21, res.Attributes().Len()) + assert.Equal(t, 22, res.Attributes().Len()) case "none_set": assert.Equal(t, 0, res.Attributes().Len()) return @@ -65,6 +66,11 @@ func TestResourceBuilder(t *testing.T) { if ok { assert.EqualValues(t, "container.image.tag-val", val.Str()) } + val, ok = res.Attributes().Get("k8s.cluster.uid") + assert.Equal(t, test == "all_set", ok) + if ok { + assert.EqualValues(t, "k8s.cluster.uid-val", val.Str()) + } val, ok = res.Attributes().Get("k8s.container.name") assert.Equal(t, test == "all_set", ok) if ok { diff --git a/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml b/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml index e35bc8699370..55de9c16c27c 100644 --- a/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml +++ b/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml @@ -7,6 +7,8 @@ all_set: enabled: true container.image.tag: enabled: true + k8s.cluster.uid: + enabled: true k8s.container.name: enabled: true k8s.cronjob.name: @@ -51,6 +53,8 @@ none_set: enabled: false container.image.tag: enabled: false + k8s.cluster.uid: + enabled: false k8s.container.name: enabled: false k8s.cronjob.name: diff --git a/processor/k8sattributesprocessor/metadata.yaml b/processor/k8sattributesprocessor/metadata.yaml index 58c9b5d526cf..33afa9a2f0d0 100644 --- a/processor/k8sattributesprocessor/metadata.yaml +++ b/processor/k8sattributesprocessor/metadata.yaml @@ -9,6 +9,10 @@ status: active: [dmitryax, rmfitzpatrick, fatsheep9146] # resource attributes are exposed through a different configuration interface (extract::metadata). resource_attributes: + k8s.cluster.uid: + description: Gives cluster uid identified with kube-system namespace + type: string + enabled: false k8s.namespace.name: description: The name of the namespace that the pod is running in. type: string diff --git a/processor/k8sattributesprocessor/options.go b/processor/k8sattributesprocessor/options.go index 75abb9b7d495..1135897c914b 100644 --- a/processor/k8sattributesprocessor/options.go +++ b/processor/k8sattributesprocessor/options.go @@ -23,6 +23,8 @@ const ( filterOPDoesNotExist = "does-not-exist" metadataPodStartTime = "k8s.pod.start_time" specPodHostName = "k8s.pod.hostname" + // TODO: use k8s.cluster.uid from semconv when available, and replace clusterUID with conventions.AttributeClusterUid + clusterUID = "k8s.cluster.uid" ) // option represents a configuration option that can be passes. @@ -50,6 +52,9 @@ func withPassthrough() option { // enabledAttributes returns the list of resource attributes enabled by default. func enabledAttributes() (attributes []string) { defaultConfig := metadata.DefaultResourceAttributesConfig() + if defaultConfig.K8sClusterUID.Enabled { + attributes = append(attributes, clusterUID) + } if defaultConfig.ContainerID.Enabled { attributes = append(attributes, conventions.AttributeContainerID) } @@ -167,6 +172,8 @@ func withExtractMetadata(fields ...string) option { p.rules.ContainerImageName = true case conventions.AttributeContainerImageTag: p.rules.ContainerImageTag = true + case clusterUID: + p.rules.ClusterUID = true default: return fmt.Errorf("\"%s\" is not a supported metadata field", field) } diff --git a/processor/k8sattributesprocessor/testdata/config.yaml b/processor/k8sattributesprocessor/testdata/config.yaml index f3431ad9657c..002de6f975a8 100644 --- a/processor/k8sattributesprocessor/testdata/config.yaml +++ b/processor/k8sattributesprocessor/testdata/config.yaml @@ -18,6 +18,7 @@ k8sattributes/2: - k8s.namespace.name - k8s.node.name - k8s.pod.start_time + - k8s.cluster.uid annotations: - tag_name: a1 # extracts value of annotation with key `annotation-one` and inserts it as a tag with key `a1` diff --git a/processor/k8sattributesprocessor/testdata/e2e/collector/configmap.yaml b/processor/k8sattributesprocessor/testdata/e2e/collector/configmap.yaml index f92f11f1cc31..c8347529582e 100644 --- a/processor/k8sattributesprocessor/testdata/e2e/collector/configmap.yaml +++ b/processor/k8sattributesprocessor/testdata/e2e/collector/configmap.yaml @@ -40,6 +40,7 @@ data: - k8s.job.name - k8s.job.uid - k8s.node.name + - k8s.cluster.uid - container.id - container.image.name - container.image.tag