diff --git a/.chloggen/add_k8s_pod_ip.yaml b/.chloggen/add_k8s_pod_ip.yaml new file mode 100644 index 000000000000..0429f9c0a1de --- /dev/null +++ b/.chloggen/add_k8s_pod_ip.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: k8sattributesprocessor + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "Add support for exposing `k8s.pod.ip` as a resource attribute" + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [32960] + +# (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] diff --git a/processor/k8sattributesprocessor/config.go b/processor/k8sattributesprocessor/config.go index 809ff4fe301f..631d4508b779 100644 --- a/processor/k8sattributesprocessor/config.go +++ b/processor/k8sattributesprocessor/config.go @@ -84,7 +84,7 @@ func (cfg *Config) Validate() error { for _, field := range cfg.Extract.Metadata { switch field { case conventions.AttributeK8SNamespaceName, conventions.AttributeK8SPodName, conventions.AttributeK8SPodUID, - specPodHostName, metadataPodStartTime, + specPodHostName, metadataPodStartTime, metadataPodIP, conventions.AttributeK8SDeploymentName, conventions.AttributeK8SDeploymentUID, conventions.AttributeK8SReplicaSetName, conventions.AttributeK8SReplicaSetUID, conventions.AttributeK8SDaemonSetName, conventions.AttributeK8SDaemonSetUID, diff --git a/processor/k8sattributesprocessor/config_test.go b/processor/k8sattributesprocessor/config_test.go index cece8a3e1260..d82b6fa6c537 100644 --- a/processor/k8sattributesprocessor/config_test.go +++ b/processor/k8sattributesprocessor/config_test.go @@ -40,7 +40,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", "k8s.cluster.uid"}, + Metadata: []string{"k8s.pod.name", "k8s.pod.uid", "k8s.pod.ip", "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/documentation.md b/processor/k8sattributesprocessor/documentation.md index ce3d52558c56..152061d3805e 100644 --- a/processor/k8sattributesprocessor/documentation.md +++ b/processor/k8sattributesprocessor/documentation.md @@ -22,6 +22,7 @@ | k8s.node.name | The name of the Node. | Any Str | true | | k8s.node.uid | The UID of the Node. | Any Str | false | | k8s.pod.hostname | The hostname of the Pod. | Any Str | false | +| k8s.pod.ip | The IP address of the Pod. | Any Str | false | | k8s.pod.name | The name of the Pod. | Any Str | true | | k8s.pod.start_time | The start time of the Pod. | Any Str | true | | k8s.pod.uid | The UID of the Pod. | Any Str | true | diff --git a/processor/k8sattributesprocessor/e2e_test.go b/processor/k8sattributesprocessor/e2e_test.go index 7520f06169b6..41802c81563e 100644 --- a/processor/k8sattributesprocessor/e2e_test.go +++ b/processor/k8sattributesprocessor/e2e_test.go @@ -32,6 +32,7 @@ const ( equal = iota regex exist + shouldnotexist testKubeConfig = "/tmp/kube-config-otelcol-e2e-testing" uidRe = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" startTimeRe = "^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}%3A\\\\d{2}%3A\\\\d{2}(?:%2E\\\\d+)?[A-Z]?(?:[+.-](?:08%3A\\\\d{2}|\\\\d{2}[A-Z]))?$" @@ -722,6 +723,146 @@ func TestE2E_MixRBAC(t *testing.T) { } } +// Test with `filter::namespace` set and only role binding to collector's SA. We can't get node and namespace labels/annotations. +// While `k8s.pod.ip` is not set in `k8sattributes:extract:metadata` and the `pod_association` is not `connection` +// we expect that the `k8s.pod.ip` metadata is not added. +func TestE2E_NamespacedRBACNoPodIP(t *testing.T) { + testDir := filepath.Join("testdata", "e2e", "namespaced_rbac_no_pod_ip") + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + nsFile := filepath.Join(testDir, "namespace.yaml") + buf, err := os.ReadFile(nsFile) + require.NoErrorf(t, err, "failed to read namespace object file %s", nsFile) + nsObj, err := k8stest.CreateObject(k8sClient, buf) + require.NoErrorf(t, err, "failed to create k8s namespace from file %s", nsFile) + nsName := nsObj.GetName() + defer func() { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, nsObj), "failed to delete namespace %s", nsName) + }() + + metricsConsumer := new(consumertest.MetricsSink) + tracesConsumer := new(consumertest.TracesSink) + logsConsumer := new(consumertest.LogsSink) + shutdownSinks := startUpSinks(t, metricsConsumer, tracesConsumer, logsConsumer) + defer shutdownSinks() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(testDir, "collector")) + createTeleOpts := &k8stest.TelemetrygenCreateOpts{ + ManifestsDir: filepath.Join(testDir, "telemetrygen"), + TestID: testID, + OtlpEndpoint: fmt.Sprintf("otelcol-%s.%s:4317", testID, nsName), + DataTypes: []string{"metrics", "logs", "traces"}, + } + telemetryGenObjs, telemetryGenObjInfos := k8stest.CreateTelemetryGenObjects(t, k8sClient, createTeleOpts) + defer func() { + for _, obj := range append(collectorObjs, telemetryGenObjs...) { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + for _, info := range telemetryGenObjInfos { + k8stest.WaitForTelemetryGenToStart(t, k8sClient, info.Namespace, info.PodLabelSelectors, info.Workload, info.DataType) + } + + wantEntries := 20 // Minimal number of metrics/traces/logs to wait for. + waitForData(t, wantEntries, metricsConsumer, tracesConsumer, logsConsumer) + + tcs := []struct { + name string + dataType component.DataType + service string + attrs map[string]*expectedValue + }{ + { + name: "traces-deployment", + dataType: component.DataTypeTraces, + service: "test-traces-deployment", + attrs: map[string]*expectedValue{ + "k8s.pod.name": newExpectedValue(regex, "telemetrygen-"+testID+"-traces-deployment-[a-z0-9]*-[a-z0-9]*"), + "k8s.pod.ip": newExpectedValue(shouldnotexist, ""), + "k8s.pod.uid": newExpectedValue(regex, uidRe), + "k8s.pod.start_time": newExpectedValue(exist, startTimeRe), + "k8s.node.name": newExpectedValue(exist, ""), + "k8s.namespace.name": newExpectedValue(equal, nsName), + "k8s.deployment.name": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-deployment"), + "k8s.deployment.uid": newExpectedValue(regex, uidRe), + "k8s.replicaset.name": newExpectedValue(regex, "telemetrygen-"+testID+"-traces-deployment-[a-z0-9]*"), + "k8s.replicaset.uid": newExpectedValue(regex, uidRe), + "k8s.annotations.workload": newExpectedValue(equal, "deployment"), + "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-traces-deployment"), + "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), + "container.image.tag": newExpectedValue(equal, "latest"), + "container.id": newExpectedValue(exist, ""), + }, + }, + { + name: "metrics-deployment", + dataType: component.DataTypeMetrics, + service: "test-metrics-deployment", + attrs: map[string]*expectedValue{ + "k8s.pod.name": newExpectedValue(regex, "telemetrygen-"+testID+"-metrics-deployment-[a-z0-9]*-[a-z0-9]*"), + "k8s.pod.ip": newExpectedValue(shouldnotexist, ""), + "k8s.pod.uid": newExpectedValue(regex, uidRe), + "k8s.pod.start_time": newExpectedValue(exist, startTimeRe), + "k8s.node.name": newExpectedValue(exist, ""), + "k8s.namespace.name": newExpectedValue(equal, nsName), + "k8s.deployment.name": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-deployment"), + "k8s.deployment.uid": newExpectedValue(regex, uidRe), + "k8s.replicaset.name": newExpectedValue(regex, "telemetrygen-"+testID+"-metrics-deployment-[a-z0-9]*"), + "k8s.replicaset.uid": newExpectedValue(regex, uidRe), + "k8s.annotations.workload": newExpectedValue(equal, "deployment"), + "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-metrics-deployment"), + "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), + "container.image.tag": newExpectedValue(equal, "latest"), + "container.id": newExpectedValue(exist, ""), + }, + }, + { + name: "logs-deployment", + dataType: component.DataTypeLogs, + service: "test-logs-deployment", + attrs: map[string]*expectedValue{ + "k8s.pod.name": newExpectedValue(regex, "telemetrygen-"+testID+"-logs-deployment-[a-z0-9]*-[a-z0-9]*"), + "k8s.pod.ip": newExpectedValue(shouldnotexist, ""), + "k8s.pod.uid": newExpectedValue(regex, uidRe), + "k8s.pod.start_time": newExpectedValue(exist, startTimeRe), + "k8s.node.name": newExpectedValue(exist, ""), + "k8s.namespace.name": newExpectedValue(equal, nsName), + "k8s.deployment.name": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-deployment"), + "k8s.deployment.uid": newExpectedValue(regex, uidRe), + "k8s.replicaset.name": newExpectedValue(regex, "telemetrygen-"+testID+"-logs-deployment-[a-z0-9]*"), + "k8s.replicaset.uid": newExpectedValue(regex, uidRe), + "k8s.annotations.workload": newExpectedValue(equal, "deployment"), + "k8s.labels.app": newExpectedValue(equal, "telemetrygen-"+testID+"-logs-deployment"), + "k8s.container.name": newExpectedValue(equal, "telemetrygen"), + "container.image.name": newExpectedValue(equal, "ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen"), + "container.image.tag": newExpectedValue(equal, "latest"), + "container.id": newExpectedValue(exist, ""), + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + switch tc.dataType { + case component.DataTypeTraces: + scanTracesForAttributes(t, tracesConsumer, tc.service, tc.attrs) + case component.DataTypeMetrics: + scanMetricsForAttributes(t, metricsConsumer, tc.service, tc.attrs) + case component.DataTypeLogs: + scanLogsForAttributes(t, logsConsumer, tc.service, tc.attrs) + default: + t.Fatalf("unknown data type %s", tc.dataType) + } + }) + } +} + func scanTracesForAttributes(t *testing.T, ts *consumertest.TracesSink, expectedService string, kvs map[string]*expectedValue) { // Iterate over the received set of traces starting from the most recent entries due to a bug in the processor: @@ -787,8 +928,13 @@ func scanLogsForAttributes(t *testing.T, ls *consumertest.LogsSink, expectedServ func resourceHasAttributes(resource pcommon.Resource, kvs map[string]*expectedValue) error { foundAttrs := make(map[string]bool) - for k := range kvs { - foundAttrs[k] = false + shouldNotFoundAttrs := make(map[string]bool) + for k, v := range kvs { + if v.mode != shouldnotexist { + foundAttrs[k] = false + continue + } + shouldNotFoundAttrs[k] = false } resource.Attributes().Range( @@ -806,6 +952,8 @@ func resourceHasAttributes(resource pcommon.Resource, kvs map[string]*expectedVa } case exist: foundAttrs[k] = true + case shouldnotexist: + shouldNotFoundAttrs[k] = true } } @@ -814,6 +962,11 @@ func resourceHasAttributes(resource pcommon.Resource, kvs map[string]*expectedVa ) var err error + for k, v := range shouldNotFoundAttrs { + if v { + err = multierr.Append(err, fmt.Errorf("%v attribute should not be added", k)) + } + } for k, v := range foundAttrs { if !v { err = multierr.Append(err, fmt.Errorf("%v attribute not found", k)) diff --git a/processor/k8sattributesprocessor/internal/kube/client.go b/processor/k8sattributesprocessor/internal/kube/client.go index 19ba3fedfdd3..9270af014b4d 100644 --- a/processor/k8sattributesprocessor/internal/kube/client.go +++ b/processor/k8sattributesprocessor/internal/kube/client.go @@ -425,6 +425,10 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string { tags[tagHostName] = pod.Spec.Hostname } + if c.Rules.PodIP { + tags[K8sIPLabelName] = pod.Status.PodIP + } + if c.Rules.Namespace { tags[conventions.AttributeK8SNamespaceName] = pod.GetNamespace() } diff --git a/processor/k8sattributesprocessor/internal/kube/client_test.go b/processor/k8sattributesprocessor/internal/kube/client_test.go index ff18fb59c405..0c314c25d2ef 100644 --- a/processor/k8sattributesprocessor/internal/kube/client_test.go +++ b/processor/k8sattributesprocessor/internal/kube/client_test.go @@ -814,6 +814,7 @@ func TestExtractionRules(t *testing.T) { PodName: true, PodUID: true, PodHostName: true, + PodIP: true, Node: true, StartTime: true, }, @@ -825,6 +826,7 @@ func TestExtractionRules(t *testing.T) { "k8s.pod.name": "auth-service-abc12-xyz3", "k8s.pod.hostname": "host1", "k8s.pod.uid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "k8s.pod.ip": "1.1.1.1", "k8s.pod.start_time": func() string { b, err := pod.GetCreationTimestamp().MarshalText() require.NoError(t, err) diff --git a/processor/k8sattributesprocessor/internal/kube/kube.go b/processor/k8sattributesprocessor/internal/kube/kube.go index 45a98c586a16..9e3f4835a92b 100644 --- a/processor/k8sattributesprocessor/internal/kube/kube.go +++ b/processor/k8sattributesprocessor/internal/kube/kube.go @@ -205,6 +205,7 @@ type ExtractionRules struct { PodName bool PodUID bool PodHostName bool + PodIP bool ReplicaSetID bool ReplicaSetName bool StatefulSetUID bool diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_config.go b/processor/k8sattributesprocessor/internal/metadata/generated_config.go index 0f5901f5ab62..98844fe00f8e 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_config.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_config.go @@ -43,6 +43,7 @@ type ResourceAttributesConfig struct { K8sNodeName ResourceAttributeConfig `mapstructure:"k8s.node.name"` K8sNodeUID ResourceAttributeConfig `mapstructure:"k8s.node.uid"` K8sPodHostname ResourceAttributeConfig `mapstructure:"k8s.pod.hostname"` + K8sPodIP ResourceAttributeConfig `mapstructure:"k8s.pod.ip"` K8sPodName ResourceAttributeConfig `mapstructure:"k8s.pod.name"` K8sPodStartTime ResourceAttributeConfig `mapstructure:"k8s.pod.start_time"` K8sPodUID ResourceAttributeConfig `mapstructure:"k8s.pod.uid"` @@ -102,6 +103,9 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { K8sPodHostname: ResourceAttributeConfig{ Enabled: false, }, + K8sPodIP: ResourceAttributeConfig{ + Enabled: false, + }, K8sPodName: ResourceAttributeConfig{ Enabled: true, }, diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go b/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go index 2d191e7d5623..2002ac62a2e4 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_config_test.go @@ -40,6 +40,7 @@ func TestResourceAttributesConfig(t *testing.T) { K8sNodeName: ResourceAttributeConfig{Enabled: true}, K8sNodeUID: ResourceAttributeConfig{Enabled: true}, K8sPodHostname: ResourceAttributeConfig{Enabled: true}, + K8sPodIP: ResourceAttributeConfig{Enabled: true}, K8sPodName: ResourceAttributeConfig{Enabled: true}, K8sPodStartTime: ResourceAttributeConfig{Enabled: true}, K8sPodUID: ResourceAttributeConfig{Enabled: true}, @@ -68,6 +69,7 @@ func TestResourceAttributesConfig(t *testing.T) { K8sNodeName: ResourceAttributeConfig{Enabled: false}, K8sNodeUID: ResourceAttributeConfig{Enabled: false}, K8sPodHostname: ResourceAttributeConfig{Enabled: false}, + K8sPodIP: ResourceAttributeConfig{Enabled: false}, K8sPodName: ResourceAttributeConfig{Enabled: false}, K8sPodStartTime: ResourceAttributeConfig{Enabled: false}, K8sPodUID: ResourceAttributeConfig{Enabled: false}, diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_resource.go b/processor/k8sattributesprocessor/internal/metadata/generated_resource.go index ab0c0746fca4..398a0a38131e 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_resource.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_resource.go @@ -133,6 +133,13 @@ func (rb *ResourceBuilder) SetK8sPodHostname(val string) { } } +// SetK8sPodIP sets provided value as "k8s.pod.ip" attribute. +func (rb *ResourceBuilder) SetK8sPodIP(val string) { + if rb.config.K8sPodIP.Enabled { + rb.res.Attributes().PutStr("k8s.pod.ip", val) + } +} + // SetK8sPodName sets provided value as "k8s.pod.name" attribute. func (rb *ResourceBuilder) SetK8sPodName(val string) { if rb.config.K8sPodName.Enabled { diff --git a/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go b/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go index fd96be1e87fe..ed75e6522145 100644 --- a/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go +++ b/processor/k8sattributesprocessor/internal/metadata/generated_resource_test.go @@ -29,6 +29,7 @@ func TestResourceBuilder(t *testing.T) { rb.SetK8sNodeName("k8s.node.name-val") rb.SetK8sNodeUID("k8s.node.uid-val") rb.SetK8sPodHostname("k8s.pod.hostname-val") + rb.SetK8sPodIP("k8s.pod.ip-val") rb.SetK8sPodName("k8s.pod.name-val") rb.SetK8sPodStartTime("k8s.pod.start_time-val") rb.SetK8sPodUID("k8s.pod.uid-val") @@ -44,7 +45,7 @@ func TestResourceBuilder(t *testing.T) { case "default": assert.Equal(t, 8, res.Attributes().Len()) case "all_set": - assert.Equal(t, 23, res.Attributes().Len()) + assert.Equal(t, 24, res.Attributes().Len()) case "none_set": assert.Equal(t, 0, res.Attributes().Len()) return @@ -132,6 +133,11 @@ func TestResourceBuilder(t *testing.T) { if ok { assert.EqualValues(t, "k8s.pod.hostname-val", val.Str()) } + val, ok = res.Attributes().Get("k8s.pod.ip") + assert.Equal(t, test == "all_set", ok) + if ok { + assert.EqualValues(t, "k8s.pod.ip-val", val.Str()) + } val, ok = res.Attributes().Get("k8s.pod.name") assert.True(t, ok) if ok { diff --git a/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml b/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml index d7f95e5bf943..e5a0495e1451 100644 --- a/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml +++ b/processor/k8sattributesprocessor/internal/metadata/testdata/config.yaml @@ -33,6 +33,8 @@ all_set: enabled: true k8s.pod.hostname: enabled: true + k8s.pod.ip: + enabled: true k8s.pod.name: enabled: true k8s.pod.start_time: @@ -81,6 +83,8 @@ none_set: enabled: false k8s.pod.hostname: enabled: false + k8s.pod.ip: + enabled: false k8s.pod.name: enabled: false k8s.pod.start_time: diff --git a/processor/k8sattributesprocessor/metadata.yaml b/processor/k8sattributesprocessor/metadata.yaml index 3b1e0330074c..ae5728c9a7d2 100644 --- a/processor/k8sattributesprocessor/metadata.yaml +++ b/processor/k8sattributesprocessor/metadata.yaml @@ -34,6 +34,10 @@ resource_attributes: description: The start time of the Pod. type: string enabled: true + k8s.pod.ip: + description: The IP address of the Pod. + type: string + enabled: false k8s.deployment.name: description: The name of the Deployment. type: string diff --git a/processor/k8sattributesprocessor/options.go b/processor/k8sattributesprocessor/options.go index 033d3bb303c0..c28ff303a989 100644 --- a/processor/k8sattributesprocessor/options.go +++ b/processor/k8sattributesprocessor/options.go @@ -21,6 +21,7 @@ const ( filterOPNotEquals = "not-equals" filterOPExists = "exists" filterOPDoesNotExist = "does-not-exist" + metadataPodIP = "k8s.pod.ip" metadataPodStartTime = "k8s.pod.start_time" specPodHostName = "k8s.pod.hostname" // TODO: use k8s.cluster.uid from semconv when available, and replace clusterUID with conventions.AttributeClusterUid @@ -109,6 +110,9 @@ func enabledAttributes() (attributes []string) { if defaultConfig.K8sPodUID.Enabled { attributes = append(attributes, conventions.AttributeK8SPodUID) } + if defaultConfig.K8sPodIP.Enabled { + attributes = append(attributes, metadataPodIP) + } if defaultConfig.K8sReplicasetName.Enabled { attributes = append(attributes, conventions.AttributeK8SReplicaSetName) } @@ -140,6 +144,8 @@ func withExtractMetadata(fields ...string) option { p.rules.PodHostName = true case metadataPodStartTime: p.rules.StartTime = true + case metadataPodIP: + p.rules.PodIP = true case conventions.AttributeK8SDeploymentName: p.rules.DeploymentName = true case conventions.AttributeK8SDeploymentUID: diff --git a/processor/k8sattributesprocessor/testdata/config.yaml b/processor/k8sattributesprocessor/testdata/config.yaml index 99b62032d734..553ca2102fec 100644 --- a/processor/k8sattributesprocessor/testdata/config.yaml +++ b/processor/k8sattributesprocessor/testdata/config.yaml @@ -14,6 +14,7 @@ k8sattributes/2: # extract the following well-known metadata fields from pods and namespaces - k8s.pod.name - k8s.pod.uid + - k8s.pod.ip - k8s.deployment.name - k8s.namespace.name - k8s.node.name diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/configmap.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/configmap.yaml new file mode 100644 index 000000000000..e0394b254a7c --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/configmap.yaml @@ -0,0 +1,83 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: {} + processors: + k8sattributes: + filter: + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip + node_from_env_var: MY_NODE_NAME + labels: + - key: component + value: telemetrygen + op: equals + fields: + - key: spec.restartPolicy + value: Never + op: not-equals + extract: + annotations: + - from: pod + key: workload + tag_name: k8s.annotations.workload + labels: + - from: pod + key: app + tag_name: k8s.labels.app + metadata: + - k8s.pod.name + - k8s.pod.start_time + - k8s.pod.uid + - k8s.namespace.name + - k8s.deployment.name + - k8s.deployment.uid + - k8s.replicaset.name + - k8s.replicaset.uid + - k8s.node.name + - container.id + - container.image.name + - container.image.tag + pod_association: + - sources: + - from: resource_attribute + name: k8s.deployment.name + receivers: + otlp: + protocols: + grpc: + endpoint: ${env:MY_POD_IP}:4317 + service: + extensions: + - health_check + pipelines: + metrics: + exporters: + - otlp + processors: + - k8sattributes + receivers: + - otlp + traces: + exporters: + - otlp + processors: + - k8sattributes + receivers: + - otlp + logs: + exporters: + - otlp + processors: + - k8sattributes + receivers: + - otlp diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/deployment.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/deployment.yaml new file mode 100644 index 000000000000..bc1ee38dfd38 --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: opentelemetry-collector + app.kubernetes.io/instance: {{ .Name }} + template: + metadata: + labels: + app.kubernetes.io/name: opentelemetry-collector + app.kubernetes.io/instance: {{ .Name }} + spec: + serviceAccountName: {{ .Name }} + containers: + - name: opentelemetry-collector + command: + - /otelcontribcol + - --config=/conf/relay.yaml + image: "otelcontribcol:latest" + imagePullPolicy: Never + ports: + - name: otlp + containerPort: 4317 + protocol: TCP + env: + - name: MY_POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: MY_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + livenessProbe: + httpGet: + path: / + port: 13133 + initialDelaySeconds: 3 + readinessProbe: + httpGet: + path: / + port: 13133 + initialDelaySeconds: 3 + resources: + limits: + cpu: 128m + memory: 256Mi + volumeMounts: + - mountPath: /conf + name: opentelemetry-collector-configmap + volumes: + - name: opentelemetry-collector-configmap + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay.yaml diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/role.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/role.yaml new file mode 100644 index 000000000000..45ef35c64af7 --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/role.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list"] + - apiGroups: ["apps"] + resources: ["replicasets"] + verbs: ["get", "watch", "list"] diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/rolebinding.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/rolebinding.yaml new file mode 100644 index 000000000000..875195f8a8a1 --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Name }} +subjects: + - kind: ServiceAccount + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/service.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/service.yaml new file mode 100644 index 000000000000..0eaaa6696a7f --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +spec: + type: ClusterIP + ports: + - name: otlp + port: 4317 + targetPort: 4317 + protocol: TCP + appProtocol: grpc + selector: + app.kubernetes.io/name: opentelemetry-collector + app.kubernetes.io/instance: {{ .Name }} diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/serviceaccount.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/serviceaccount.yaml new file mode 100644 index 000000000000..9e723f72346a --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/collector/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }} + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/namespace.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/namespace.yaml new file mode 100644 index 000000000000..3350dd9743ed --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: e2ek8sattribute-namespacedrbac-no-pod-ip + labels: + foons: barns diff --git a/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/telemetrygen/deployment.yaml b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/telemetrygen/deployment.yaml new file mode 100644 index 000000000000..953e21514e60 --- /dev/null +++ b/processor/k8sattributesprocessor/testdata/e2e/namespaced_rbac_no_pod_ip/telemetrygen/deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }}-{{ .DataType }}-deployment + namespace: e2ek8sattribute-namespacedrbac-no-pod-ip +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }}-{{ .DataType }}-deployment + template: + metadata: + annotations: + workload: deployment + labels: + app: {{ .Name }}-{{ .DataType }}-deployment + component: telemetrygen + spec: + containers: + - command: + - /telemetrygen + - {{ .DataType }} + - --otlp-insecure + - --otlp-endpoint={{ .OTLPEndpoint }} + - --duration=36000s + - --rate=1 + - --otlp-attributes=service.name="test-{{ .DataType }}-deployment" + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.deployment.name="{{ .Name }}-{{ .DataType }}-deployment" +{{- if eq .DataType "traces" }} + - --status-code= +{{- end }} + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest + imagePullPolicy: IfNotPresent + name: telemetrygen + restartPolicy: Always