From 58fbd1ad25ea43fc7bf34beb1a085d2c683e6612 Mon Sep 17 00:00:00 2001 From: Samarth Bagga Date: Wed, 3 Sep 2025 16:17:27 +0530 Subject: [PATCH 1/2] populate __meta_kubernetes_service_loadbalancer_ip from status.loadBalancer.ingress with fallback to spec.loadBalancerIP --- discovery/kubernetes/service.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/discovery/kubernetes/service.go b/discovery/kubernetes/service.go index a2e00b1032..39f658c4b9 100644 --- a/discovery/kubernetes/service.go +++ b/discovery/kubernetes/service.go @@ -20,6 +20,7 @@ import ( "log/slog" "net" "strconv" + "strings" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" @@ -233,7 +234,21 @@ func (s *Service) buildService(svc *apiv1.Service) *targetgroup.Group { } if svc.Spec.Type == apiv1.ServiceTypeLoadBalancer { - labelSet[serviceLoadBalancerIP] = lv(svc.Spec.LoadBalancerIP) + if len(svc.Status.LoadBalancer.Ingress) > 0 { + ips := make([]string, 0, len(svc.Status.LoadBalancer.Ingress)) + for _, ing := range svc.Status.LoadBalancer.Ingress { + if ing.IP != "" { + ips = append(ips, ing.IP) + } + } + if len(ips) > 0 { + labelSet[serviceLoadBalancerIP] = lv(strings.Join(ips, ",")) + } else { + labelSet[serviceLoadBalancerIP] = lv(svc.Spec.LoadBalancerIP) + } + } else { + labelSet[serviceLoadBalancerIP] = lv(svc.Spec.LoadBalancerIP) + } } tg.Targets = append(tg.Targets, labelSet) From f16bfbba9b5a8ce9a5129b1ce7ff4df0235fe115 Mon Sep 17 00:00:00 2001 From: Samarth Bagga Date: Thu, 2 Oct 2025 17:40:18 +0530 Subject: [PATCH 2/2] Added tests --- discovery/kubernetes/service.go | 19 +++- discovery/kubernetes/service_test.go | 127 +++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/discovery/kubernetes/service.go b/discovery/kubernetes/service.go index 39f658c4b9..910fb31da0 100644 --- a/discovery/kubernetes/service.go +++ b/discovery/kubernetes/service.go @@ -196,6 +196,8 @@ const ( serviceLoadBalancerIP = metaLabelPrefix + "service_loadbalancer_ip" serviceExternalNameLabel = metaLabelPrefix + "service_external_name" serviceType = metaLabelPrefix + "service_type" + serviceLoadBalancerIPs = metaLabelPrefix + "service_loadbalancer_ips" + serviceLoadBalancerHostnames = metaLabelPrefix + "service_loadbalancer_hostnames" ) func serviceLabels(svc *apiv1.Service) model.LabelSet { @@ -236,17 +238,26 @@ func (s *Service) buildService(svc *apiv1.Service) *targetgroup.Group { if svc.Spec.Type == apiv1.ServiceTypeLoadBalancer { if len(svc.Status.LoadBalancer.Ingress) > 0 { ips := make([]string, 0, len(svc.Status.LoadBalancer.Ingress)) + hostnames := make([]string, 0, len(svc.Status.LoadBalancer.Ingress)) for _, ing := range svc.Status.LoadBalancer.Ingress { if ing.IP != "" { ips = append(ips, ing.IP) } + if ing.Hostname != "" { + hostnames = append(hostnames, ing.Hostname) + } } if len(ips) > 0 { - labelSet[serviceLoadBalancerIP] = lv(strings.Join(ips, ",")) - } else { - labelSet[serviceLoadBalancerIP] = lv(svc.Spec.LoadBalancerIP) + labelSet[serviceLoadBalancerIPs] = lv(strings.Join(ips, ",")) + if len(ips) == 1 { + labelSet[serviceLoadBalancerIP] = lv(ips[0]) + } } - } else { + if len(hostnames) > 0 { + labelSet[serviceLoadBalancerHostnames] = lv(strings.Join(hostnames, ",")) + } + } else if svc.Spec.LoadBalancerIP != "" { + labelSet[serviceLoadBalancerIPs] = lv(svc.Spec.LoadBalancerIP) labelSet[serviceLoadBalancerIP] = lv(svc.Spec.LoadBalancerIP) } } diff --git a/discovery/kubernetes/service_test.go b/discovery/kubernetes/service_test.go index 43c2b7922d..e670c48761 100644 --- a/discovery/kubernetes/service_test.go +++ b/discovery/kubernetes/service_test.go @@ -113,6 +113,56 @@ func makeLoadBalancerService() *v1.Service { } } +func makeLoadBalancerServiceWithStatusIPs(name string, ips []string) *v1.Service { + ing := make([]v1.LoadBalancerIngress, 0, len(ips)) + for _, ip := range ips { + ing = append(ing, v1.LoadBalancerIngress{IP: ip}) + } + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Name: "testport", + Protocol: v1.ProtocolTCP, + Port: int32(32001), + }}, + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "10.0.0.1", + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{Ingress: ing}, + }, + } +} + +func makeLoadBalancerServiceWithStatusHostnames(name string, hosts []string) *v1.Service { + ing := make([]v1.LoadBalancerIngress, 0, len(hosts)) + for _, h := range hosts { + ing = append(ing, v1.LoadBalancerIngress{Hostname: h}) + } + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Name: "testport", + Protocol: v1.ProtocolTCP, + Port: int32(32002), + }}, + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "10.0.0.1", + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{Ingress: ing}, + }, + } +} + func TestServiceDiscoveryAdd(t *testing.T) { t.Parallel() n, c := makeDiscovery(RoleService, NamespaceDiscovery{}) @@ -173,6 +223,7 @@ func TestServiceDiscoveryAdd(t *testing.T) { "__meta_kubernetes_service_port_number": "31900", "__meta_kubernetes_service_cluster_ip": "10.0.0.1", "__meta_kubernetes_service_loadbalancer_ip": "127.0.0.1", + "__meta_kubernetes_service_loadbalancer_ips": "127.0.0.1", }, }, Labels: model.LabelSet{ @@ -185,6 +236,82 @@ func TestServiceDiscoveryAdd(t *testing.T) { }.Run(t) } +func TestServiceDiscoveryLoadBalancerRealWorld(t *testing.T) { + t.Parallel() + n, c := makeDiscovery(RoleService, NamespaceDiscovery{}) + + metallbV4 := makeLoadBalancerServiceWithStatusIPs("svc-metallb-v4", []string{"1.2.3.4"}) + metallbDual := makeLoadBalancerServiceWithStatusIPs("svc-metallb-dual", []string{"2001:1:2:3:4::", "1.2.3.4"}) + eks := makeLoadBalancerServiceWithStatusHostnames("svc-eks", []string{"example-lb-name-abc.elb.eu-central-1.test.com"}) + + k8sDiscoveryTest{ + discovery: n, + afterStart: func() { + c.CoreV1().Services(metallbV4.Namespace).Create(context.Background(), metallbV4, metav1.CreateOptions{}) + c.CoreV1().Services(metallbDual.Namespace).Create(context.Background(), metallbDual, metav1.CreateOptions{}) + c.CoreV1().Services(eks.Namespace).Create(context.Background(), eks, metav1.CreateOptions{}) + }, + expectedMaxItems: 3, + expectedRes: map[string]*targetgroup.Group{ + "svc/default/svc-metallb-v4": { + Targets: []model.LabelSet{ + { + "__meta_kubernetes_service_port_protocol": "TCP", + "__address__": "svc-metallb-v4.default.svc:32001", + "__meta_kubernetes_service_type": "LoadBalancer", + "__meta_kubernetes_service_port_name": "testport", + "__meta_kubernetes_service_port_number": "32001", + "__meta_kubernetes_service_cluster_ip": "10.0.0.1", + "__meta_kubernetes_service_loadbalancer_ips": "1.2.3.4", + "__meta_kubernetes_service_loadbalancer_ip": "1.2.3.4", + }, + }, + Labels: model.LabelSet{ + "__meta_kubernetes_service_name": "svc-metallb-v4", + "__meta_kubernetes_namespace": "default", + }, + Source: "svc/default/svc-metallb-v4", + }, + "svc/default/svc-metallb-dual": { + Targets: []model.LabelSet{ + { + "__meta_kubernetes_service_port_protocol": "TCP", + "__address__": "svc-metallb-dual.default.svc:32001", + "__meta_kubernetes_service_type": "LoadBalancer", + "__meta_kubernetes_service_port_name": "testport", + "__meta_kubernetes_service_port_number": "32001", + "__meta_kubernetes_service_cluster_ip": "10.0.0.1", + "__meta_kubernetes_service_loadbalancer_ips": "2001:1:2:3:4::,1.2.3.4", + }, + }, + Labels: model.LabelSet{ + "__meta_kubernetes_service_name": "svc-metallb-dual", + "__meta_kubernetes_namespace": "default", + }, + Source: "svc/default/svc-metallb-dual", + }, + "svc/default/svc-eks": { + Targets: []model.LabelSet{ + { + "__meta_kubernetes_service_port_protocol": "TCP", + "__address__": "svc-eks.default.svc:32002", + "__meta_kubernetes_service_type": "LoadBalancer", + "__meta_kubernetes_service_port_name": "testport", + "__meta_kubernetes_service_port_number": "32002", + "__meta_kubernetes_service_cluster_ip": "10.0.0.1", + "__meta_kubernetes_service_loadbalancer_hostnames": "example-lb-name-abc.elb.eu-central-1.test.com", + }, + }, + Labels: model.LabelSet{ + "__meta_kubernetes_service_name": "svc-eks", + "__meta_kubernetes_namespace": "default", + }, + Source: "svc/default/svc-eks", + }, + }, + }.Run(t) +} + func TestServiceDiscoveryDelete(t *testing.T) { t.Parallel() n, c := makeDiscovery(RoleService, NamespaceDiscovery{}, makeService("default"))