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))