From a22db7476b098f4063ff42f4563667bdf3f1d91a Mon Sep 17 00:00:00 2001 From: Mario Macias Date: Tue, 21 May 2024 12:44:01 +0200 Subject: [PATCH] Deal with unsupported owners for replicasets (#847) --- pkg/internal/discover/watcher_kube.go | 11 +-- pkg/internal/export/attributes/names/attrs.go | 19 ++--- pkg/internal/kube/informer.go | 21 ++---- pkg/internal/kube/owner.go | 69 +++++++++---------- pkg/internal/kube/owner_test.go | 47 ++++++++++++- pkg/transform/k8s.go | 2 +- pkg/transform/k8s_test.go | 4 +- 7 files changed, 105 insertions(+), 68 deletions(-) diff --git a/pkg/internal/discover/watcher_kube.go b/pkg/internal/discover/watcher_kube.go index 30c6a42df..ab2ab427f 100644 --- a/pkg/internal/discover/watcher_kube.go +++ b/pkg/internal/discover/watcher_kube.go @@ -148,7 +148,7 @@ func (wk *watcherKubeEnricher) enrichReplicaSetEvent(rsEvent Event[*kube.Replica switch rsEvent.Type { case EventCreated: wk.log.Debug("ReplicaSet added", "namespace", - rsEvent.Obj.Namespace, "name", rsEvent.Obj.Name, "deployment", rsEvent.Obj.DeploymentName) + rsEvent.Obj.Namespace, "name", rsEvent.Obj.Name, "owner", rsEvent.Obj.Owner) out <- wk.onNewReplicaSet(rsEvent.Obj) case EventDeleted: wk.log.Debug("ReplicaSet deleted", "namespace", rsEvent.Obj.Namespace, "name", rsEvent.Obj.Name) @@ -238,9 +238,10 @@ func (wk *watcherKubeEnricher) onNewReplicaSet(rsInfo *kube.ReplicaSetInfo) []Ev for _, pod := range podInfos { for _, containerID := range pod.ContainerIDs { if procInfo, ok := wk.processByContainer[containerID]; ok { - pod.Owner = &kube.Owner{Type: kube.OwnerReplicaSet, Name: rsInfo.Name} - if rsInfo.DeploymentName != "" { - pod.Owner.Owner = &kube.Owner{Type: kube.OwnerDeployment, Name: rsInfo.DeploymentName} + pod.Owner = &kube.Owner{ + LabelName: kube.OwnerReplicaSet, + Name: rsInfo.Name, + Owner: rsInfo.Owner, } allProcesses = append(allProcesses, Event[processAttrs]{ Type: EventCreated, @@ -310,7 +311,7 @@ func withMetadata(pp processAttrs, info *kube.PodInfo) processAttrs { owner := info.Owner for owner != nil { ret.metadata[services.AttrOwnerName] = owner.Name - switch owner.Type { + switch owner.LabelName { case kube.OwnerDaemonSet: ret.metadata[services.AttrDaemonSetName] = owner.Name case kube.OwnerReplicaSet: diff --git a/pkg/internal/export/attributes/names/attrs.go b/pkg/internal/export/attributes/names/attrs.go index 2696985ea..dc839ef34 100644 --- a/pkg/internal/export/attributes/names/attrs.go +++ b/pkg/internal/export/attributes/names/attrs.go @@ -47,15 +47,16 @@ const ( RPCGRPCStatusCode = Name(semconv.RPCGRPCStatusCodeKey) HTTPRoute = Name(semconv.HTTPRouteKey) - K8sNamespaceName = Name("k8s.namespace.name") - K8sPodName = Name("k8s.pod.name") - K8sDeploymentName = Name("k8s.deployment.name") - K8sReplicaSetName = Name("k8s.replicaset.name") - K8sDaemonSetName = Name("k8s.daemonset.name") - K8sStatefulSetName = Name("k8s.statefulset.name") - K8sNodeName = Name("k8s.node.name") - K8sPodUID = Name("k8s.pod.uid") - K8sPodStartTime = Name("k8s.pod.start_time") + K8sNamespaceName = Name("k8s.namespace.name") + K8sPodName = Name("k8s.pod.name") + K8sDeploymentName = Name("k8s.deployment.name") + K8sReplicaSetName = Name("k8s.replicaset.name") + K8sDaemonSetName = Name("k8s.daemonset.name") + K8sStatefulSetName = Name("k8s.statefulset.name") + K8sUnknownOwnerName = Name("k8s.owner.name") + K8sNodeName = Name("k8s.node.name") + K8sPodUID = Name("k8s.pod.uid") + K8sPodStartTime = Name("k8s.pod.start_time") ) // Beyla-specific network attributes diff --git a/pkg/internal/kube/informer.go b/pkg/internal/kube/informer.go index 551c04e95..41f0d0469 100644 --- a/pkg/internal/kube/informer.go +++ b/pkg/internal/kube/informer.go @@ -65,7 +65,7 @@ type PodInfo struct { type ReplicaSetInfo struct { metav1.ObjectMeta - DeploymentName string + Owner *Owner } func qName(namespace, name string) string { @@ -147,7 +147,7 @@ func (k *Metadata) initPodInformer(informerFactory informers.SharedInformerFacto } } - owner := OwnerFromPodInfo(pod) + owner := OwnerFrom(pod.OwnerReferences) startTime := pod.GetCreationTimestamp().String() if log.Enabled(context.TODO(), slog.LevelDebug) { log.Debug("inserting pod", "name", pod.Name, "namespace", pod.Namespace, @@ -233,24 +233,17 @@ func (k *Metadata) initReplicaSetInformer(informerFactory informers.SharedInform } return nil, fmt.Errorf("was expecting a ReplicaSet. Got: %T", i) } - var deployment string - for i := range rs.OwnerReferences { - or := &rs.OwnerReferences[i] - if or.APIVersion == "apps/v1" && or.Kind == "Deployment" { - deployment = or.Name - break - } - } + owner := OwnerFrom(rs.OwnerReferences) if log.Enabled(context.TODO(), slog.LevelDebug) { log.Debug("inserting ReplicaSet", "name", rs.Name, "namespace", rs.Namespace, - "deployment", deployment) + "owner", owner) } return &ReplicaSetInfo{ ObjectMeta: metav1.ObjectMeta{ Name: rs.Name, Namespace: rs.Namespace, }, - DeploymentName: deployment, + Owner: owner, }, nil }); err != nil { return fmt.Errorf("can't set pods transform: %w", err) @@ -328,9 +321,9 @@ func (k *Metadata) initInformers(ctx context.Context, client kubernetes.Interfac // usually has a Deployment as owner reference, which is the one that we'd really like // to report as owner. func (k *Metadata) FetchPodOwnerInfo(pod *PodInfo) { - if pod.Owner != nil && pod.Owner.Type == OwnerReplicaSet { + if pod.Owner != nil && pod.Owner.LabelName == OwnerReplicaSet { if rsi, ok := k.GetReplicaSetInfo(pod.Namespace, pod.Owner.Name); ok { - pod.Owner.Owner = &Owner{Type: OwnerDeployment, Name: rsi.DeploymentName} + pod.Owner.Owner = rsi.Owner } } } diff --git a/pkg/internal/kube/owner.go b/pkg/internal/kube/owner.go index e06acd27d..6d2f79693 100644 --- a/pkg/internal/kube/owner.go +++ b/pkg/internal/kube/owner.go @@ -3,63 +3,62 @@ package kube import ( "strings" - v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" attr "github.com/grafana/beyla/pkg/internal/export/attributes/names" ) -type OwnerType int +type OwnerLabel attr.Name const ( - OwnerUnknown = OwnerType(iota) - OwnerReplicaSet - OwnerDeployment - OwnerStatefulSet - OwnerDaemonSet + OwnerReplicaSet = OwnerLabel(attr.K8sReplicaSetName) + OwnerDeployment = OwnerLabel(attr.K8sDeploymentName) + OwnerStatefulSet = OwnerLabel(attr.K8sStatefulSetName) + OwnerDaemonSet = OwnerLabel(attr.K8sDaemonSetName) + OwnerUnknown = OwnerLabel(attr.K8sUnknownOwnerName) ) -func (o OwnerType) LabelName() attr.Name { - switch o { - case OwnerReplicaSet: - return attr.K8sReplicaSetName - case OwnerDeployment: - return attr.K8sDeploymentName - case OwnerStatefulSet: - return attr.K8sStatefulSetName - case OwnerDaemonSet: - return attr.K8sDaemonSetName - default: - return "k8s.unknown.owner" - } -} - type Owner struct { - Type OwnerType - Name string + LabelName OwnerLabel + Name string // Owner of the owner. For example, a ReplicaSet might be owned by a Deployment Owner *Owner } -// OwnerFromPodInfo returns the pod Owner reference. It might be -// null if the Pod does not have any owner -func OwnerFromPodInfo(pod *v1.Pod) *Owner { - for i := range pod.OwnerReferences { - or := &pod.OwnerReferences[i] +// OwnerFrom returns the most plausible Owner reference. It might be +// null if the entity does not have any owner +func OwnerFrom(orefs []metav1.OwnerReference) *Owner { + // fallback will store any found owner that is not part of the bundled + // K8s owner types (e.g. argocd rollouts). + // It will be returned if any of the standard K8s owners are found + var fallback *Owner + for i := range orefs { + or := &orefs[i] if or.APIVersion != "apps/v1" { + fallback = unrecognizedOwner(or) continue } switch or.Kind { case "ReplicaSet": - return &Owner{Type: OwnerReplicaSet, Name: or.Name} + return &Owner{LabelName: OwnerReplicaSet, Name: or.Name} case "Deployment": - return &Owner{Type: OwnerDeployment, Name: or.Name} + return &Owner{LabelName: OwnerDeployment, Name: or.Name} case "StatefulSet": - return &Owner{Type: OwnerStatefulSet, Name: or.Name} + return &Owner{LabelName: OwnerStatefulSet, Name: or.Name} case "DaemonSet": - return &Owner{Type: OwnerDaemonSet, Name: or.Name} + return &Owner{LabelName: OwnerDaemonSet, Name: or.Name} + default: + fallback = unrecognizedOwner(or) } } - return nil + return fallback +} + +func unrecognizedOwner(or *metav1.OwnerReference) *Owner { + return &Owner{ + LabelName: OwnerLabel(attr.K8sUnknownOwnerName), + Name: or.Name, + } } func (o *Owner) String() string { @@ -73,7 +72,7 @@ func (o *Owner) string(sb *strings.Builder) { o.Owner.string(sb) sb.WriteString("->") } - sb.WriteString(string(o.Type.LabelName())) + sb.WriteString(string(o.LabelName)) sb.WriteByte(':') sb.WriteString(o.Name) } diff --git a/pkg/internal/kube/owner_test.go b/pkg/internal/kube/owner_test.go index 4fe5156f5..ccab81159 100644 --- a/pkg/internal/kube/owner_test.go +++ b/pkg/internal/kube/owner_test.go @@ -1,14 +1,57 @@ package kube import ( + "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestOwnerString(t *testing.T) { - owner := Owner{Type: OwnerReplicaSet, Name: "rs"} + owner := Owner{LabelName: OwnerReplicaSet, Name: "rs"} assert.Equal(t, "k8s.replicaset.name:rs", owner.String()) - owner.Owner = &Owner{Type: OwnerDeployment, Name: "dep"} + owner.Owner = &Owner{LabelName: OwnerDeployment, Name: "dep"} assert.Equal(t, "k8s.deployment.name:dep->k8s.replicaset.name:rs", owner.String()) } + +func TestOwnerFrom(t *testing.T) { + for _, kind := range []string{"ReplicaSet", "Deployment", "StatefulSet", "DaemonSet"} { + t.Run(kind, func(t *testing.T) { + owner := OwnerFrom([]v1.OwnerReference{ + {APIVersion: "foo/bar", Kind: kind, Name: "no"}, + {APIVersion: "apps/v1", Kind: "Unknown", Name: "no"}, + {APIVersion: "apps/v1", Kind: kind, Name: "theowner"}, + }) + require.NotNil(t, owner) + assert.Equal(t, &Owner{ + LabelName: OwnerLabel(fmt.Sprintf("k8s.%s.name", strings.ToLower(kind))), + Name: "theowner", + }, owner) + }) + } +} + +func TestOwnerFrom_Unrecognized(t *testing.T) { + owner := OwnerFrom([]v1.OwnerReference{ + {APIVersion: "foo/v1", Kind: "Unknown", Name: "theowner"}, + }) + require.NotNil(t, owner) + assert.Equal(t, &Owner{ + LabelName: OwnerUnknown, + Name: "theowner", + }, owner) +} + +func TestOwnerFrom_Unrecognized_AppsV1(t *testing.T) { + owner := OwnerFrom([]v1.OwnerReference{ + {APIVersion: "apps/v1", Kind: "Unknown", Name: "theowner"}, + }) + require.NotNil(t, owner) + assert.Equal(t, &Owner{ + LabelName: OwnerUnknown, + Name: "theowner", + }, owner) +} diff --git a/pkg/transform/k8s.go b/pkg/transform/k8s.go index 879d6253c..75fde4ea8 100644 --- a/pkg/transform/k8s.go +++ b/pkg/transform/k8s.go @@ -132,7 +132,7 @@ func appendMetadata(span *request.Span, info *kube.PodInfo) { } owner := info.Owner for owner != nil { - span.ServiceID.Metadata[owner.Type.LabelName()] = owner.Name + span.ServiceID.Metadata[attr.Name(owner.LabelName)] = owner.Name owner = owner.Owner } } diff --git a/pkg/transform/k8s_test.go b/pkg/transform/k8s_test.go index 3f81ab289..d83d4d038 100644 --- a/pkg/transform/k8s_test.go +++ b/pkg/transform/k8s_test.go @@ -26,7 +26,7 @@ func TestDecoration(t *testing.T) { }, NodeName: "the-node", StartTimeStr: "2020-01-02 12:12:56", - Owner: &kube.Owner{Type: kube.OwnerDeployment, Name: "deployment-12"}, + Owner: &kube.Owner{LabelName: kube.OwnerDeployment, Name: "deployment-12"}, }, 34: &kube.PodInfo{ ObjectMeta: v1.ObjectMeta{ @@ -34,7 +34,7 @@ func TestDecoration(t *testing.T) { }, NodeName: "the-node", StartTimeStr: "2020-01-02 12:34:56", - Owner: &kube.Owner{Type: kube.OwnerReplicaSet, Name: "rs-34"}, + Owner: &kube.Owner{LabelName: kube.OwnerReplicaSet, Name: "rs-34"}, }, 56: &kube.PodInfo{ ObjectMeta: v1.ObjectMeta{