Skip to content

Commit 3dcab2b

Browse files
authored
Use endpoint slices for fabric8 catalog watcher (#1149)
1 parent 4d48ada commit 3dcab2b

File tree

30 files changed

+867
-176
lines changed

30 files changed

+867
-176
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,19 @@ should not rely on the details. Instead, they should see if there are difference
134134
The endpoints will be queried in either all namespaces (enabled via `spring.cloud.kubernetes.discovery.all-namespaces=true`), or
135135
we will use: xref:property-source-config.adoc#namespace-resolution[Namespace Resolution].
136136

137-
138-
139-
140137
In order to enable this functionality you need to add
141138
`@EnableScheduling` on a configuration class in your application.
139+
140+
By default, we use the `Endpoints`(see https://kubernetes.io/docs/concepts/services-networking/service/#endpoints) API to find out the current state of services. There is another way though, via `EndpointSlices` (https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/). Such support can be enabled via a property: `spring.cloud.kubernetes.discovery.use-endpoint-slices=true` (by default it is `false`). Of course, your cluster has to support it also. As a matter of fact, if you enable this property, but your cluster does not support it, we will fail starting the application. If you decide to enable such support, you also need proper Role/ClusterRole set-up. For example:
141+
142+
```
143+
apiVersion: rbac.authorization.k8s.io/v1
144+
kind: Role
145+
metadata:
146+
namespace: default
147+
name: namespace-reader
148+
rules:
149+
- apiGroups: ["discovery.k8s.io"]
150+
resources: ["endpointslices"]
151+
verbs: ["get", "list", "watch"]
152+
```

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void testServiceWithUnsetPortNames() {
124124

125125
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
126126
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
127-
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
127+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
128128

129129
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
130130
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -157,7 +157,7 @@ public void testDiscoveryWithServiceLabels() {
157157
labels.put("spring", "true");
158158

159159
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
160-
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
160+
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0, true);
161161

162162
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
163163
sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties);
@@ -176,7 +176,7 @@ public void testDiscoveryInstancesWithServiceLabels() {
176176
labels.put("spring", "true");
177177

178178
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
179-
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0);
179+
Set.of(), true, 60, false, null, Set.of(), labels, null, null, 0, true);
180180

181181
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
182182
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -192,7 +192,7 @@ public void testDiscoveryInstancesWithSecuredServiceByAnnotations() {
192192
Lister<V1Service> serviceLister = setupServiceLister(testServiceSecuredAnnotation1);
193193
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
194194
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
195-
Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0);
195+
Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0, false);
196196
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
197197
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
198198
assertThat(discoveryClient.getServices().toArray())
@@ -207,7 +207,7 @@ public void testDiscoveryInstancesWithSecuredServiceByLabels() {
207207
Lister<V1Service> serviceLister = setupServiceLister(testServiceSecuredLabel1);
208208
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
209209
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
210-
Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0);
210+
Set.of(), true, 60, false, null, Set.of(), new HashMap<>(), null, null, 0, false);
211211
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
212212
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
213213

@@ -235,7 +235,7 @@ public void testDiscoveryGetInstanceAllNamespaceShouldWork() {
235235
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
236236

237237
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
238-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
238+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
239239

240240
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
241241
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -250,7 +250,7 @@ public void testDiscoveryGetInstanceOneNamespaceShouldWork() {
250250
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
251251

252252
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
253-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
253+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
254254

255255
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
256256
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -277,7 +277,7 @@ public void testDiscoveryGetInstanceWithNotReadyAddressesIncludedShouldWork() {
277277
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithoutReadyAddresses);
278278

279279
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
280-
Set.of(), true, 60, true, null, Set.of(), null, null, null, 0);
280+
Set.of(), true, 60, true, null, Set.of(), null, null, null, 0, true);
281281

282282
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
283283
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -318,7 +318,7 @@ public void instanceWithMultiplePortsAndPrimaryPortNameConfiguredWithLabelShould
318318
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
319319

320320
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
321-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
321+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
322322

323323
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
324324
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -337,7 +337,7 @@ public void instanceWithMultiplePortsAndMisconfiguredPrimaryPortNameInLabelShoul
337337
testEndpointWithMultiplePortsWithoutSupportedPortNames);
338338

339339
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
340-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
340+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
341341

342342
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
343343
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -353,7 +353,7 @@ public void instanceWithMultiplePortsAndGenericPrimaryPortNameConfiguredShouldWo
353353
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
354354

355355
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
356-
Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0);
356+
Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0, true);
357357

358358
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
359359
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -369,7 +369,7 @@ public void instanceWithMultiplePortsAndMisconfiguredGenericPrimaryPortNameShoul
369369
testEndpointWithMultiplePortsWithoutSupportedPortNames);
370370

371371
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
372-
Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0);
372+
Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0, true);
373373

374374
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
375375
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -384,7 +384,7 @@ public void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedShouldFal
384384
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts);
385385

386386
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
387-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
387+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
388388

389389
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
390390
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -399,7 +399,7 @@ public void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedOrHttpsPo
399399
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpointWithMultiplePortsWithoutHttps);
400400

401401
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
402-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
402+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
403403

404404
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
405405
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -415,7 +415,7 @@ public void instanceWithMultiplePortsAndWithoutAnyConfigurationShouldPickTheFirs
415415
testEndpointWithMultiplePortsWithoutSupportedPortNames);
416416

417417
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
418-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
418+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true);
419419

420420
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
421421
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
@@ -430,7 +430,7 @@ public void getInstancesShouldReturnInstancesWithTheSameServiceIdFromNamespaces(
430430
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1, testEndpoints2);
431431

432432
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
433-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
433+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false);
434434

435435
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(null,
436436
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);

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-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
104+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false);
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-
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0);
123+
Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false);
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void getListAllNamespaces() {
147147
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("default");
148148
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
149149
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
150-
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0);
150+
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
151151
CoreV1Api coreV1Api = new CoreV1Api();
152152
KubernetesClientServiceInstanceMapper mapper = new KubernetesClientServiceInstanceMapper(
153153
new KubernetesLoadBalancerProperties(), kubernetesDiscoveryProperties);

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* fetched from the Kubernetes API server.
4343
* @param primaryPortName If set then the port with a given name is used as primary when
4444
* multiple ports are defined for a service.
45+
* @param useEndpointSlices use EndpointSlice instead of Endpoints
4546
*/
4647
// @formatter:off
4748
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
@@ -54,14 +55,15 @@ public record KubernetesDiscoveryProperties(
5455
@DefaultValue({"443", "8443"}) Set<Integer> knownSecurePorts,
5556
@DefaultValue Map<String, String> serviceLabels, String primaryPortName,
5657
@DefaultValue Metadata metadata,
57-
@DefaultValue("" + DEFAULT_ORDER) int order) {
58+
@DefaultValue("" + DEFAULT_ORDER) int order,
59+
boolean useEndpointSlices) {
5860
// @formatter:on
5961

6062
/**
6163
* Default instance.
6264
*/
6365
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);
66+
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
6567

6668
/**
6769
* @param addLabels include labels as metadata

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ void testBindingWhenNoPropertiesProvided() {
5252
assertThat(props.serviceLabels()).isEmpty();
5353
assertThat(props.primaryPortName()).isNull();
5454
assertThat(props.order()).isZero();
55+
assertThat(props.useEndpointSlices()).isFalse();
5556
});
5657
}
5758

@@ -61,6 +62,7 @@ void testBindingWhenSomePropertiesProvided() {
6162
.withPropertyValues("spring.cloud.kubernetes.discovery.filter=some-filter",
6263
"spring.cloud.kubernetes.discovery.knownSecurePorts[0]=222",
6364
"spring.cloud.kubernetes.discovery.metadata.labelsPrefix=labelsPrefix",
65+
"spring.cloud.kubernetes.discovery.use-endpoint-slices=true",
6466
"spring.cloud.kubernetes.discovery.namespaces[0]=ns1",
6567
"spring.cloud.kubernetes.discovery.namespaces[1]=ns2")
6668
.run(context -> {
@@ -81,6 +83,7 @@ void testBindingWhenSomePropertiesProvided() {
8183
assertThat(props.serviceLabels()).isEmpty();
8284
assertThat(props.primaryPortName()).isNull();
8385
assertThat(props.order()).isZero();
86+
assertThat(props.useEndpointSlices()).isTrue();
8487
});
8588
}
8689

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.kubernetes.fabric8.discovery;
18+
19+
import java.util.Comparator;
20+
import java.util.List;
21+
import java.util.Objects;
22+
import java.util.stream.Stream;
23+
24+
import io.fabric8.kubernetes.api.model.ObjectReference;
25+
import io.fabric8.kubernetes.client.KubernetesClient;
26+
27+
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
28+
import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace;
29+
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
30+
31+
/**
32+
* A simple holder for some instances needed for either Endpoints or EndpointSlice catalog
33+
* implementations.
34+
*
35+
* @author wind57
36+
*/
37+
record Fabric8CatalogWatchContext(KubernetesClient kubernetesClient, KubernetesDiscoveryProperties properties,
38+
KubernetesNamespaceProvider namespaceProvider) {
39+
40+
static List<EndpointNameAndNamespace> state(Stream<ObjectReference> references) {
41+
return references.filter(Objects::nonNull).map(x -> new EndpointNameAndNamespace(x.getName(), x.getNamespace()))
42+
.sorted(Comparator.comparing(EndpointNameAndNamespace::endpointName, String::compareTo)).toList();
43+
}
44+
45+
}

0 commit comments

Comments
 (0)