Skip to content

Commit 8a0dfe9

Browse files
authored
Namespace filter for service discovery (issue #1000). (#1113)
1 parent 79c1871 commit 8a0dfe9

File tree

29 files changed

+1017
-92
lines changed

29 files changed

+1017
-92
lines changed

docs/src/main/asciidoc/_configprops.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
|spring.cloud.kubernetes.config.sources | |
6060
|spring.cloud.kubernetes.config.use-name-as-prefix | `+++false+++` |
6161
|spring.cloud.kubernetes.discovery.all-namespaces | `+++false+++` |
62-
|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `+++60+++` |
62+
|spring.cloud.kubernetes.discovery.namespaces | `+++[]+++` |
63+
|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `+++60+++` |
6364
|spring.cloud.kubernetes.discovery.enabled | `+++true+++` |
6465
|spring.cloud.kubernetes.discovery.filter | |
6566
|spring.cloud.kubernetes.discovery.include-not-ready-addresses | `+++false+++` |

docs/src/main/asciidoc/discovery-client.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ spring.cloud.kubernetes.discovery.all-namespaces=true
7878
----
7979
====
8080

81+
To discover services and endpoints only from specified namespaces you should set property `all-namespaces` to `false` and set the following property in `application.properties` (in this example namespaces are: `ns1` and `ns2`).
82+
83+
====
84+
[source]
85+
----
86+
spring.cloud.kubernetes.discovery.namespaces[0]=ns1
87+
spring.cloud.kubernetes.discovery.namespaces[1]=ns2
88+
----
89+
====
90+
8191
To discover service endpoint addresses that are not marked as "ready" by the kubernetes api server, you can set the following property in `application.properties` (default: false):
8292

8393
====

spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Optional;
2525
import java.util.function.Supplier;
2626
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
2728

2829
import io.kubernetes.client.extended.wait.Wait;
2930
import io.kubernetes.client.informer.SharedInformer;
@@ -98,14 +99,17 @@ public List<ServiceInstance> getInstances(String serviceId) {
9899
log.warn("Namespace is null or empty, this may cause issues looking up services");
99100
}
100101

101-
V1Service service = properties.allNamespaces() ? this.serviceLister.list().stream()
102-
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).findFirst().orElse(null)
103-
: this.serviceLister.namespace(this.namespace).get(serviceId);
104-
if (service == null || !matchServiceLabels(service)) {
102+
List<V1Service> services = properties.allNamespaces() ? this.serviceLister.list().stream()
103+
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).toList()
104+
: List.of(this.serviceLister.namespace(this.namespace).get(serviceId));
105+
if (services.size() == 0 || !services.stream().anyMatch(this::matchServiceLabels)) {
105106
// no such service present in the cluster
106107
return new ArrayList<>();
107108
}
109+
return services.stream().flatMap(s -> getServiceInstanceDetails(s, serviceId)).toList();
110+
}
108111

112+
private Stream<ServiceInstance> getServiceInstanceDetails(V1Service service, String serviceId) {
109113
Map<String, String> svcMetadata = new HashMap<>();
110114
if (this.properties.metadata() != null) {
111115
if (this.properties.metadata().addLabels()) {
@@ -132,7 +136,7 @@ public List<ServiceInstance> getInstances(String serviceId) {
132136
.get(service.getMetadata().getName());
133137
if (ep == null || ep.getSubsets() == null) {
134138
// no available endpoints in the cluster
135-
return new ArrayList<>();
139+
return Stream.empty();
136140
}
137141

138142
Optional<String> discoveredPrimaryPortName = Optional.empty();
@@ -166,7 +170,7 @@ public List<ServiceInstance> getInstances(String serviceId) {
166170
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
167171
addr.getIp(), port, metadata, false, service.getMetadata().getNamespace(),
168172
service.getMetadata().getClusterName()));
169-
}).collect(Collectors.toList());
173+
});
170174
}
171175

172176
private int findEndpointPort(List<V1EndpointPort> endpointPorts, String primaryPortName, String serviceId) {

spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public class KubernetesInformerDiscoveryClientTests {
6565
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
6666
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
6767

68+
private static final V1Endpoints testEndpoints2 = new V1Endpoints()
69+
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace2"))
70+
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
71+
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
72+
6873
private static final V1Endpoints testEndpointWithoutReadyAddresses = new V1Endpoints()
6974
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
7075
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
@@ -108,7 +113,8 @@ public void testServiceWithUnsetPortNames() {
108113
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithUnsetPortName);
109114

110115
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
111-
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
116+
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
117+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
112118

113119
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
114120
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -141,7 +147,7 @@ public void testDiscoveryWithServiceLabels() {
141147
labels.put("spring", "true");
142148

143149
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
144-
true, 60, false, null, Set.of(), labels, null, null, 0);
150+
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
145151

146152
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
147153
sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties);
@@ -160,7 +166,7 @@ public void testDiscoveryInstancesWithServiceLabels() {
160166
labels.put("spring", "true");
161167

162168
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
163-
true, 60, false, null, Set.of(), labels, null, null, 0);
169+
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
164170

165171
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
166172
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -188,7 +194,7 @@ public void testDiscoveryGetInstanceAllNamespaceShouldWork() {
188194
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
189195

190196
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
191-
true, 60, false, null, Set.of(), null, null, null, 0);
197+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
192198

193199
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
194200
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -203,7 +209,7 @@ public void testDiscoveryGetInstanceOneNamespaceShouldWork() {
203209
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
204210

205211
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
206-
true, 60, false, null, Set.of(), null, null, null, 0);
212+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
207213

208214
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
209215
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -230,7 +236,7 @@ public void testDiscoveryGetInstanceWithNotReadyAddressesIncludedShouldWork() {
230236
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithoutReadyAddresses);
231237

232238
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
233-
true, 60, true, null, Set.of(), null, null, null, 0);
239+
Set.of(), true, 60, true, null, Set.of(), null, null, null, 0);
234240

235241
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
236242
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -271,7 +277,7 @@ public void instanceWithMultiplePortsAndPrimaryPortNameConfiguredWithLabelShould
271277
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
272278

273279
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
274-
true, 60, false, null, Set.of(), null, null, null, 0);
280+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
275281

276282
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
277283
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -290,7 +296,7 @@ public void instanceWithMultiplePortsAndMisconfiguredPrimaryPortNameInLabelShoul
290296
testEndpointWithMultiplePortsWithoutSupportedPortNames);
291297

292298
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
293-
true, 60, false, null, Set.of(), null, null, null, 0);
299+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
294300

295301
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
296302
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -306,7 +312,7 @@ public void instanceWithMultiplePortsAndGenericPrimaryPortNameConfiguredShouldWo
306312
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
307313

308314
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
309-
true, 60, false, null, Set.of(), null, "https", null, 0);
315+
Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0);
310316

311317
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
312318
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -322,7 +328,7 @@ public void instanceWithMultiplePortsAndMisconfiguredGenericPrimaryPortNameShoul
322328
testEndpointWithMultiplePortsWithoutSupportedPortNames);
323329

324330
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
325-
true, 60, false, null, Set.of(), null, "oops", null, 0);
331+
Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0);
326332

327333
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
328334
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -337,7 +343,7 @@ public void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedShouldFal
337343
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
338344

339345
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
340-
true, 60, false, null, Set.of(), null, null, null, 0);
346+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
341347

342348
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
343349
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -352,7 +358,7 @@ public void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedOrHttpsPo
352358
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePortsWithoutHttps);
353359

354360
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
355-
true, 60, false, null, Set.of(), null, null, null, 0);
361+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
356362

357363
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
358364
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -368,7 +374,7 @@ public void instanceWithMultiplePortsAndWithoutAnyConfigurationShouldPickTheFirs
368374
testEndpointWithMultiplePortsWithoutSupportedPortNames);
369375

370376
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
371-
true, 60, false, null, Set.of(), null, null, null, 0);
377+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
372378

373379
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
374380
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -377,6 +383,24 @@ public void instanceWithMultiplePortsAndWithoutAnyConfigurationShouldPickTheFirs
377383
"test-svc-1", "1.1.1.1", 80, new HashMap<>(), false, "namespace1", null));
378384
}
379385

386+
@Test
387+
public void getInstancesShouldReturnInstancesWithTheSameServiceIdFromNamespaces() {
388+
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
389+
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1, testEndpoints2);
390+
391+
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
392+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
393+
394+
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(null,
395+
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
396+
397+
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(
398+
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false,
399+
"namespace1", null),
400+
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false,
401+
"namespace2", null));
402+
}
403+
380404
private Lister<V1Service> setupServiceLister(V1Service... services) {
381405
Cache<V1Service> serviceCache = new Cache<>();
382406
Lister<V1Service> serviceLister = new Lister<>(serviceCache);

spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void testDiscoveryGetInstanceAllNamespaceShouldWork() {
101101
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
102102

103103
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
104-
true, 60, false, null, Set.of(), null, null, null, 0);
104+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
105105

106106
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
107107
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister,
@@ -120,7 +120,7 @@ public void testDiscoveryGetInstanceOneNamespaceShouldWork() {
120120
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
121121

122122
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
123-
true, 60, false, null, Set.of(), null, null, null, 0);
123+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
124124

125125
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
126126
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("namespace1");

spring-cloud-kubernetes-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/KubernetesClientServicesListSupplierTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ void getListAllNamespaces() {
146146
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
147147
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("default");
148148
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
149-
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
149+
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
150+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
150151
CoreV1Api coreV1Api = new CoreV1Api();
151152
KubernetesClientServiceInstanceMapper mapper = new KubernetesClientServiceInstanceMapper(
152153
new KubernetesLoadBalancerProperties(), kubernetesDiscoveryProperties);

spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryProperties.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
/**
2828
* @param enabled if kubernetes discovery is enabled
2929
* @param allNamespaces if discover is enabled for all namespaces
30+
* @param namespaces If set and allNamespaces is false, then only the services and
31+
* endpoints matching these namespaces will be fetched from the Kubernetes API server.
3032
* @param waitCacheReady wait for the discovery cache (service and endpoints) to be fully
3133
* loaded, otherwise aborts the application on starting
3234
* @param cacheLoadingTimeoutSeconds timeout for initializing discovery cache, will abort
@@ -45,6 +47,7 @@
4547
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
4648
public record KubernetesDiscoveryProperties(
4749
@DefaultValue("true") boolean enabled, boolean allNamespaces,
50+
@DefaultValue Set<String> namespaces,
4851
@DefaultValue("true") boolean waitCacheReady,
4952
@DefaultValue("60") long cacheLoadingTimeoutSeconds,
5053
boolean includeNotReadyAddresses, String filter,
@@ -57,8 +60,8 @@ public record KubernetesDiscoveryProperties(
5760
/**
5861
* Default instance.
5962
*/
60-
public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, true, 60,
61-
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
63+
public static final KubernetesDiscoveryProperties DEFAULT = new KubernetesDiscoveryProperties(true, false, Set.of(),
64+
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
6265

6366
/**
6467
* @param addLabels include labels as metadata

spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/KubernetesDiscoveryPropertiesTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ void testBindingWhenNoPropertiesProvided() {
4343

4444
assertThat(props.enabled()).isTrue();
4545
assertThat(props.allNamespaces()).isFalse();
46+
assertThat(props.namespaces()).isEmpty();
4647
assertThat(props.waitCacheReady()).isTrue();
4748
assertThat(props.cacheLoadingTimeoutSeconds()).isEqualTo(60);
4849
assertThat(props.includeNotReadyAddresses()).isFalse();
@@ -59,7 +60,9 @@ void testBindingWhenSomePropertiesProvided() {
5960
new ApplicationContextRunner().withUserConfiguration(KubernetesDiscoveryPropertiesMetadataTests.Config.class)
6061
.withPropertyValues("spring.cloud.kubernetes.discovery.filter=some-filter",
6162
"spring.cloud.kubernetes.discovery.knownSecurePorts[0]=222",
62-
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix")
63+
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix",
64+
"spring.cloud.kubernetes.discovery.namespaces[0]=ns1",
65+
"spring.cloud.kubernetes.discovery.namespaces[1]=ns2")
6366
.run(context -> {
6467
KubernetesDiscoveryProperties props = context.getBean(KubernetesDiscoveryProperties.class);
6568
assertThat(props).isNotNull();
@@ -69,6 +72,7 @@ void testBindingWhenSomePropertiesProvided() {
6972

7073
assertThat(props.enabled()).isTrue();
7174
assertThat(props.allNamespaces()).isFalse();
75+
assertThat(props.namespaces()).containsExactlyInAnyOrder("ns1", "ns2");
7276
assertThat(props.waitCacheReady()).isTrue();
7377
assertThat(props.cacheLoadingTimeoutSeconds()).isEqualTo(60);
7478
assertThat(props.includeNotReadyAddresses()).isFalse();

0 commit comments

Comments
 (0)