Skip to content

Commit 5b85ea1

Browse files
authored
Merge pull request #3496 from cnvergence/add-lc-metrics
Add metrics for logical clusters count
2 parents 64bdd4a + 57a23b9 commit 5b85ea1

File tree

3 files changed

+148
-11
lines changed

3 files changed

+148
-11
lines changed

pkg/reconciler/tenancy/logicalcluster/logicalcluster_controller.go

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"sync"
2324
"time"
2425

2526
authenticationv1 "k8s.io/api/authentication/v1"
@@ -37,9 +38,11 @@ import (
3738
kcprbacinformers "github.com/kcp-dev/client-go/informers/rbac/v1"
3839
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"
3940
kcprbaclisters "github.com/kcp-dev/client-go/listers/rbac/v1"
41+
"github.com/kcp-dev/logicalcluster/v3"
4042

4143
"github.com/kcp-dev/kcp/pkg/logging"
4244
"github.com/kcp-dev/kcp/pkg/reconciler/events"
45+
kcpmetrics "github.com/kcp-dev/kcp/pkg/server/metrics"
4346
corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
4447
tenancyv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
4548
corev1alpha1informers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions/core/v1alpha1"
@@ -58,6 +61,7 @@ func NewController(
5861
kubeClusterClient kcpkubernetesclientset.ClusterInterface,
5962
logicalClusterInformer corev1alpha1informers.LogicalClusterClusterInformer,
6063
clusterRoleBindingInformer kcprbacinformers.ClusterRoleBindingClusterInformer,
64+
shardName string,
6165
) *Controller {
6266
c := &Controller{
6367
queue: workqueue.NewTypedRateLimitingQueueWithConfig(
@@ -69,26 +73,36 @@ func NewController(
6973
kubeClusterClient: kubeClusterClient,
7074
logicalClusterLister: logicalClusterInformer.Lister(),
7175
clusterRoleBindingLister: clusterRoleBindingInformer.Lister(),
76+
shardName: shardName,
77+
countedClusters: make(map[string]string),
7278
}
7379

7480
_, _ = logicalClusterInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
75-
AddFunc: func(obj interface{}) { c.enqueue(obj) },
76-
UpdateFunc: func(obj, _ interface{}) { c.enqueue(obj) },
77-
DeleteFunc: func(obj interface{}) { c.enqueue(obj) },
81+
AddFunc: func(obj any) {
82+
c.enqueue(obj)
83+
c.handleMetricsOnAdd(obj)
84+
},
85+
UpdateFunc: func(oldObj, newObj any) {
86+
c.enqueue(newObj)
87+
c.handleMetricsOnUpdate(oldObj, newObj)
88+
},
89+
DeleteFunc: func(obj any) {
90+
c.enqueue(obj)
91+
c.handleMetricsOnDelete(obj)
92+
},
7893
})
79-
8094
_, _ = clusterRoleBindingInformer.Informer().AddEventHandler(events.WithoutSyncs(cache.FilteringResourceEventHandler{
81-
FilterFunc: func(obj interface{}) bool {
95+
FilterFunc: func(obj any) bool {
8296
crb, ok := obj.(*rbacv1.ClusterRoleBinding)
8397
if !ok {
8498
return false
8599
}
86100
return crb.Name == workspaceAdminClusterRoleBindingName
87101
},
88102
Handler: cache.ResourceEventHandlerFuncs{
89-
AddFunc: func(obj interface{}) { c.enqueueCRB(obj) },
90-
UpdateFunc: func(obj, _ interface{}) { c.enqueueCRB(obj) },
91-
DeleteFunc: func(obj interface{}) { c.enqueueCRB(obj) },
103+
AddFunc: func(obj any) { c.enqueueCRB(obj) },
104+
UpdateFunc: func(obj, _ any) { c.enqueueCRB(obj) },
105+
DeleteFunc: func(obj any) { c.enqueueCRB(obj) },
92106
},
93107
}))
94108

@@ -104,9 +118,12 @@ type Controller struct {
104118
logicalClusterLister corev1alpha1listers.LogicalClusterClusterLister
105119

106120
clusterRoleBindingLister kcprbaclisters.ClusterRoleBindingClusterLister
121+
mu sync.Mutex
122+
countedClusters map[string]string
123+
shardName string
107124
}
108125

109-
func (c *Controller) enqueue(obj interface{}) {
126+
func (c *Controller) enqueue(obj any) {
110127
key, err := kcpcache.DeletionHandlingMetaClusterNamespaceKeyFunc(obj)
111128
if err != nil {
112129
utilruntime.HandleError(err)
@@ -117,7 +134,7 @@ func (c *Controller) enqueue(obj interface{}) {
117134
c.queue.Add(key)
118135
}
119136

120-
func (c *Controller) enqueueCRB(obj interface{}) {
137+
func (c *Controller) enqueueCRB(obj any) {
121138
key, err := kcpcache.DeletionHandlingMetaClusterNamespaceKeyFunc(obj)
122139
if err != nil {
123140
utilruntime.HandleError(err)
@@ -189,7 +206,6 @@ func (c *Controller) process(ctx context.Context, key string) error {
189206
if !apierrors.IsNotFound(err) {
190207
logger.Error(err, "failed to get LogicalCluster from lister", "cluster", clusterName)
191208
}
192-
193209
return nil // nothing we can do here
194210
}
195211

@@ -252,3 +268,76 @@ func (c *Controller) process(ctx context.Context, key string) error {
252268
_, err = c.kubeClusterClient.Cluster(clusterName.Path()).RbacV1().ClusterRoleBindings().Update(ctx, newBinding, metav1.UpdateOptions{})
253269
return err
254270
}
271+
272+
func (c *Controller) handleMetricsOnAdd(obj any) {
273+
logicalCluster, ok := obj.(*corev1alpha1.LogicalCluster)
274+
if !ok {
275+
return
276+
}
277+
278+
c.mu.Lock()
279+
defer c.mu.Unlock()
280+
281+
clusterKey := string(logicalcluster.From(logicalCluster))
282+
phase := string(logicalCluster.Status.Phase)
283+
if _, exists := c.countedClusters[clusterKey]; !exists {
284+
c.countedClusters[clusterKey] = phase
285+
if phase != "" {
286+
kcpmetrics.IncrementLogicalClusterCount(c.shardName, phase)
287+
}
288+
}
289+
}
290+
291+
func (c *Controller) handleMetricsOnUpdate(oldObj, newObj any) {
292+
oldLogicalCluster, ok := oldObj.(*corev1alpha1.LogicalCluster)
293+
if !ok {
294+
return
295+
}
296+
297+
newLogicalCluster, ok := newObj.(*corev1alpha1.LogicalCluster)
298+
if !ok {
299+
return
300+
}
301+
302+
c.mu.Lock()
303+
defer c.mu.Unlock()
304+
305+
clusterKey := string(logicalcluster.From(newLogicalCluster))
306+
oldPhase := string(oldLogicalCluster.Status.Phase)
307+
newPhase := string(newLogicalCluster.Status.Phase)
308+
309+
if oldPhase != newPhase {
310+
if oldPhase != "" {
311+
kcpmetrics.DecrementLogicalClusterCount(c.shardName, oldPhase)
312+
}
313+
if newPhase != "" {
314+
kcpmetrics.IncrementLogicalClusterCount(c.shardName, newPhase)
315+
}
316+
c.countedClusters[clusterKey] = newPhase
317+
}
318+
}
319+
320+
func (c *Controller) handleMetricsOnDelete(obj any) {
321+
logicalCluster, ok := obj.(*corev1alpha1.LogicalCluster)
322+
if !ok {
323+
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
324+
logicalCluster, ok = tombstone.Obj.(*corev1alpha1.LogicalCluster)
325+
if !ok {
326+
return
327+
}
328+
} else {
329+
return
330+
}
331+
}
332+
333+
c.mu.Lock()
334+
defer c.mu.Unlock()
335+
336+
clusterKey := string(logicalcluster.From(logicalCluster))
337+
if phase, exists := c.countedClusters[clusterKey]; exists {
338+
delete(c.countedClusters, clusterKey)
339+
if phase != "" {
340+
kcpmetrics.DecrementLogicalClusterCount(c.shardName, phase)
341+
}
342+
}
343+
}

pkg/server/controllers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ func (s *Server) installTenancyLogicalClusterController(ctx context.Context, con
436436
kubeClusterClient,
437437
s.KcpSharedInformerFactory.Core().V1alpha1().LogicalClusters(),
438438
s.KubeSharedInformerFactory.Rbac().V1().ClusterRoleBindings(),
439+
s.Options.Extra.ShardName,
439440
)
440441

441442
return s.registerController(&controllerWrapper{

pkg/server/metrics/metrics.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"k8s.io/component-base/metrics"
21+
"k8s.io/component-base/metrics/legacyregistry"
22+
)
23+
24+
var (
25+
logicalClusterCount = metrics.NewGaugeVec(
26+
&metrics.GaugeOpts{
27+
Name: "kcp_logicalcluster_count",
28+
Help: "Number of logical clusters currently running with specific phases on this shard.",
29+
StabilityLevel: metrics.ALPHA,
30+
},
31+
[]string{"shard", "phase"},
32+
)
33+
)
34+
35+
func init() {
36+
legacyregistry.MustRegister(logicalClusterCount)
37+
}
38+
39+
// IncrementLogicalClusterCount increments the count for the given shard and phase.
40+
func IncrementLogicalClusterCount(shardName string, phase string) {
41+
logicalClusterCount.WithLabelValues(shardName, phase).Inc()
42+
}
43+
44+
// DecrementLogicalClusterCount decrements the count for the given shard and phase.
45+
func DecrementLogicalClusterCount(shardName string, phase string) {
46+
logicalClusterCount.WithLabelValues(shardName, phase).Dec()
47+
}

0 commit comments

Comments
 (0)