diff --git a/docs/endpointslice-metrics.md b/docs/endpointslice-metrics.md
index 59af50bda4..9b914e3544 100644
--- a/docs/endpointslice-metrics.md
+++ b/docs/endpointslice-metrics.md
@@ -6,5 +6,6 @@
| kube_endpointslice_info | Gauge | | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace> | EXPERIMENTAL |
| kube_endpointslice_ports | Gauge | | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`port_name`=<endpointslice-port-name>
`port_protocol`=<endpointslice-port-protocol>
`port_number`=<endpointslice-port-number> | EXPERIMENTAL |
| kube_endpointslice_endpoints | Gauge | | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`ready`=<endpointslice-ready>
`serving`=<endpointslice-serving>
`terminating`=<endpointslice-terminating>
`hostname`=<endpointslice-hostname>
`targetref_kind`=<endpointslice-targetref-kind>
`targetref_name`=<endpointslice-targetref-name>
`targetref_namespace`=<endpointslice-targetref-namespace>
`nodename`=<endpointslice-nodename>
`endpoint_zone`=<endpointslice-zone> | EXPERIMENTAL |
+| kube_endpointslice_endpoints_hints | Gauge | Each line is a hint applied to an endpoint-slice | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`address`=<endpointslice-address[0]>
`for_zone`=<endpointslice-hint> | EXPERIMENTAL |
| kube_endpointslice_labels | Gauge | Kubernetes labels converted to Prometheus labels controlled via [--metric-labels-allowlist](./cli-arguments.md) | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`label_ENDPOINTSLICE_LABEL`=<ENDPOINTSLICE_LABEL> | EXPERIMENTAL |
| kube_endpointslice_created | Gauge | | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace> | EXPERIMENTAL |
diff --git a/internal/store/endpointslice.go b/internal/store/endpointslice.go
index ad72ce794a..bf80cebbd4 100644
--- a/internal/store/endpointslice.go
+++ b/internal/store/endpointslice.go
@@ -74,6 +74,44 @@ func endpointSliceMetricFamilies(allowAnnotationsList, allowLabelsList []string)
}
}),
),
+ *generator.NewFamilyGeneratorWithStability(
+ "kube_endpointslice_endpoints_hints",
+ "Topology routing hints attached to endpoints",
+ metric.Gauge,
+ basemetrics.ALPHA,
+ "",
+ wrapEndpointSliceFunc(func(e *discoveryv1.EndpointSlice) *metric.Family {
+ m := []*metric.Metric{}
+ for _, ep := range e.Endpoints {
+ // Hint is populated when the endpoint is configured to be zone aware and preferentially route requests to its local zone.
+ // If there is no hint, skip this metric
+ if ep.Hints != nil && len(ep.Hints.ForZones) > 0 {
+ var (
+ labelKeys,
+ labelValues []string
+ )
+
+ // Per Docs.
+ // This must contain at least one address but no more than
+ // 100. These are all assumed to be fungible and clients may choose to only
+ // use the first element. Refer to: https://issue.k8s.io/106267
+ labelKeys = append(labelKeys, "address")
+ labelValues = append(labelValues, ep.Addresses[0])
+
+ for _, zone := range ep.Hints.ForZones {
+ m = append(m, &metric.Metric{
+ LabelKeys: append(labelKeys, "for_zone"),
+ LabelValues: append(labelValues, zone.Name),
+ Value: 1,
+ })
+ }
+ }
+ }
+ return &metric.Family{
+ Metrics: m,
+ }
+ }),
+ ),
*generator.NewFamilyGeneratorWithStability(
"kube_endpointslice_endpoints",
"Endpoints attached to the endpointslice.",
@@ -134,9 +172,11 @@ func endpointSliceMetricFamilies(allowAnnotationsList, allowLabelsList []string)
for _, address := range ep.Addresses {
newlabelValues := make([]string, len(labelValues))
copy(newlabelValues, labelValues)
+ newlabelValues = append(newlabelValues, address)
+
m = append(m, &metric.Metric{
LabelKeys: labelKeys,
- LabelValues: append(newlabelValues, address),
+ LabelValues: newlabelValues,
Value: 1,
})
}
diff --git a/internal/store/endpointslice_test.go b/internal/store/endpointslice_test.go
index 549fa73b6f..16a6ff61d8 100644
--- a/internal/store/endpointslice_test.go
+++ b/internal/store/endpointslice_test.go
@@ -113,7 +113,9 @@ func TestEndpointSliceStore(t *testing.T) {
},
Want: `
# HELP kube_endpointslice_endpoints Endpoints attached to the endpointslice.
+ # HELP kube_endpointslice_endpoints_hints Topology routing hints attached to endpoints
# TYPE kube_endpointslice_endpoints gauge
+ # TYPE kube_endpointslice_endpoints_hints gauge
kube_endpointslice_endpoints{address="10.0.0.1",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1
kube_endpointslice_endpoints{address="192.168.1.10",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1
`,
@@ -122,6 +124,44 @@ func TestEndpointSliceStore(t *testing.T) {
"kube_endpointslice_endpoints",
},
},
+ {
+ Obj: &discoveryv1.EndpointSlice{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test_endpointslice-endpoints",
+ },
+ AddressType: "IPv4",
+ Endpoints: []discoveryv1.Endpoint{
+ {
+ NodeName: &nodename,
+ Conditions: discoveryv1.EndpointConditions{
+ Ready: &ready,
+ Terminating: &terminating,
+ },
+ Hostname: &hostname,
+ Zone: &zone,
+ Addresses: addresses,
+ Hints: &discoveryv1.EndpointHints{
+ ForZones: []discoveryv1.ForZone{
+ {Name: "zone1"},
+ },
+ },
+ },
+ },
+ },
+ Want: `
+ # HELP kube_endpointslice_endpoints Endpoints attached to the endpointslice.
+ # HELP kube_endpointslice_endpoints_hints Topology routing hints attached to endpoints
+ # TYPE kube_endpointslice_endpoints gauge
+ # TYPE kube_endpointslice_endpoints_hints gauge
+ kube_endpointslice_endpoints_hints{address="10.0.0.1",endpointslice="test_endpointslice-endpoints",for_zone="zone1"} 1
+ kube_endpointslice_endpoints{address="10.0.0.1",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1
+ kube_endpointslice_endpoints{address="192.168.1.10",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1
+ `,
+
+ MetricNames: []string{
+ "kube_endpointslice_endpoints",
+ },
+ },
{
AllowAnnotationsList: []string{
"foo",