Skip to content

Commit ca777d9

Browse files
committed
add metrics to track the managed resource count
1 parent 2ff2e59 commit ca777d9

File tree

5 files changed

+165
-18
lines changed

5 files changed

+165
-18
lines changed

Diff for: main.go

+26-7
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,25 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
"k8s.io/client-go/util/workqueue"
21-
"os"
22-
23-
elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2"
24-
20+
"context"
2521
"github.com/go-logr/logr"
2622
"github.com/spf13/pflag"
2723
zapraw "go.uber.org/zap"
2824
k8sruntime "k8s.io/apimachinery/pkg/runtime"
2925
"k8s.io/client-go/kubernetes"
3026
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3127
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
28+
"k8s.io/client-go/util/workqueue"
3229
"k8s.io/klog/v2"
30+
"os"
3331
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
3432
elbv2controller "sigs.k8s.io/aws-load-balancer-controller/controllers/elbv2"
3533
"sigs.k8s.io/aws-load-balancer-controller/controllers/ingress"
3634
"sigs.k8s.io/aws-load-balancer-controller/controllers/service"
3735
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
3836
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
3937
"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
38+
elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2"
4039
"sigs.k8s.io/aws-load-balancer-controller/pkg/inject"
4140
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
4241
awsmetrics "sigs.k8s.io/aws-load-balancer-controller/pkg/metrics/aws"
@@ -52,6 +51,7 @@ import (
5251
"sigs.k8s.io/controller-runtime/pkg/healthz"
5352
"sigs.k8s.io/controller-runtime/pkg/log/zap"
5453
"sigs.k8s.io/controller-runtime/pkg/metrics"
54+
"time"
5555
// +kubebuilder:scaffold:imports
5656
)
5757

@@ -84,8 +84,6 @@ func main() {
8484
klog.SetLoggerWithOptions(appLogger, klog.ContextualLogger(true))
8585

8686
var awsMetricsCollector *awsmetrics.Collector
87-
lbcMetricsCollector := lbcmetrics.NewCollector(metrics.Registry)
88-
8987
if metrics.Registry != nil {
9088
awsMetricsCollector = awsmetrics.NewCollector(metrics.Registry)
9189
}
@@ -107,6 +105,9 @@ func main() {
107105
os.Exit(1)
108106
}
109107

108+
// for open-source LBC, we track the resources with finalizers contains "k8s.aws"
109+
lbcMetricsCollector := lbcmetrics.NewCollector(metrics.Registry, mgr.GetClient(), "k8s.aws")
110+
110111
clientSet, err := kubernetes.NewForConfig(mgr.GetConfig())
111112
if err != nil {
112113
setupLog.Error(err, "unable to obtain clientSet")
@@ -163,6 +164,23 @@ func main() {
163164
os.Exit(1)
164165
}
165166

167+
// update the managed resource every 30s
168+
go func() {
169+
ticker := time.NewTicker(30 * time.Second) // Call every 30 seconds
170+
defer ticker.Stop()
171+
172+
for {
173+
select {
174+
case <-ticker.C:
175+
// Update metrics
176+
err := lbcMetricsCollector.UpdateManagedK8sResourceMetrics(context.Background())
177+
if err != nil {
178+
setupLog.Error(err, "failed to update managed Kubernetes resource metrics")
179+
}
180+
}
181+
}
182+
}()
183+
166184
// Add liveness probe
167185
err = mgr.AddHealthzCheck("health-ping", healthz.Ping)
168186
setupLog.Info("adding health check for controller")
@@ -210,6 +228,7 @@ func main() {
210228
setupLog.Error(err, "problem running manager")
211229
os.Exit(1)
212230
}
231+
213232
}
214233

215234
// loadControllerConfig loads the controller configuration.

Diff for: pkg/metrics/aws/instruments.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ func newInstruments(registerer prometheus.Registerer) *instruments {
5959
Name: metricAPIRequestDurationSeconds,
6060
Help: "Latency of an individual HTTP request to the service endpoint",
6161
}, []string{labelService, labelOperation})
62-
63-
registerer.MustRegister(apiCallsTotal, apiCallDurationSeconds, apiCallRetries, apiRequestsTotal, apiRequestDurationSecond)
62+
registerer.MustRegister(
63+
apiCallsTotal,
64+
apiCallDurationSeconds,
65+
apiCallRetries,
66+
apiRequestsTotal,
67+
apiRequestDurationSecond,
68+
)
6469
return &instruments{
6570
apiCallsTotal: apiCallsTotal,
6671
apiCallDurationSeconds: apiCallDurationSeconds,

Diff for: pkg/metrics/lbc/collector.go

+85-4
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
11
package lbc
22

33
import (
4+
"context"
45
"github.com/prometheus/client_golang/prometheus"
6+
corev1 "k8s.io/api/core/v1"
7+
networkingv1 "k8s.io/api/networking/v1"
8+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
9+
"strings"
10+
11+
"sigs.k8s.io/controller-runtime/pkg/client"
512
"time"
613
)
714

15+
const networkLoadBalancerStr = "nlb"
16+
817
type MetricCollector interface {
918
// ObservePodReadinessGateReady this metric is useful to determine how fast pods are becoming ready in the load balancer.
1019
// Due to some architectural constraints, we can only emit this metric for pods that are using readiness gates.
1120
ObservePodReadinessGateReady(namespace string, tgbName string, duration time.Duration)
21+
22+
// UpdateMetrics fetches and updates all relevant metrics.
23+
UpdateManagedK8sResourceMetrics(ctx context.Context) error
1224
}
1325

1426
type collector struct {
15-
instruments *instruments
27+
instruments *instruments
28+
runtimeClient client.Client
29+
finalizerKeyWord string
1630
}
1731

1832
type noOpCollector struct{}
1933

2034
func (n *noOpCollector) ObservePodReadinessGateReady(_ string, _ string, _ time.Duration) {
2135
}
2236

23-
func NewCollector(registerer prometheus.Registerer) MetricCollector {
24-
if registerer == nil {
37+
func (n *noOpCollector) UpdateManagedK8sResourceMetrics(_ context.Context) error {
38+
return nil
39+
}
40+
41+
func NewCollector(registerer prometheus.Registerer, runtimeClient client.Client, finalizerKeyWord string) MetricCollector {
42+
if registerer == nil || runtimeClient == nil {
2543
return &noOpCollector{}
2644
}
2745

2846
instruments := newInstruments(registerer)
2947
return &collector{
30-
instruments: instruments,
48+
instruments: instruments,
49+
runtimeClient: runtimeClient,
50+
finalizerKeyWord: finalizerKeyWord,
3151
}
3252
}
3353

@@ -37,3 +57,64 @@ func (c *collector) ObservePodReadinessGateReady(namespace string, tgbName strin
3757
labelName: tgbName,
3858
}).Observe(duration.Seconds())
3959
}
60+
61+
func (c *collector) UpdateManagedK8sResourceMetrics(ctx context.Context) error {
62+
listOpts := &client.ListOptions{
63+
Namespace: "",
64+
}
65+
ingressCount, serviceCount, tgbCount := 0, 0, 0
66+
// Fetch ingress count
67+
ingressList := &networkingv1.IngressList{}
68+
err := c.runtimeClient.List(ctx, ingressList, listOpts)
69+
if err != nil {
70+
return err
71+
}
72+
for _, ingress := range ingressList.Items {
73+
for _, finalizer := range ingress.Finalizers {
74+
if strings.Contains(finalizer, c.finalizerKeyWord) {
75+
ingressCount++
76+
break
77+
}
78+
}
79+
}
80+
c.instruments.ingressCount.Set(float64(ingressCount))
81+
82+
// Fetch service count
83+
serviceList := &corev1.ServiceList{}
84+
err = c.runtimeClient.List(ctx, serviceList, listOpts)
85+
if err != nil {
86+
return err
87+
}
88+
for _, service := range serviceList.Items {
89+
hasMatchingFinalizer := false
90+
for _, finalizer := range service.Finalizers {
91+
if strings.Contains(finalizer, c.finalizerKeyWord) {
92+
hasMatchingFinalizer = true
93+
break
94+
}
95+
}
96+
97+
if hasMatchingFinalizer && service.Spec.LoadBalancerClass != nil && strings.Contains(*service.Spec.LoadBalancerClass, networkLoadBalancerStr) {
98+
serviceCount++
99+
}
100+
}
101+
c.instruments.serviceCount.Set(float64(serviceCount))
102+
103+
// Fetch TargetGroupBinding count
104+
tgbList := &elbv2api.TargetGroupBindingList{}
105+
err = c.runtimeClient.List(ctx, tgbList, listOpts)
106+
if err != nil {
107+
return err
108+
}
109+
for _, tgb := range tgbList.Items {
110+
for _, finalizer := range tgb.Finalizers {
111+
if strings.Contains(finalizer, c.finalizerKeyWord) {
112+
tgbCount++
113+
break
114+
}
115+
}
116+
}
117+
c.instruments.tgbCount.Set(float64(tgbCount))
118+
119+
return nil
120+
}

Diff for: pkg/metrics/lbc/instruments.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const (
2121

2222
type instruments struct {
2323
podReadinessFlipSeconds *prometheus.HistogramVec
24+
ingressCount prometheus.Gauge
25+
serviceCount prometheus.Gauge
26+
tgbCount prometheus.Gauge
2427
}
2528

2629
// newInstruments allocates and register new metrics to registerer
@@ -31,9 +34,28 @@ func newInstruments(registerer prometheus.Registerer) *instruments {
3134
Help: "Latency from pod getting added to the load balancer until the readiness gate is flipped to healthy.",
3235
Buckets: []float64{10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600},
3336
}, []string{labelNamespace, labelName})
34-
35-
registerer.MustRegister(podReadinessFlipSeconds)
37+
ingressCount := prometheus.NewGauge(prometheus.GaugeOpts{
38+
Name: "lb_controller_managed_ingress_count",
39+
Help: "Number of ingresses managed by the AWS Load Balancer Controller.",
40+
})
41+
serviceCount := prometheus.NewGauge(prometheus.GaugeOpts{
42+
Name: "lb_controller_managed_service_count",
43+
Help: "Number of service type Load Balancers (NLBs) managed by the AWS Load Balancer Controller.",
44+
})
45+
tgbCount := prometheus.NewGauge(prometheus.GaugeOpts{
46+
Name: "lb_controller_managed_targetgroupbinding_count",
47+
Help: "Number of targetgroupbindings managed by the AWS Load Balancer Controller.",
48+
})
49+
registerer.MustRegister(
50+
podReadinessFlipSeconds,
51+
ingressCount,
52+
serviceCount,
53+
tgbCount,
54+
)
3655
return &instruments{
3756
podReadinessFlipSeconds: podReadinessFlipSeconds,
57+
ingressCount: ingressCount,
58+
serviceCount: serviceCount,
59+
tgbCount: tgbCount,
3860
}
3961
}

Diff for: pkg/metrics/lbc/mockcollector.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lbc
22

33
import (
4+
"context"
45
"time"
56
)
67

@@ -14,22 +15,41 @@ type MockHistogramMetric struct {
1415
duration time.Duration
1516
}
1617

18+
// ObservePodReadinessGateReady mocks observing the readiness gate latency.
1719
func (m *MockCollector) ObservePodReadinessGateReady(namespace string, tgbName string, d time.Duration) {
1820
m.recordHistogram(MetricPodReadinessGateReady, namespace, tgbName, d)
1921
}
2022

23+
// UpdateManagedK8sResourceMetrics mocks updating managed Kubernetes resource metrics.
24+
func (m *MockCollector) UpdateManagedK8sResourceMetrics(ctx context.Context) error {
25+
m.recordInvocation("UpdateManagedK8sResourceMetrics", ctx)
26+
return nil // No-op for the mock
27+
}
28+
29+
// recordHistogram adds a histogram metric invocation.
2130
func (m *MockCollector) recordHistogram(metricName string, namespace string, name string, d time.Duration) {
22-
m.Invocations[metricName] = append(m.Invocations[MetricPodReadinessGateReady], MockHistogramMetric{
31+
if _, exists := m.Invocations[metricName]; !exists {
32+
m.Invocations[metricName] = []interface{}{}
33+
}
34+
m.Invocations[metricName] = append(m.Invocations[metricName], MockHistogramMetric{
2335
namespace: namespace,
2436
name: name,
2537
duration: d,
2638
})
2739
}
2840

29-
func NewMockCollector() MetricCollector {
41+
// recordInvocation tracks a method invocation with arguments.
42+
func (m *MockCollector) recordInvocation(methodName string, args ...interface{}) {
43+
if _, exists := m.Invocations[methodName]; !exists {
44+
m.Invocations[methodName] = []interface{}{}
45+
}
46+
m.Invocations[methodName] = append(m.Invocations[methodName], args)
47+
}
3048

49+
// NewMockCollector creates and returns a new MockCollector.
50+
func NewMockCollector() MetricCollector {
3151
mockInvocations := make(map[string][]interface{})
32-
mockInvocations[MetricPodReadinessGateReady] = make([]interface{}, 0)
52+
mockInvocations[MetricPodReadinessGateReady] = []interface{}{}
3353

3454
return &MockCollector{
3555
Invocations: mockInvocations,

0 commit comments

Comments
 (0)