Repository: kedacore/keda (2026-04-10T20:46:40Z)

Issue:
Metrics API scaler with aggregateFromKubeServiceEndpoints spuriously probes all pods in namespace
### Report

After configuring a Metrics API scaler with `aggregateFromKubeServiceEndpoints: true` and the URL of a headless service, pods unrelated to the service started to receive requests from the scaler. 

It manifested as 404 errors in the logs of the unrelated pods, with the source IP being that of the KEDA operator pod. Only pods in the same namespace as the service are affected (ie. not cluster wide).


### Expected Behavior

KEDA should only query pods (endpoints) belonging to the service specified in the url metadata field.


### Actual Behavior

All pods from the namespace are receiving requests from the KEDA operator.


### Steps to Reproduce the Problem

1. Deploy two headless services (A and B) in the same namespace and each with ready pods 
2. Create a ScaledObject with type: metrics-api, `aggregateFromKubeServiceEndpoints: "true"`, targeting services A
3. Observe logs of the service B and see requests from KEDA Operator targeting the URL configured in the ScaledObject


### Logs from KEDA operator

n/a


### KEDA Version

2.19.0

### Kubernetes Version

1.34

### Platform

Google Cloud

### Scaler Details

Metrics API

### Would you be open to contributing a fix?

Yes

### Anything else?

_No response_


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0f60aef02e..3ae334cf4cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -97,6 +97,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
 - **Github Runner Scaler**: Improve URL construction and error handling ([#7495](https://github.com/kedacore/keda/pull/7495))
 - **Github Runner Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469))
 - **Loki Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469))
+- **Metrics API Scaler**: Fix `aggregateFromKubeServiceEndpoints` using empty label selector that matched all EndpointSlices in the namespace instead of only the target service's ([#7641](https://github.com/kedacore/keda/issues/7641))
 - **NATS JetStream Scaler**: URL-encode user input in monitoring URL construction ([#7483](https://github.com/kedacore/keda/pull/7483))
 - **Prometheus Scaler**: Handle NaN results in the same manner as Inf ([#7475](https://github.com/kedacore/keda/issues/7475))
 - **Prometheus Scaler**: Limit HTTP error response logging ([#7469](https://github.com/kedacore/keda/pull/7469))
diff --git a/pkg/scalers/metrics_api_scaler.go b/pkg/scalers/metrics_api_scaler.go
index cbb00feab95..56eb802520c 100644
--- a/pkg/scalers/metrics_api_scaler.go
+++ b/pkg/scalers/metrics_api_scaler.go
@@ -325,10 +325,9 @@ func (s *metricsAPIScaler) getEndpointsUrlsFromServiceURL(ctx context.Context, s
 	}
 	// get service serviceEndpointsSlices
 	serviceEndpointsSlices := &discoveryV1.EndpointSliceList{}
-	serviceNameSelector := labels.NewSelector()
-	serviceNameSelector.Matches(labels.Set(map[string]string{
+	serviceNameSelector := labels.SelectorFromSet(labels.Set{
 		discoveryV1.LabelServiceName: serviceName,
-	}))
+	})
 	err = s.kubeClient.List(ctx, serviceEndpointsSlices, &client.ListOptions{
 		LabelSelector: serviceNameSelector,
 		Namespace:     namespace,


Test patch:
diff --git a/pkg/scalers/metrics_api_scaler_test.go b/pkg/scalers/metrics_api_scaler_test.go
index 4dd2ba3208f..8a4141672e6 100644
--- a/pkg/scalers/metrics_api_scaler_test.go
+++ b/pkg/scalers/metrics_api_scaler_test.go
@@ -8,8 +8,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/go-logr/logr"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
+	discoveryV1 "k8s.io/api/discovery/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	"github.com/kedacore/keda/v2/pkg/scalers/scalersconfig"
 )
@@ -255,3 +259,52 @@ func TestGetMetricValueErrorMessage(t *testing.T) {
 
 	assert.Equal(t, err.Error(), "/api/v1/: api returned 418")
 }
+
+func TestGetEndpointsUrlsFromServiceURL_SelectorFiltersCorrectly(t *testing.T) {
+	ready := true
+	port8080 := int32(8080)
+
+	targetSlice := &discoveryV1.EndpointSlice{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-service-abc",
+			Namespace: "my-namespace",
+			Labels: map[string]string{
+				discoveryV1.LabelServiceName: "my-service",
+			},
+		},
+		Ports: []discoveryV1.EndpointPort{{Port: &port8080}},
+		Endpoints: []discoveryV1.Endpoint{{
+			Addresses:  []string{"10.0.0.1"},
+			Conditions: discoveryV1.EndpointConditions{Ready: &ready},
+		}},
+	}
+
+	unrelatedSlice := &discoveryV1.EndpointSlice{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "other-service-xyz",
+			Namespace: "my-namespace",
+			Labels: map[string]string{
+				discoveryV1.LabelServiceName: "other-service",
+			},
+		},
+		Ports: []discoveryV1.EndpointPort{{Port: &port8080}},
+		Endpoints: []discoveryV1.Endpoint{{
+			Addresses:  []string{"10.0.0.99"},
+			Conditions: discoveryV1.EndpointConditions{Ready: &ready},
+		}},
+	}
+
+	kubeClient := fake.NewClientBuilder().
+		WithLists(&discoveryV1.EndpointSliceList{Items: []discoveryV1.EndpointSlice{*targetSlice, *unrelatedSlice}}).
+		Build()
+
+	s := metricsAPIScaler{
+		kubeClient: kubeClient,
+		logger:     logr.Discard(),
+	}
+
+	urls, err := s.getEndpointsUrlsFromServiceURL(t.Context(), "http://my-service.my-namespace.svc:8080/metrics")
+	assert.NoError(t, err)
+	assert.Equal(t, []string{"http://10.0.0.1:8080/metrics"}, urls,
+		"should only return endpoints from my-service, not other-service")
+}