From 9fc15db2fac4ec81d3a03e2668e3fe54d60f7dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= <69143962+pmalek-sumo@users.noreply.github.com> Date: Thu, 1 Oct 2020 11:50:46 +0200 Subject: [PATCH] k8sprocessor: add host.name and container.name fields (#188) --- processor/k8sprocessor/config_test.go | 29 ++++++++++---- processor/k8sprocessor/kube/client.go | 25 ++++++++++++ processor/k8sprocessor/kube/client_test.go | 27 +++++++++---- processor/k8sprocessor/kube/kube.go | 16 ++++---- processor/k8sprocessor/options.go | 44 ++++++++++++--------- processor/k8sprocessor/options_test.go | 4 ++ processor/k8sprocessor/testdata/config.yaml | 8 ++-- 7 files changed, 111 insertions(+), 42 deletions(-) diff --git a/processor/k8sprocessor/config_test.go b/processor/k8sprocessor/config_test.go index df447c2e1193..06f767fe62d5 100644 --- a/processor/k8sprocessor/config_test.go +++ b/processor/k8sprocessor/config_test.go @@ -41,23 +41,26 @@ func TestLoadConfig(t *testing.T) { config, err := configtest.LoadConfigFile( t, path.Join(".", "testdata", "config.yaml"), - factories) + factories, + ) - require.Nil(t, err) + require.NoError(t, err) require.NotNil(t, config) p0 := config.Processors["k8s_tagger"] - assert.Equal(t, p0, + assert.Equal(t, &Config{ ProcessorSettings: configmodels.ProcessorSettings{ TypeVal: "k8s_tagger", NameVal: "k8s_tagger", }, APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeServiceAccount}, - }) + }, + p0, + ) p1 := config.Processors["k8s_tagger/2"] - assert.Equal(t, p1, + assert.Equal(t, &Config{ ProcessorSettings: configmodels.ProcessorSettings{ TypeVal: "k8s_tagger", @@ -66,7 +69,17 @@ func TestLoadConfig(t *testing.T) { APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeKubeConfig}, Passthrough: false, Extract: ExtractConfig{ - Metadata: []string{"podName", "podUID", "deployment", "cluster", "namespace", "node", "startTime"}, + Metadata: []string{ + "cluster", + "containerName", + "deployment", + "hostName", + "namespace", + "node", + "podName", + "podUID", + "startTime", + }, Annotations: []FieldExtractConfig{ {TagName: "a1", Key: "annotation-one"}, {TagName: "a2", Key: "annotation-two", Regex: "field=(?P.+)"}, @@ -89,5 +102,7 @@ func TestLoadConfig(t *testing.T) { {Key: "key2", Value: "value2", Op: "not-equals"}, }, }, - }) + }, + p1, + ) } diff --git a/processor/k8sprocessor/kube/client.go b/processor/k8sprocessor/kube/client.go index 5c09bdda185b..55299ad60124 100644 --- a/processor/k8sprocessor/kube/client.go +++ b/processor/k8sprocessor/kube/client.go @@ -17,6 +17,7 @@ package kube import ( "fmt" "regexp" + "sort" "strings" "sync" "time" @@ -235,6 +236,30 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string { tags[r.Name] = c.extractField(v, r) } } + + if c.Rules.HostName { + // Basing on v1.17 Kubernetes docs, when a hostname is specified, it takes precedence over + // the associated metadata name, see: + // https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-hostname-and-subdomain-fields + if pod.Spec.Hostname == "" { + tags[conventions.AttributeHostName] = pod.Name + } else { + tags[conventions.AttributeHostName] = pod.Spec.Hostname + } + } + + if len(pod.Spec.Containers) > 0 { + if c.Rules.ContainerName { + var names []string + for _, container := range pod.Spec.Containers { + names = append(names, container.Name) + } + + sort.Strings(names) + tags[conventions.AttributeContainerName] = strings.Join(names, ",") + } + } + return tags } diff --git a/processor/k8sprocessor/kube/client_test.go b/processor/k8sprocessor/kube/client_test.go index 12a559eb91cb..9a5505f95572 100644 --- a/processor/k8sprocessor/kube/client_test.go +++ b/processor/k8sprocessor/kube/client_test.go @@ -323,6 +323,15 @@ func TestExtractionRules(t *testing.T) { }, Spec: api_v1.PodSpec{ NodeName: "node1", + Hostname: "myhostname", + Containers: []api_v1.Container{ + { + Name: "container-zzzzz", + }, + { + Name: "sidecar-container-aaaaa", + }, + }, }, Status: api_v1.PodStatus{ PodIP: "1.1.1.1", @@ -348,13 +357,15 @@ func TestExtractionRules(t *testing.T) { }, { name: "metadata", rules: ExtractionRules{ - Deployment: true, - Namespace: true, - PodName: true, - PodUID: true, - Node: true, - Cluster: true, - StartTime: true, + Deployment: true, + Namespace: true, + PodName: true, + PodUID: true, + Node: true, + Cluster: true, + StartTime: true, + HostName: true, + ContainerName: true, }, attributes: map[string]string{ "k8s.deployment.name": "auth-service", @@ -364,6 +375,8 @@ func TestExtractionRules(t *testing.T) { "k8s.pod.name": "auth-service-abc12-xyz3", "k8s.pod.uid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "k8s.pod.startTime": pod.GetCreationTimestamp().String(), + "container.name": "container-zzzzz,sidecar-container-aaaaa", + "host.name": "myhostname", }, }, { name: "labels", diff --git a/processor/k8sprocessor/kube/kube.go b/processor/k8sprocessor/kube/kube.go index b3611de2e3d6..1c754aad6d36 100644 --- a/processor/k8sprocessor/kube/kube.go +++ b/processor/k8sprocessor/kube/kube.go @@ -102,13 +102,15 @@ type FieldFilter struct { // ExtractionRules is used to specify the information that needs to be extracted // from pods and added to the spans as tags. type ExtractionRules struct { - Deployment bool - Namespace bool - PodName bool - PodUID bool - Node bool - Cluster bool - StartTime bool + Deployment bool + Namespace bool + PodName bool + PodUID bool + Node bool + Cluster bool + StartTime bool + HostName bool + ContainerName bool Annotations []FieldExtractionRule Labels []FieldExtractionRule diff --git a/processor/k8sprocessor/options.go b/processor/k8sprocessor/options.go index fbe79ecfcb4b..a02023a3d40b 100644 --- a/processor/k8sprocessor/options.go +++ b/processor/k8sprocessor/options.go @@ -31,13 +31,15 @@ const ( filterOPExists = "exists" filterOPDoesNotExist = "does-not-exist" - metdataNamespace = "namespace" - metadataPodName = "podName" - metadataPodUID = "podUID" - metadataStartTime = "startTime" - metadataDeployment = "deployment" - metadataCluster = "cluster" - metadataNode = "node" + metadataNamespace = "namespace" + metadataPodName = "podName" + metadataPodUID = "podUID" + metadataStartTime = "startTime" + metadataDeployment = "deployment" + metadataCluster = "cluster" + metadataNode = "node" + metadataHostName = "hostName" + metadataContainerName = "containerName" ) // Option represents a configuration option that can be passes. @@ -68,31 +70,37 @@ func WithExtractMetadata(fields ...string) Option { return func(p *kubernetesprocessor) error { if len(fields) == 0 { fields = []string{ - metdataNamespace, + metadataCluster, + metadataContainerName, + metadataDeployment, + metadataHostName, + metadataNamespace, + metadataNode, metadataPodName, metadataPodUID, metadataStartTime, - metadataDeployment, - metadataCluster, - metadataNode, } } for _, field := range fields { switch field { - case metdataNamespace: + case metadataCluster: + p.rules.Cluster = true + case metadataContainerName: + p.rules.ContainerName = true + case metadataDeployment: + p.rules.Deployment = true + case metadataHostName: + p.rules.HostName = true + case metadataNamespace: p.rules.Namespace = true + case metadataNode: + p.rules.Node = true case metadataPodName: p.rules.PodName = true case metadataPodUID: p.rules.PodUID = true case metadataStartTime: p.rules.StartTime = true - case metadataDeployment: - p.rules.Deployment = true - case metadataCluster: - p.rules.Cluster = true - case metadataNode: - p.rules.Node = true default: return fmt.Errorf("\"%s\" is not a supported metadata field", field) } diff --git a/processor/k8sprocessor/options_test.go b/processor/k8sprocessor/options_test.go index f28c63545e49..d0e4e9f03076 100644 --- a/processor/k8sprocessor/options_test.go +++ b/processor/k8sprocessor/options_test.go @@ -205,6 +205,8 @@ func TestWithExtractMetadata(t *testing.T) { assert.True(t, p.rules.Deployment) assert.True(t, p.rules.Cluster) assert.True(t, p.rules.Node) + assert.True(t, p.rules.HostName) + assert.True(t, p.rules.ContainerName) p = &kubernetesprocessor{} err := WithExtractMetadata("randomfield")(p) @@ -219,6 +221,8 @@ func TestWithExtractMetadata(t *testing.T) { assert.False(t, p.rules.StartTime) assert.False(t, p.rules.Deployment) assert.False(t, p.rules.Node) + assert.False(t, p.rules.HostName) + assert.False(t, p.rules.ContainerName) } func TestWithFilterLabels(t *testing.T) { diff --git a/processor/k8sprocessor/testdata/config.yaml b/processor/k8sprocessor/testdata/config.yaml index f0df8bb840ba..e15ebe1bec20 100644 --- a/processor/k8sprocessor/testdata/config.yaml +++ b/processor/k8sprocessor/testdata/config.yaml @@ -9,12 +9,14 @@ processors: extract: metadata: # extract the following well-known metadata fields - - podName - - podUID - - deployment - cluster + - containerName + - deployment + - hostName - namespace - node + - podName + - podUID - startTime annotations: