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",