diff --git a/docs/metrics/service/service-metrics.md b/docs/metrics/service/service-metrics.md
index 981b0c83ef..f73f4a1e46 100644
--- a/docs/metrics/service/service-metrics.md
+++ b/docs/metrics/service/service-metrics.md
@@ -9,3 +9,4 @@
| kube_service_spec_type | Gauge | Type about service | | `service`=<service-name>
`namespace`=<service-namespace>
`uid`=<service-uid>
`type`=<ClusterIP\|NodePort\|LoadBalancer\|ExternalName> | STABLE |
| kube_service_spec_external_ip | Gauge | Service external ips. One series for each ip | | `service`=<service-name>
`namespace`=<service-namespace>
`uid`=<service-uid>
`external_ip`=<external-ip> | STABLE |
| kube_service_status_load_balancer_ingress | Gauge | Service load balancer ingress status | | `service`=<service-name>
`namespace`=<service-namespace>
`uid`=<service-uid>
`ip`=<load-balancer-ingress-ip>
`hostname`=<load-balancer-ingress-hostname> | STABLE |
+| kube_service_ports | Gauge | Metric providing details about the ports exposed by services. | | `service`=<service-name>
`namespace`=<service-namespace>
`uid`=<service-uid>
`port_name`=<service-port-name>
`port_protocol`=<service-port-protocol>
`port_number`=<service-port-number>
`port_target`=<service-port-target>
`port_app_protocol`=<service-port-appProtocol>
`port_node_number`=<service-node-port-number> | EXPERIMENTAL |
diff --git a/internal/store/service.go b/internal/store/service.go
index e4a8282e58..f6fdf07634 100644
--- a/internal/store/service.go
+++ b/internal/store/service.go
@@ -18,6 +18,7 @@ package store
import (
"context"
+ "strconv"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -184,6 +185,49 @@ func serviceMetricFamilies(allowAnnotationsList, allowLabelsList []string) []gen
}
}),
),
+ *generator.NewFamilyGeneratorWithStability(
+ "kube_service_ports",
+ "Metric providing details about the ports exposed by services.",
+ metric.Gauge,
+ basemetrics.ALPHA,
+ "",
+ wrapSvcFunc(func(e *v1.Service) *metric.Family {
+ ms := []*metric.Metric{}
+ labelKeys := []string{"port_protocol", "port_number", "port_name", "port_target", "port_node_number", "port_app_protocol"}
+ for _, port := range e.Spec.Ports {
+ appProtocol := ""
+ if port.AppProtocol != nil {
+ appProtocol = *port.AppProtocol
+ }
+ var labelValues []string
+ portNumber := strconv.FormatInt(int64(port.Port), 10)
+ targetPort := port.TargetPort.String()
+ nodePort := ""
+
+ if port.NodePort != 0 {
+ nodePort = strconv.FormatInt(int64(port.NodePort), 10)
+ }
+
+ labelValues = []string{
+ string(port.Protocol),
+ portNumber,
+ port.Name,
+ targetPort,
+ nodePort,
+ appProtocol,
+ }
+
+ ms = append(ms, &metric.Metric{
+ LabelValues: labelValues,
+ LabelKeys: labelKeys,
+ Value: 1,
+ })
+ }
+ return &metric.Family{
+ Metrics: ms,
+ }
+ }),
+ ),
}
}
diff --git a/internal/store/service_test.go b/internal/store/service_test.go
index 20be8ea83c..ee591e1edf 100644
--- a/internal/store/service_test.go
+++ b/internal/store/service_test.go
@@ -22,6 +22,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
)
@@ -44,6 +45,8 @@ func TestServiceStore(t *testing.T) {
# TYPE kube_service_spec_external_ip gauge
# HELP kube_service_status_load_balancer_ingress [STABLE] Service load balancer ingress status
# TYPE kube_service_status_load_balancer_ingress gauge
+ # HELP kube_service_ports Metric providing details about the ports exposed by services.
+ # TYPE kube_service_ports gauge
`
cases := []generateMetricsTestCase{
{
@@ -68,11 +71,13 @@ func TestServiceStore(t *testing.T) {
# HELP kube_service_info [STABLE] Information about service.
# HELP kube_service_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_service_spec_type [STABLE] Type about service.
+ # HELP kube_service_ports Metric providing details about the ports exposed by services.
# TYPE kube_service_annotations gauge
# TYPE kube_service_created gauge
# TYPE kube_service_info gauge
# TYPE kube_service_labels gauge
# TYPE kube_service_spec_type gauge
+ # TYPE kube_service_ports gauge
kube_service_created{namespace="default",service="test-service1",uid="uid1"} 1.5e+09
kube_service_info{cluster_ip="1.2.3.4",external_name="",external_traffic_policy="",load_balancer_ip="",namespace="default",service="test-service1",uid="uid1"} 1
kube_service_spec_type{namespace="default",service="test-service1",type="ClusterIP",uid="uid1"} 1
@@ -83,6 +88,7 @@ func TestServiceStore(t *testing.T) {
"kube_service_info",
"kube_service_labels",
"kube_service_spec_type",
+ "kube_service_ports",
},
},
{
@@ -259,6 +265,35 @@ func TestServiceStore(t *testing.T) {
kube_service_spec_type{namespace="default",service="test-service8",uid="uid8",type="LoadBalancer"} 1
`,
},
+
+ {
+ Obj: &v1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-service9",
+ CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)},
+ Namespace: "default",
+ UID: "uid9",
+ Labels: map[string]string{
+ "app": "example9",
+ },
+ },
+ Spec: v1.ServiceSpec{
+ ClusterIP: "1.2.3.14",
+ LoadBalancerIP: "1.2.3.15",
+ Type: v1.ServiceTypeLoadBalancer,
+ ExternalTrafficPolicy: "Local",
+ Ports: []v1.ServicePort{
+ {Port: 80, Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt(8080), Name: "http", NodePort: 65000, AppProtocol: func(s string) *string { return &s }("grpc")},
+ },
+ },
+ },
+ Want: metadata + `
+ kube_service_created{namespace="default",service="test-service9",uid="uid9"} 1.5e+09
+ kube_service_info{cluster_ip="1.2.3.14",external_name="",external_traffic_policy="Local",load_balancer_ip="1.2.3.15",namespace="default",service="test-service9",uid="uid9"} 1
+ kube_service_spec_type{namespace="default",service="test-service9",uid="uid9",type="LoadBalancer"} 1
+ kube_service_ports{namespace="default",port_app_protocol="grpc",port_name="http",port_node_number="65000",port_number="80",port_protocol="TCP",port_target="8080",service="test-service9",uid="uid9"} 1
+ `,
+ },
}
for i, c := range cases {
c.Func = generator.ComposeMetricGenFuncs(serviceMetricFamilies(nil, nil))