From cd49f71a1250dec9872712d9356e9cfef21ea84b Mon Sep 17 00:00:00 2001 From: Nour Date: Sun, 21 Sep 2025 21:45:02 +0300 Subject: [PATCH] Add kube_deployment_owner metric --- docs/metrics/workload/deployment-metrics.md | 1 + internal/store/deployment.go | 46 ++++++++++++++++ internal/store/deployment_test.go | 58 +++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/docs/metrics/workload/deployment-metrics.md b/docs/metrics/workload/deployment-metrics.md index e8d0fdb42..cab9ff154 100644 --- a/docs/metrics/workload/deployment-metrics.md +++ b/docs/metrics/workload/deployment-metrics.md @@ -18,3 +18,4 @@ | kube_deployment_labels | Gauge | Kubernetes labels converted to Prometheus labels controlled via [--metric-labels-allowlist](../../developer/cli-arguments.md) | `deployment`=<deployment-name>
`namespace`=<deployment-namespace>
`label_DEPLOYMENT_LABEL`=<DEPLOYMENT_LABEL> | STABLE | | kube_deployment_created | Gauge | Unix creation timestamp | `deployment`=<deployment-name>
`namespace`=<deployment-namespace> | STABLE | | kube_deployment_deletion_timestamp | Gauge | Unix deletion timestamp | `deployment`=<deployment-name>
`namespace`=<deployment-namespace> | EXPIREMENTAL | +| kube_deployment_owner | Gauge | Information about the Deployment's owner. | `deployment`=<deployment-name>
`namespace`=<deployment-namespace>
`owner_kind`=<owner-kind>
`owner_name`=<owner-name>
`owner_is_controller`=<true\|false> | ALPHA | diff --git a/internal/store/deployment.go b/internal/store/deployment.go index 31e3b200a..b610a387c 100644 --- a/internal/store/deployment.go +++ b/internal/store/deployment.go @@ -18,6 +18,7 @@ package store import ( "context" + "strconv" basemetrics "k8s.io/component-base/metrics" @@ -60,6 +61,51 @@ var ( func deploymentMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { return []generator.FamilyGenerator{ + *generator.NewFamilyGeneratorWithStability( + "kube_deployment_owner", + "Information about the Deployment's owner.", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapDeploymentFunc(func(d *v1.Deployment) *metric.Family { + labelKeys := []string{"owner_kind", "owner_name", "owner_is_controller"} + + owners := d.GetOwnerReferences() + if len(owners) == 0 { + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: []string{"", "", ""}, + Value: 1, + }, + }, + } + } + + ms := make([]*metric.Metric, len(owners)) + + for i, owner := range owners { + if owner.Controller != nil { + ms[i] = &metric.Metric{ + LabelKeys: labelKeys, + LabelValues: []string{owner.Kind, owner.Name, strconv.FormatBool(*owner.Controller)}, + Value: 1, + } + } else { + ms[i] = &metric.Metric{ + LabelKeys: labelKeys, + LabelValues: []string{owner.Kind, owner.Name, "false"}, + Value: 1, + } + } + } + + return &metric.Family{ + Metrics: ms, + } + }), + ), *generator.NewFamilyGeneratorWithStability( "kube_deployment_created", "Unix creation timestamp", diff --git a/internal/store/deployment_test.go b/internal/store/deployment_test.go index a0e3f4aa1..d8f0bd1d5 100644 --- a/internal/store/deployment_test.go +++ b/internal/store/deployment_test.go @@ -45,6 +45,8 @@ func TestDeploymentStore(t *testing.T) { // Fixed metadata on type and help text. We prepend this to every expected // output so we only have to modify a single place when doing adjustments. const metadata = ` + # HELP kube_deployment_owner Information about the Deployment's owner. + # TYPE kube_deployment_owner gauge # HELP kube_deployment_annotations Kubernetes annotations converted to Prometheus labels. # TYPE kube_deployment_annotations gauge # HELP kube_deployment_created [STABLE] Unix creation timestamp @@ -119,6 +121,7 @@ func TestDeploymentStore(t *testing.T) { Want: metadata + ` kube_deployment_annotations{annotation_company_io_team="my-brilliant-team",deployment="depl1",namespace="ns1"} 1 kube_deployment_created{deployment="depl1",namespace="ns1"} 1.5e+09 + kube_deployment_owner{deployment="depl1",namespace="ns1",owner_kind="",owner_name="",owner_is_controller=""} 1 kube_deployment_metadata_generation{deployment="depl1",namespace="ns1"} 21 kube_deployment_spec_paused{deployment="depl1",namespace="ns1"} 0 kube_deployment_spec_replicas{deployment="depl1",namespace="ns1"} 200 @@ -174,6 +177,7 @@ func TestDeploymentStore(t *testing.T) { }, Want: metadata + ` kube_deployment_metadata_generation{deployment="depl2",namespace="ns2"} 14 + kube_deployment_owner{deployment="depl2",namespace="ns2",owner_kind="",owner_name="",owner_is_controller=""} 1 kube_deployment_spec_paused{deployment="depl2",namespace="ns2"} 1 kube_deployment_spec_replicas{deployment="depl2",namespace="ns2"} 5 kube_deployment_spec_strategy_rollingupdate_max_surge{deployment="depl2",namespace="ns2"} 1 @@ -213,6 +217,7 @@ func TestDeploymentStore(t *testing.T) { }, Want: metadata + ` kube_deployment_metadata_generation{deployment="depl3",namespace="ns3"} 0 + kube_deployment_owner{deployment="depl3",namespace="ns3",owner_kind="",owner_name="",owner_is_controller=""} 1 kube_deployment_spec_paused{deployment="depl3",namespace="ns3"} 0 kube_deployment_spec_replicas{deployment="depl3",namespace="ns3"} 1 kube_deployment_status_condition{condition="Available",deployment="depl3",namespace="ns3",reason="unknown",status="true"} 0 @@ -252,6 +257,59 @@ func TestDeploymentStore(t *testing.T) { kube_deployment_deletion_timestamp{deployment="deployment-terminating",namespace="ns4"} 1.8e+09`, MetricNames: []string{"kube_deployment_deletion_timestamp"}, }, + { + Obj: &v1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-with-owner", + Namespace: "ns5", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "Application", + Name: "my-app", + Controller: &[]bool{true}[0], + }, + }, + }, + Spec: v1.DeploymentSpec{ + Replicas: &depl1Replicas, + }, + }, + Want: metadata + ` + kube_deployment_owner{deployment="deployment-with-owner",namespace="ns5",owner_kind="Application",owner_name="my-app",owner_is_controller="true"} 1 + kube_deployment_metadata_generation{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_spec_paused{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_spec_replicas{deployment="deployment-with-owner",namespace="ns5"} 200 + kube_deployment_status_observed_generation{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_status_replicas{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_available{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_ready{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_unavailable{deployment="deployment-with-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_updated{deployment="deployment-with-owner",namespace="ns5"} 0 + `, + }, + { + Obj: &v1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment-without-owner", + Namespace: "ns5", + }, + Spec: v1.DeploymentSpec{ + Replicas: &depl1Replicas, + }, + }, + Want: metadata + ` + kube_deployment_owner{deployment="deployment-without-owner",namespace="ns5",owner_kind="",owner_name="",owner_is_controller=""} 1 + kube_deployment_metadata_generation{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_spec_paused{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_spec_replicas{deployment="deployment-without-owner",namespace="ns5"} 200 + kube_deployment_status_observed_generation{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_status_replicas{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_available{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_ready{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_unavailable{deployment="deployment-without-owner",namespace="ns5"} 0 + kube_deployment_status_replicas_updated{deployment="deployment-without-owner",namespace="ns5"} 0 + `, + }, } for i, c := range cases { c.Func = generator.ComposeMetricGenFuncs(deploymentMetricFamilies(c.AllowAnnotationsList, nil))