Repository: kubernetes/kubernetes (Go)

Issue:
apiserver_storage_objects metrics still exists after crd deleted
### What happened?

<img width="1498" alt="Image" src="https://github.com/user-attachments/assets/580f8f3f-ac8c-42ab-95b9-b367f27ba027" />

```bash
kubectl get ephemeralreports.reports.kyverno.io
error: the server doesn't have a resource type "ephemeralreports"
```

apiserver_storage_objects metrics still exists after crd deleted

### What did you expect to happen?

remove apiserver_storage_objects metrics after crd deleted

### How can we reproduce it (as minimally and precisely as possible)?

Create a CRD
Create some CR for CRD
Check apiserver_storage_objects metrics
Delete CRD
Check apiserver_storage_objects metrics

### Anything else we need to know?

_No response_

### Kubernetes version

v1.32.0



Discussion:
There are no sig labels on this issue. Please add an appropriate label by using one of the following commands:
- `/sig <group-name>`
- `/wg <group-name>`
- `/committee <group-name>`

Please see the [group list](https://git.k8s.io/community/sig-list.md) for a listing of the SIGs, working groups, and committees available.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>



Patch:
diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go
index 83644ce834cc3..a57d524efa973 100644
--- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go
+++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go
@@ -1678,7 +1678,10 @@ func (e *Store) startObservingCount(period time.Duration, objectCountTracker flo
 			objectCountTracker.Set(resourceName, stats)
 		}
 	}, period, resourceCountPollPeriodJitter, true, stopCh)
-	return func() { close(stopCh) }
+	return func() {
+		metrics.DeleteObjectCount(e.DefaultQualifiedResource)
+		close(stopCh)
+	}
 }
 
 func (e *Store) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go
index 28361c390cb49..41e1647c3286a 100644
--- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go
+++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics.go
@@ -184,6 +184,11 @@ func UpdateObjectCount(groupResource schema.GroupResource, count int64) {
 	objectCounts.WithLabelValues(groupResource.String()).Set(float64(count))
 }
 
+// DeleteObjectCount delete the apiserver_storage_object_counts metric.
+func DeleteObjectCount(groupResource schema.GroupResource) {
+	objectCounts.DeleteLabelValues(groupResource.String())
+}
+
 // RecordEtcdRequest updates and sets the etcd_request_duration_seconds,
 // etcd_request_total, etcd_request_errors_total metrics.
 func RecordEtcdRequest(verb string, groupResource schema.GroupResource, err error, startTime time.Time) {


Test patch:
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go
index 07de97e1f026e..67468cc1585fd 100644
--- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go
+++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/metrics/metrics_test.go
@@ -268,6 +268,42 @@ apiserver_storage_objects{resource="bar.foo"} -1
 	}
 }
 
+func TestDeleteObjectCount(t *testing.T) {
+	registry := metrics.NewKubeRegistry()
+	registry.MustRegister(objectCounts)
+	testedMetrics := "apiserver_storage_objects"
+
+	UpdateObjectCount(schema.GroupResource{Group: "foo1", Resource: "bar1"}, int64(10))
+	UpdateObjectCount(schema.GroupResource{Group: "foo2", Resource: "bar2"}, int64(20))
+
+	expectedMetrics := `# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind. In case of a fetching error, the value will be -1.
+# TYPE apiserver_storage_objects gauge
+apiserver_storage_objects{resource="bar1.foo1"} 10
+apiserver_storage_objects{resource="bar2.foo2"} 20
+`
+	if err := testutil.GatherAndCompare(registry, strings.NewReader(expectedMetrics), testedMetrics); err != nil {
+		t.Fatal(err)
+	}
+
+	DeleteObjectCount(schema.GroupResource{Group: "foo1", Resource: "bar1"})
+
+	expectedMetrics = `# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind. In case of a fetching error, the value will be -1.
+# TYPE apiserver_storage_objects gauge
+apiserver_storage_objects{resource="bar2.foo2"} 20
+`
+	if err := testutil.GatherAndCompare(registry, strings.NewReader(expectedMetrics), testedMetrics); err != nil {
+		t.Fatal(err)
+	}
+
+	DeleteObjectCount(schema.GroupResource{Group: "foo2", Resource: "bar2"})
+	expectedMetrics = `# HELP apiserver_storage_objects [STABLE] Number of stored objects at the time of last check split by kind. In case of a fetching error, the value will be -1.
+# TYPE apiserver_storage_objects gauge
+`
+	if err := testutil.GatherAndCompare(registry, strings.NewReader(expectedMetrics), testedMetrics); err != nil {
+		t.Fatal(err)
+	}
+}
+
 type fakeEtcdMonitor struct {
 	storageSize int64
 }