Skip to content

Commit 9027651

Browse files
committed
Add Metrics for EndpointSlices
Implements https://pkg.go.dev/k8s.io/api/discovery/v1#EndpointSlice This resourcetype is disabled by default as they are very verbose and have a high cardinality. Metrics from endpointslices can be used to identify if specific pods are part of an endpoint and thus discoverable through a service. Signed-off-by: Manuel Rüger <[email protected]>
1 parent 0cbabf9 commit 9027651

File tree

10 files changed

+446
-0
lines changed

10 files changed

+446
-0
lines changed

docs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ See each file for specific documentation about the exposed metrics:
6565

6666
- [ClusterRole Metrics](clusterrole-metrics.md)
6767
- [ClusterRoleBinding Metrics](clusterrolebinding-metrics.md)
68+
- [EndpointSlice Metrics](endpointslice-metrics.md)
6869
- [IngressClass Metrics](ingressclass-metrics.md)
6970
- [Role Metrics](role-metrics.md)
7071
- [RoleBinding Metrics](rolebinding-metrics.md)

docs/endpointslice-metrics.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Endpoint Metrics
2+
3+
| Metric name| Metric type | Labels/tags | Status |
4+
| ---------- | ----------- | ----------- | ----------- |
5+
| kube_endpointslice_annotations | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; <br> `annotation_ENDPOINTSLICE_ANNOTATION`=&lt;ENDPOINTSLICE_ANNOTATION&gt; | EXPERIMENTAL |
6+
| kube_endpointslice_info | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; | EXPERIMENTAL |
7+
| kube_endpointslice_ports | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; <br> `port_name`=&lt;endpointslice-port-name&gt; <br> `port_protocol`=&lt;endpointslice-port-protocol&gt; <br> `port_number`=&lt;endpointslice-port-number&gt; | EXPERIMENTAL |
8+
| kube_endpointslice_endpoints | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; <br> `ready`=&lt;endpointslice-ready&gt; <br> `serving`=&lt;endpointslice-serving&gt; <br> `terminating`=&lt;endpointslice-terminating&gt; <br> `hostname`=&lt;endpointslice-hostname&gt; <br> `targetref_kind`=&lt;endpointslice-targetref-kind&gt; <br> `targetref_name`=&lt;endpointslice-targetref-name&gt; <br> `targetref_namespace`=&lt;endpointslice-targetref-namespace&gt; <br> `nodename`=&lt;endpointslice-nodename&gt; <br> `endpoint_zone`=&lt;endpointslice-zone&gt; | EXPERIMENTAL |
9+
| kube_endpointslice_labels | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; <br> `label_ENDPOINTSLICE_LABEL`=&lt;ENDPOINTSLICE_LABEL&gt; | EXPERIMENTAL |
10+
| kube_endpointslice_created | Gauge | `endpointslice`=&lt;endpointslice-name&gt; <br> `namespace`=&lt;endpointslice-namespace&gt; | EXPERIMENTAL |

examples/autosharding/cluster-role.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ rules:
7777
verbs:
7878
- list
7979
- watch
80+
- apiGroups:
81+
- discovery.k8s.io
82+
resources:
83+
- endpointslices
84+
verbs:
85+
- list
86+
- watch
8087
- apiGroups:
8188
- storage.k8s.io
8289
resources:

examples/standard/cluster-role.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ rules:
7777
verbs:
7878
- list
7979
- watch
80+
- apiGroups:
81+
- discovery.k8s.io
82+
resources:
83+
- endpointslices
84+
verbs:
85+
- list
86+
- watch
8087
- apiGroups:
8188
- storage.k8s.io
8289
resources:

internal/store/builder.go

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
certv1 "k8s.io/api/certificates/v1"
3333
coordinationv1 "k8s.io/api/coordination/v1"
3434
v1 "k8s.io/api/core/v1"
35+
discoveryv1 "k8s.io/api/discovery/v1"
3536
networkingv1 "k8s.io/api/networking/v1"
3637
policyv1 "k8s.io/api/policy/v1"
3738
rbacv1 "k8s.io/api/rbac/v1"
@@ -293,6 +294,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{
293294
"daemonsets": func(b *Builder) []cache.Store { return b.buildDaemonSetStores() },
294295
"deployments": func(b *Builder) []cache.Store { return b.buildDeploymentStores() },
295296
"endpoints": func(b *Builder) []cache.Store { return b.buildEndpointsStores() },
297+
"endpointslices": func(b *Builder) []cache.Store { return b.buildEndpointSlicesStores() },
296298
"horizontalpodautoscalers": func(b *Builder) []cache.Store { return b.buildHPAStores() },
297299
"ingresses": func(b *Builder) []cache.Store { return b.buildIngressStores() },
298300
"ingressclasses": func(b *Builder) []cache.Store { return b.buildIngressClassStores() },
@@ -355,6 +357,10 @@ func (b *Builder) buildEndpointsStores() []cache.Store {
355357
return b.buildStoresFunc(endpointMetricFamilies(b.allowAnnotationsList["endpoints"], b.allowLabelsList["endpoints"]), &v1.Endpoints{}, createEndpointsListWatch, b.useAPIServerCache)
356358
}
357359

360+
func (b *Builder) buildEndpointSlicesStores() []cache.Store {
361+
return b.buildStoresFunc(endpointSliceMetricFamilies(b.allowAnnotationsList["endpointslices"], b.allowLabelsList["endpointslices"]), &discoveryv1.EndpointSlice{}, createEndpointSliceListWatch, b.useAPIServerCache)
362+
}
363+
358364
func (b *Builder) buildHPAStores() []cache.Store {
359365
return b.buildStoresFunc(hpaMetricFamilies(b.allowAnnotationsList["horizontalpodautoscalers"], b.allowLabelsList["horizontalpodautoscalers"]), &autoscaling.HorizontalPodAutoscaler{}, createHPAListWatch, b.useAPIServerCache)
360366
}

internal/store/endpointslice.go

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors All rights reserved.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package store
15+
16+
import (
17+
"context"
18+
"strconv"
19+
20+
basemetrics "k8s.io/component-base/metrics"
21+
22+
"k8s.io/kube-state-metrics/v2/pkg/metric"
23+
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
24+
25+
discoveryv1 "k8s.io/api/discovery/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/apimachinery/pkg/watch"
29+
clientset "k8s.io/client-go/kubernetes"
30+
"k8s.io/client-go/tools/cache"
31+
)
32+
33+
var (
34+
descEndpointSliceAnnotationsName = "kube_endpointslice_annotations"
35+
descEndpointSliceAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels."
36+
descEndpointSliceLabelsName = "kube_endpointslice_labels"
37+
descEndpointSliceLabelsHelp = "Kubernetes labels converted to Prometheus labels."
38+
descEndpointSliceLabelsDefaultLabels = []string{"endpointslice"}
39+
)
40+
41+
func endpointSliceMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator {
42+
return []generator.FamilyGenerator{
43+
*generator.NewFamilyGeneratorWithStability(
44+
"kube_endpointslice_info",
45+
"Information about endpointslice.",
46+
metric.Gauge,
47+
basemetrics.ALPHA,
48+
"",
49+
wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family {
50+
51+
m := metric.Metric{
52+
LabelKeys: []string{"addresstype"},
53+
LabelValues: []string{string(s.AddressType)},
54+
Value: 1,
55+
}
56+
return &metric.Family{Metrics: []*metric.Metric{&m}}
57+
}),
58+
),
59+
*generator.NewFamilyGeneratorWithStability(
60+
"kube_endpointslice_created",
61+
"Unix creation timestamp",
62+
metric.Gauge,
63+
basemetrics.ALPHA,
64+
"",
65+
wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family {
66+
ms := []*metric.Metric{}
67+
if !s.CreationTimestamp.IsZero() {
68+
ms = append(ms, &metric.Metric{
69+
Value: float64(s.CreationTimestamp.Unix()),
70+
})
71+
}
72+
return &metric.Family{
73+
Metrics: ms,
74+
}
75+
}),
76+
),
77+
*generator.NewFamilyGeneratorWithStability(
78+
"kube_endpointslice_endpoints",
79+
"Endpoints attached to the endpointslice.",
80+
metric.Gauge,
81+
basemetrics.ALPHA,
82+
"",
83+
wrapEndpointSliceFunc(func(e *discoveryv1.EndpointSlice) *metric.Family {
84+
m := []*metric.Metric{}
85+
for _, ep := range e.Endpoints {
86+
var (
87+
labelKeys,
88+
labelValues []string
89+
)
90+
91+
if ep.Conditions.Ready != nil {
92+
labelKeys = append(labelKeys, "ready")
93+
labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Ready))
94+
}
95+
if ep.Conditions.Serving != nil {
96+
labelKeys = append(labelKeys, "serving")
97+
labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Serving))
98+
}
99+
if ep.Conditions.Terminating != nil {
100+
labelKeys = append(labelKeys, "terminating")
101+
labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Terminating))
102+
}
103+
104+
if ep.Hostname != nil {
105+
labelKeys = append(labelKeys, "hostname")
106+
labelValues = append(labelValues, *ep.Hostname)
107+
}
108+
109+
if ep.TargetRef != nil {
110+
if ep.TargetRef.Kind != "" {
111+
labelKeys = append(labelKeys, "targetref_kind")
112+
labelValues = append(labelValues, ep.TargetRef.Kind)
113+
}
114+
if ep.TargetRef.Name != "" {
115+
labelKeys = append(labelKeys, "targetref_name")
116+
labelValues = append(labelValues, ep.TargetRef.Name)
117+
}
118+
if ep.TargetRef.Namespace != "" {
119+
labelKeys = append(labelKeys, "targetref_namespace")
120+
labelValues = append(labelValues, ep.TargetRef.Namespace)
121+
}
122+
}
123+
124+
if ep.NodeName != nil {
125+
labelKeys = append(labelKeys, "endpoint_nodename")
126+
labelValues = append(labelValues, *ep.NodeName)
127+
}
128+
129+
if ep.Zone != nil {
130+
labelKeys = append(labelKeys, "endpoint_zone")
131+
labelValues = append(labelValues, *ep.Zone)
132+
}
133+
labelKeys = append(labelKeys, "address")
134+
for _, address := range ep.Addresses {
135+
newlabelValues := make([]string, len(labelValues))
136+
copy(newlabelValues, labelValues)
137+
m = append(m, &metric.Metric{
138+
LabelKeys: labelKeys,
139+
LabelValues: append(newlabelValues, address),
140+
Value: 1,
141+
})
142+
}
143+
}
144+
return &metric.Family{
145+
Metrics: m,
146+
}
147+
}),
148+
),
149+
150+
*generator.NewFamilyGeneratorWithStability(
151+
"kube_endpointslice_ports",
152+
"Ports attached to the endpointslice.",
153+
metric.Gauge,
154+
basemetrics.ALPHA,
155+
"",
156+
wrapEndpointSliceFunc(func(e *discoveryv1.EndpointSlice) *metric.Family {
157+
m := []*metric.Metric{}
158+
for _, port := range e.Ports {
159+
m = append(m, &metric.Metric{
160+
LabelValues: []string{*port.Name, string(*port.Protocol), strconv.FormatInt(int64(*port.Port), 10)},
161+
LabelKeys: []string{"port_name", "port_protocol", "port_number"},
162+
Value: 1,
163+
})
164+
}
165+
return &metric.Family{
166+
Metrics: m,
167+
}
168+
}),
169+
),
170+
*generator.NewFamilyGeneratorWithStability(
171+
descEndpointSliceAnnotationsName,
172+
descEndpointSliceAnnotationsHelp,
173+
metric.Gauge,
174+
basemetrics.ALPHA,
175+
"",
176+
wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family {
177+
annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", s.Annotations, allowAnnotationsList)
178+
return &metric.Family{
179+
Metrics: []*metric.Metric{
180+
{
181+
LabelKeys: annotationKeys,
182+
LabelValues: annotationValues,
183+
Value: 1,
184+
},
185+
},
186+
}
187+
}),
188+
),
189+
*generator.NewFamilyGeneratorWithStability(
190+
descEndpointSliceLabelsName,
191+
descEndpointSliceLabelsHelp,
192+
metric.Gauge,
193+
basemetrics.ALPHA,
194+
"",
195+
wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family {
196+
labelKeys, labelValues := createPrometheusLabelKeysValues("label", s.Labels, allowLabelsList)
197+
return &metric.Family{
198+
Metrics: []*metric.Metric{
199+
{
200+
LabelKeys: labelKeys,
201+
LabelValues: labelValues,
202+
Value: 1,
203+
},
204+
},
205+
}
206+
}),
207+
),
208+
}
209+
}
210+
211+
func wrapEndpointSliceFunc(f func(*discoveryv1.EndpointSlice) *metric.Family) func(interface{}) *metric.Family {
212+
return func(obj interface{}) *metric.Family {
213+
endpointSlice := obj.(*discoveryv1.EndpointSlice)
214+
215+
metricFamily := f(endpointSlice)
216+
217+
for _, m := range metricFamily.Metrics {
218+
m.LabelKeys, m.LabelValues = mergeKeyValues(descEndpointSliceLabelsDefaultLabels, []string{endpointSlice.Name}, m.LabelKeys, m.LabelValues)
219+
}
220+
221+
return metricFamily
222+
}
223+
}
224+
225+
func createEndpointSliceListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher {
226+
return &cache.ListWatch{
227+
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
228+
return kubeClient.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), opts)
229+
},
230+
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
231+
return kubeClient.DiscoveryV1().EndpointSlices(ns).Watch(context.TODO(), opts)
232+
},
233+
}
234+
}

0 commit comments

Comments
 (0)