merge 1.1.x

This commit is contained in:
Haytham Mohamed
2020-12-01 12:19:27 -06:00
77 changed files with 2146 additions and 91 deletions

View File

@@ -27,8 +27,7 @@ This reference guide covers how to use Spring Cloud Kubernetes.
== Why do you need Spring Cloud Kubernetes?
Spring Cloud Kubernetes provide Spring Cloud common interface implementations that consume Kubernetes native services.
The main objective of the projects provided in this repository is to facilitate the integration of Spring Cloud and Spring Boot applications running inside Kubernetes.
Spring Cloud Kubernetes provides implementations of well known Spring Cloud interfaces allowing developers to build and run Spring Cloud applications on Kubernetes. While this project may be useful to you when building a cloud native application, it is also not a requirement in order to deploy a Spring Boot app on Kubernetes. If you are just getting started in your journey to running your Spring Boot app on Kubernetes you can accomplish a lot with nothing more than a basic Spring Boot app and Kubernetes itself. To learn more, you can get started by reading the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#cloud-deployment-kubernetes[Spring Boot reference documentation for deploying to Kubernetes ] and also working through the workshop material https://hackmd.io/@ryanjbaxter/spring-on-k8s-workshop[Spring and Kubernetes].
== Starters

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
<name>Spring Cloud Kubernetes Docs</name>

View File

@@ -0,0 +1,7 @@
== Info Contributor
Spring Cloud Kubernetes includes an `InfoContributor` which adds Pod information to
Spring Boot's `/info` Acturator endpoint.
You can disable this `InfoContributor` by setting `management.info.kubernetes.enabled`
to `false` in `bootstrap.[properties | yaml]`.

View File

@@ -0,0 +1,28 @@
== LoadBalancer for Kubernetes
This project includes Spring Cloud Load Balancer for load balancing based on Kubernetes Endpoints and provides implementation of load balancer based on Kubernetes Service.
To include it to your project add the following dependency.
====
[source,xml]
----
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
</dependency>
----
====
To enable load balancing based on Kubernetes Service name use the following property. Then load balancer would try to call application using address, for example `service-a.default.svc.cluster.local`
====
[source]
----
spring.cloud.kubernetes.loadbalancer.mode=SERVICE
----
====
To enabled load balancing across all namespaces use the following property. Property from `spring-cloud-kubernetes-discovery` module is respected.
====
[source]
----
spring.cloud.kubernetes.discovery.all-namespaces=true
----
====

View File

@@ -279,6 +279,10 @@ You can specify multiple (exact) file paths in `spring.cloud.kubernetes.config.p
NOTE: You have to provide the full exact path to each property file, because directories are not being recursively parsed.
NOTE: If you use `spring.cloud.kubernetes.config.paths` or `spring.cloud.kubernetes.secrets.path` the automatic reload
functionality will not work. You will need to make a `POST` request to the `/actuator/refresh` endpoint or
restart/redeploy the application.
.Properties:
[options="header,footer"]
|===

View File

@@ -5,8 +5,7 @@ This reference guide covers how to use Spring Cloud Kubernetes.
== Why do you need Spring Cloud Kubernetes?
Spring Cloud Kubernetes provide Spring Cloud common interface implementations that consume Kubernetes native services.
The main objective of the projects provided in this repository is to facilitate the integration of Spring Cloud and Spring Boot applications running inside Kubernetes.
Spring Cloud Kubernetes provides implementations of well known Spring Cloud interfaces allowing developers to build and run Spring Cloud applications on Kubernetes. While this project may be useful to you when building a cloud native application, it is also not a requirement in order to deploy a Spring Boot app on Kubernetes. If you are just getting started in your journey to running your Spring Boot app on Kubernetes you can accomplish a lot with nothing more than a basic Spring Boot app and Kubernetes itself. To learn more, you can get started by reading the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#cloud-deployment-kubernetes[Spring Boot reference documentation for deploying to Kubernetes ] and also working through the workshop material https://hackmd.io/@ryanjbaxter/spring-on-k8s-workshop[Spring and Kubernetes].
include::getting-started.adoc[]
@@ -22,8 +21,12 @@ include::kubernetes-awareness.adoc[]
include::pod-health-indicator.adoc[]
include::info-contributor.adoc[]
include::leader-election.adoc[]
include::load-balancer.adoc[]
include::security-service-accounts.adoc[]
include::service-registry.adoc[]

12
pom.xml
View File

@@ -24,13 +24,13 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.3.1.BUILD-SNAPSHOT</version>
<version>2.3.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Kubernetes</name>
@@ -63,9 +63,9 @@
<properties>
<!-- Dependency Versions -->
<spring-cloud-commons.version>2.2.4.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.4.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-config.version>2.2.4.BUILD-SNAPSHOT</spring-cloud-config.version>
<spring-cloud-commons.version>2.2.7.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.7.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-config.version>2.2.7.BUILD-SNAPSHOT</spring-cloud-config.version>
<!-- Maven Plugin Versions -->
<maven-compiler-plugin.version>3.5</maven-compiler-plugin.version>
@@ -99,6 +99,8 @@
<module>spring-cloud-kubernetes-istio</module>
<module>spring-cloud-kubernetes-integration-tests</module>
<module>docs</module>
<module>spring-cloud-kubernetes-loadbalancer</module>
<module>spring-cloud-starter-kubernetes-loadbalancer</module>
</modules>
<dependencyManagement>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -113,7 +113,7 @@ public class ConfigMapPropertySourceLocator implements PropertySourceLocator {
}).filter(Files::isRegularFile).forEach(p -> {
try {
String content = new String(Files.readAllBytes(p)).trim();
String filename = p.getFileName().toString().toLowerCase();
String filename = p.toAbsolutePath().toString().toLowerCase();
if (filename.endsWith(".properties")) {
addPropertySourceIfNeeded(
c -> PROPERTIES_TO_MAP

View File

@@ -24,6 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -36,6 +38,7 @@ import org.springframework.cloud.kubernetes.config.ConfigMapPropertySourceLocato
import org.springframework.cloud.kubernetes.config.SecretsPropertySourceLocator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.scheduling.annotation.EnableAsync;
@@ -72,32 +75,28 @@ public class ConfigReloadAutoConfiguration {
@Autowired
private KubernetesClient kubernetesClient;
@Autowired
private ConfigMapPropertySourceLocator configMapPropertySourceLocator;
@Autowired
private SecretsPropertySourceLocator secretsPropertySourceLocator;
/**
* @param properties config reload properties
* @param strategy configuration update strategy
* @return a bean that listen to configuration changes and fire a reload.
*/
@Bean
@ConditionalOnMissingBean
@Conditional(OnConfigEnabledOrSecretsEnabled.class)
public ConfigurationChangeDetector propertyChangeWatcher(
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy) {
ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy,
@Autowired(
required = false) ConfigMapPropertySourceLocator configMapPropertySourceLocator,
@Autowired(
required = false) SecretsPropertySourceLocator secretsPropertySourceLocator) {
switch (properties.getMode()) {
case POLLING:
return new PollingConfigurationChangeDetector(this.environment,
properties, this.kubernetesClient, strategy,
this.configMapPropertySourceLocator,
this.secretsPropertySourceLocator);
configMapPropertySourceLocator, secretsPropertySourceLocator);
case EVENT:
return new EventBasedConfigurationChangeDetector(this.environment,
properties, this.kubernetesClient, strategy,
this.configMapPropertySourceLocator,
this.secretsPropertySourceLocator);
configMapPropertySourceLocator, secretsPropertySourceLocator);
}
throw new IllegalStateException(
"Unsupported configuration reload mode: " + properties.getMode());
@@ -148,6 +147,24 @@ public class ConfigReloadAutoConfiguration {
}
}
private static class OnConfigEnabledOrSecretsEnabled extends AnyNestedCondition {
OnConfigEnabledOrSecretsEnabled() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(ConfigMapPropertySourceLocator.class)
static class configEnabled {
}
@ConditionalOnBean(SecretsPropertySourceLocator.class)
static class secretsEnabled {
}
}
}
}

View File

@@ -66,7 +66,8 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
public void watch() {
boolean activated = false;
if (this.properties.isMonitoringConfigMaps()) {
if (this.properties.isMonitoringConfigMaps()
&& this.configMapPropertySourceLocator != null) {
try {
String name = "config-maps-watch";
this.watches.put(name, this.kubernetesClient.configMaps()
@@ -91,7 +92,8 @@ public class EventBasedConfigurationChangeDetector extends ConfigurationChangeDe
}
}
if (this.properties.isMonitoringSecrets()) {
if (this.properties.isMonitoringSecrets()
&& this.secretsPropertySourceLocator != null) {
try {
activated = false;
String name = "secrets-watch";

View File

@@ -68,7 +68,8 @@ public class PollingConfigurationChangeDetector extends ConfigurationChangeDetec
public void executeCycle() {
boolean changedConfigMap = false;
if (this.properties.isMonitoringConfigMaps()) {
if (this.properties.isMonitoringConfigMaps()
&& this.configMapPropertySourceLocator != null) {
List<? extends MapPropertySource> currentConfigMapSources = findPropertySources(
ConfigMapPropertySource.class);
@@ -81,7 +82,8 @@ public class PollingConfigurationChangeDetector extends ConfigurationChangeDetec
}
boolean changedSecrets = false;
if (this.properties.isMonitoringSecrets()) {
if (this.properties.isMonitoringSecrets()
&& this.secretsPropertySourceLocator != null) {
List<MapPropertySource> currentSecretSources = locateMapPropertySources(
this.secretsPropertySourceLocator, this.environment);
if (currentSecretSources != null && !currentSecretSources.isEmpty()) {

View File

@@ -45,11 +45,14 @@ import static org.springframework.cloud.kubernetes.config.ConfigMapTestUtil.crea
"spring.cloud.kubernetes.config.enableApi=false",
"spring.cloud.kubernetes.config.paths="
+ ConfigMapsFromFilePathsTests.FIRST_FILE_NAME_FULL_PATH + ","
+ ConfigMapsFromFilePathsTests.SECOND_FILE_NAME_FULL_PATH })
+ ConfigMapsFromFilePathsTests.SECOND_FILE_NAME_FULL_PATH + ","
+ ConfigMapsFromFilePathsTests.FIRST_FILE_NAME_DUPLICATED_FULL_PATH })
public class ConfigMapsFromFilePathsTests {
protected static final String FILES_ROOT_PATH = "/tmp/scktests";
protected static final String FILES_SUB_PATH = "another-directory";
protected static final String FIRST_FILE_NAME = "application.properties";
protected static final String SECOND_FILE_NAME = "extra.properties";
@@ -65,6 +68,9 @@ public class ConfigMapsFromFilePathsTests {
protected static final String UNUSED_FILE_NAME_FULL_PATH = FILES_ROOT_PATH + "/"
+ UNUSED_FILE_NAME;
protected static final String FIRST_FILE_NAME_DUPLICATED_FULL_PATH = FILES_ROOT_PATH
+ "/" + FILES_SUB_PATH + "/" + FIRST_FILE_NAME;
@ClassRule
public static KubernetesServer server = new KubernetesServer();
@@ -87,12 +93,14 @@ public class ConfigMapsFromFilePathsTests {
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Files.createDirectories(Paths.get(FILES_ROOT_PATH));
Files.createDirectories(Paths.get(FILES_ROOT_PATH + "/" + FILES_SUB_PATH));
createFileWithContent(FIRST_FILE_NAME_FULL_PATH,
"bean.greeting=Hello from path!");
createFileWithContent(SECOND_FILE_NAME_FULL_PATH, "bean.farewell=Bye from path!");
createFileWithContent(UNUSED_FILE_NAME_FULL_PATH,
"bean.morning=Morning from path!");
createFileWithContent(FIRST_FILE_NAME_DUPLICATED_FULL_PATH,
"bean.bonjour=Bonjour from path!");
}
@AfterClass
@@ -125,4 +133,10 @@ public class ConfigMapsFromFilePathsTests {
.expectBody().jsonPath("content").isEqualTo("Good morning, World!");
}
@Test
public void bonjourInputShouldReturnPropertyFromDuplicatedFile() {
this.webClient.get().uri("/api/bonjour").exchange().expectStatus().isOk()
.expectBody().jsonPath("content").isEqualTo("Bonjour from path!");
}
}

View File

@@ -52,4 +52,10 @@ public class GreetingController {
return new ResponseMessage(String.format(this.properties.getMorning(), name));
}
@RequestMapping("/api/bonjour")
public ResponseMessage bonjour(
@RequestParam(value = "name", defaultValue = "World") String name) {
return new ResponseMessage(String.format(this.properties.getBonjour(), name));
}
}

View File

@@ -27,6 +27,8 @@ public class GreetingProperties {
private String morning = "Good morning, %s!";
private String bonjour = "Bonjour, %s!";
public String getGreeting() {
return this.greeting;
}
@@ -51,4 +53,12 @@ public class GreetingProperties {
this.morning = morning;
}
public String getBonjour() {
return this.bonjour;
}
public void setBonjour(String bonjour) {
this.bonjour = bonjour;
}
}

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -26,6 +26,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.info.ConditionalOnEnabledInfoContributor;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -130,7 +131,7 @@ public class KubernetesAutoConfiguration {
base.getHttpsProxy()))
.withProxyUsername(or(kubernetesClientProperties.getProxyUsername(),
base.getProxyUsername()))
.withPassword(or(kubernetesClientProperties.getProxyPassword(),
.withProxyPassword(or(kubernetesClientProperties.getProxyPassword(),
base.getProxyPassword()))
.withNoProxy(
or(kubernetesClientProperties.getNoProxy(), base.getNoProxy()))
@@ -166,6 +167,7 @@ public class KubernetesAutoConfiguration {
}
@Bean
@ConditionalOnEnabledInfoContributor("kubernetes")
public KubernetesInfoContributor kubernetesInfoContributor(PodUtils podUtils) {
return new KubernetesInfoContributor(podUtils);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.server.mock.KubernetesServer;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.example.App;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = App.class,
properties = { "spring.cloud.kubernetes.client.password=mypassword",
"spring.cloud.kubernetes.client.proxy-password=myproxypassword" })
public class KubernetesAutoConfigurationTests {
@ClassRule
public static KubernetesServer server = new KubernetesServer();
@Autowired
ConfigurableApplicationContext context;
@BeforeClass
public static void setUpBeforeClass() {
KubernetesClient mockClient = server.getClient();
// Configure the kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY,
mockClient.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY,
"false");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
}
@Test
public void beansAreCreated() {
assertThat(context.getBeanNamesForType(Config.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesClient.class)).hasSize(1);
assertThat(context.getBeanNamesForType(StandardPodUtils.class)).hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesHealthIndicator.class))
.hasSize(1);
assertThat(context.getBeanNamesForType(KubernetesInfoContributor.class))
.hasSize(1);
Config config = context.getBean(Config.class);
assertThat(config.getPassword()).isEqualTo("mypassword");
assertThat(config.getProxyPassword()).isEqualTo("myproxypassword");
}
}

View File

@@ -23,21 +23,22 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.3.1.BUILD-SNAPSHOT</version>
<version>2.3.2.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-kubernetes-dependencies</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Kubernetes :: Dependencies</name>
<description>Spring Cloud Kubernetes Dependencies</description>
<properties>
<arquillian.version>1.4.0.Final</arquillian.version>
<arquillian-cube.version>1.15.2</arquillian-cube.version>
<hoverfly.version>0.13.0</hoverfly.version>
<kubernetes-client.version>4.10.3</kubernetes-client.version>
<istio-client.version>1.0.0</istio-client.version>
<mockwebserver.version>0.1.2</mockwebserver.version>
<okhttp.version>3.12.0</okhttp.version>
<okhttp.version>3.14.4</okhttp.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -98,6 +99,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-loadbalancer</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Own dependencies - Starters -->
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -117,6 +124,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
@@ -148,6 +161,18 @@
<version>${arquillian-cube.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>${hoverfly.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java</artifactId>
<version>${hoverfly.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static java.util.stream.Collectors.toMap;
import static org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance.NAMESPACE_METADATA_KEY;
/**
* Kubeneretes implementation of {@link DiscoveryClient}.
@@ -153,6 +154,10 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
endpointMetadata.putAll(portMetadata);
}
if (this.properties.isAllNamespaces()) {
endpointMetadata.put(NAMESPACE_METADATA_KEY, namespace);
}
List<EndpointAddress> addresses = s.getAddresses();
for (EndpointAddress endpointAddress : addresses) {
String instanceId = null;
@@ -162,7 +167,8 @@ public class KubernetesDiscoveryClient implements DiscoveryClient {
EndpointPort endpointPort = findEndpointPort(s);
instances.add(new KubernetesServiceInstance(instanceId, serviceId,
endpointAddress, endpointPort, endpointMetadata,
endpointAddress.getIp(), endpointPort.getPort(),
endpointMetadata,
this.isServicePortSecureResolver
.resolve(new DefaultIsServicePortSecureResolver.Input(
endpointPort.getPort(),

View File

@@ -19,9 +19,6 @@ package org.springframework.cloud.kubernetes.discovery;
import java.net.URI;
import java.util.Map;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointPort;
import org.springframework.cloud.client.ServiceInstance;
/**
@@ -31,49 +28,50 @@ import org.springframework.cloud.client.ServiceInstance;
*/
public class KubernetesServiceInstance implements ServiceInstance {
/**
* Key of the namespace metadata.
*/
public static final String NAMESPACE_METADATA_KEY = "k8s_namespace";
private static final String HTTP_PREFIX = "http";
private static final String HTTPS_PREFIX = "https";
private static final String DSL = "//";
private static final String COLN = ":";
private static final String COLON = ":";
private final String instanceId;
private final String serviceId;
private final EndpointAddress endpointAddress;
private final String host;
private final EndpointPort endpointPort;
private final int port;
private final URI uri;
private final Boolean secure;
private final Map<String, String> metadata;
/**
* @param instanceId the id of the instance.
* @param serviceId the id of the service.
* @param endpointAddress the address where the service instance can be found.
* @param endpointPort the port on which the service is running.
* @param host the address where the service instance can be found.
* @param port the port on which the service is running.
* @param metadata a map containing metadata.
* @param secure indicates whether or not the connection needs to be secure.
* @deprecated - use other constructor
*/
@Deprecated
public KubernetesServiceInstance(String serviceId, EndpointAddress endpointAddress,
EndpointPort endpointPort, Map<String, String> metadata, Boolean secure) {
this(null, serviceId, endpointAddress, endpointPort, metadata, secure);
}
public KubernetesServiceInstance(String instanceId, String serviceId,
EndpointAddress endpointAddress, EndpointPort endpointPort,
Map<String, String> metadata, Boolean secure) {
public KubernetesServiceInstance(String instanceId, String serviceId, String host,
int port, Map<String, String> metadata, Boolean secure) {
this.instanceId = instanceId;
this.serviceId = serviceId;
this.endpointAddress = endpointAddress;
this.endpointPort = endpointPort;
this.host = host;
this.port = port;
this.metadata = metadata;
this.secure = secure;
this.uri = createUri(secure ? HTTPS_PREFIX : HTTP_PREFIX, host, port);
}
@Override
@@ -88,12 +86,12 @@ public class KubernetesServiceInstance implements ServiceInstance {
@Override
public String getHost() {
return this.endpointAddress.getIp();
return this.host;
}
@Override
public int getPort() {
return this.endpointPort.getPort();
return this.port;
}
@Override
@@ -103,10 +101,7 @@ public class KubernetesServiceInstance implements ServiceInstance {
@Override
public URI getUri() {
StringBuilder sb = new StringBuilder();
sb.append(getScheme()).append(COLN).append(DSL).append(getHost()).append(COLN)
.append(getPort());
return URI.create(sb.toString());
return uri;
}
public Map<String, String> getMetadata() {
@@ -118,4 +113,15 @@ public class KubernetesServiceInstance implements ServiceInstance {
return isSecure() ? HTTPS_PREFIX : HTTP_PREFIX;
}
private URI createUri(String scheme, String host, int port) {
StringBuilder sb = new StringBuilder();
sb.append(scheme).append(COLON).append(DSL).append(host).append(COLON)
.append(port);
return URI.create(sb.toString());
}
public String getNamespace() {
return this.metadata != null ? this.metadata.get(NAMESPACE_METADATA_KEY) : null;
}
}

View File

@@ -377,6 +377,14 @@ public class KubernetesDiscoveryClientTest {
.hasSize(1);
assertThat(instances).filteredOn(s -> s.getHost().equals("ip2") && !s.isSecure())
.hasSize(1);
assertThat(instances)
.filteredOn(s -> s.getServiceId().contains("endpoint")
&& ((KubernetesServiceInstance) s).getNamespace().equals("test"))
.hasSize(1);
assertThat(instances)
.filteredOn(s -> s.getServiceId().contains("endpoint")
&& ((KubernetesServiceInstance) s).getNamespace().equals("test2"))
.hasSize(1);
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("60")).hasSize(1);
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("70")).hasSize(1);
}

View File

@@ -37,7 +37,8 @@ public class KubernetesServiceInstanceTests {
EndpointPort port = new EndpointPort();
port.setPort(8080);
KubernetesServiceInstance instance = new KubernetesServiceInstance("123",
"myservice", address, port, Collections.emptyMap(), secure);
"myservice", address.getIp(), port.getPort(), Collections.emptyMap(),
secure);
assertThat(instance.getInstanceId()).isEqualTo("123");
assertThat(instance.getServiceId()).isEqualTo("myservice");

View File

@@ -23,7 +23,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>kubernetes-circuitbreaker-ribbon-example</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>greeting-service</artifactId>

View File

@@ -23,7 +23,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>kubernetes-circuitbreaker-ribbon-example</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>name-service</artifactId>

View File

@@ -23,7 +23,7 @@
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>kubernetes-leader-election-example</artifactId>

View File

@@ -0,0 +1,94 @@
## Kubernetes Spring Cloud LoadBalancer Example
This example demonstrates how to use [Spring Cloud LoadBalancer](https://github.com/spring-cloud-incubator/spring-cloud-loadbalancer). In our example the REST `greeting service` calls the `name service`.
As the Spring Cloud LoadBalancer for Kubernetes is configured within this example, it will fetch from the Kubernetes API Server the list of the endpoints available for the name service and load-balance the request between the IP addresses available.
### Running the example
This project example runs on ALL the Kubernetes or OpenShift environments, but for development purposes you can use [Minikube - Kubernetes](https://kubernetes.io/docs/getting-started-guides/minikube/) tool
to install the platform locally within a virtual machine managed by VirtualBox, Xhyve or KVM, with no fuss.
IMPORTANT: In order for this setup to work, you need to grant permissions to retrieve "pods", "services" and "enpoints" to the serviceaccont that will be used with the greeting-service.
### Build/Deploy using Minikube
First, create a new virtual machine provisioned with Kubernetes on your laptop using the command `minikube start`.
Next, you can compile your project and generate the Kubernetes resources (yaml files containing the definition of the pod, deployment, build, service and route to be created)
like also to deploy the application on Kubernetes in one maven line by running:
```
mvn clean install fabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
```
### Call the Greeting service
When maven has finished to compile the code but also to call the platform in order to deploy the yaml files generated and tell to the platform to start the process
to build/deploy the docker image and create the containers where the Spring Boot application will run 'greeting-service" and "name-service", you will be able to
check if the pods have been created using this command :
```
kc get pods
```
If the status of the Spring Boot pod application is `running` and ready state `1`, then you can
get the external address IP/Hostname to be used to call the service from your laptop
```
minikube service --url greeting-service
```
and then call the service using the curl client
```
curl https://IP_OR_HOSTNAME/greeting
```
to get a response as such
```
Hello from name-service-1-0dzb4!d
```
### Verify the load balancing
First, scale the number of pods of the `name service` to 2
```
kc scale --replicas=2 deployment name-service
```
Wait a few minutes before to issue the curl request to call the Greeting Service to let the platform to create the new pod.
```
kc get pods --selector=project=name-service
NAME READY STATUS RESTARTS AGE
name-service-1652024859-fsnfw 1/1 Running 0 33s
name-service-1652024859-wrzjs 1/1 Running 0 6m
```
If you issue the curl request to access the greeting service, you should see that the message response
contains a different id end of the message which corresponds to the name of the pod.
```
Hello from name-service-1-0ss0r!
```
As Spring Cloud LoadBalancer will question the Kubernetes API to get, base on the `name-service` name, the list of IP Addresses assigned to the service as endpoints,
you should see that you will get a response from one of the 2 pods running
```
kc get endpoints/name-service
NAME ENDPOINTS AGE
name-service 172.17.0.5:8080,172.17.0.6:8080 40m
```
Here is an example about what you will get
```
curl https://IP_OR_HOSTNAME/greeting
Hello from name-service-1652024859-hf3xv!
curl https://IP_OR_HOSTNAME/greeting
Hello from name-service-1652024859-426kv!
...
```

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>kubernetes-loadbalancer-example</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>greeting-service</artifactId>
<name>Spring Cloud LoadBalancer :: Greeting Service</name>
<description>Spring Cloud LoadBalancer :: Greeting Service</description>
<build>
<plugins>
<plugin>
<!--skip deploy -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<type>pom</type>
<scope>import</scope>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Route
metadata:
name: greeting-service
spec:
port:
targetPort: 8080
to:
kind: Service
name: greeting-service

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.examples;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Greeting service controller.
*
* @author Gytis Trikleris
* @author Olga Maciaszek-Sharma
*/
@RestController
public class GreetingController {
private final NameService nameService;
public GreetingController(NameService nameService) {
this.nameService = nameService;
}
/**
* Endpoint to get a greeting. This endpoint uses a name server to get a name for the
* greeting.
*
* Request to the name service is guarded with a circuit breaker. Therefore if a name
* service is not available or is too slow to response fallback name is used.
*
* Delay parameter can me used to make name service response slower.
* @param delay Milliseconds for how long the response from name service should be
* delayed.
* @return Greeting string.
*/
@GetMapping("/greeting")
public Mono<String> getGreeting(
@RequestParam(value = "delay", defaultValue = "0") int delay) {
return nameService.getName(delay)
.map(name -> String.format("Hello from %s!", name));
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Entry point to the application.
*
* @author Gytis Trikleris
* @author Olga Maciaszek-Sharma
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GreetingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingServiceApplication.class, args);
}
@LoadBalanced
@Bean
WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.examples;
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Service invoking name-service via REST and guarded by Hystrix.
*
* @author Gytis Trikleris
* @author Olga Maciaszek-Sharma
*/
@Service
public class NameService {
private final WebClient webClient;
public NameService(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder.build();
}
public Mono<String> getName(int delay) {
return webClient.get()
.uri(String.format("http://name-service/name?delay=%d", delay)).retrieve()
.bodyToMono(String.class);
}
}

View File

@@ -0,0 +1,10 @@
spring:
application:
name: greeting-service
cloud:
loadbalancer:
ribbon:
enabled: false
server:
port: 8080

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>kubernetes-loadbalancer-example</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>name-service</artifactId>
<name>Spring Cloud LoadBalancer :: Name Service</name>
<description>Spring Cloud LoadBalancer :: Name Service</description>
<build>
<plugins>
<plugin>
<!--skip deploy -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Route
metadata:
name: name-service
spec:
port:
targetPort: 8080
to:
kind: Service
name: name-service

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.examples;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Name service controller.
*
* @author Gytis Trikleris
* @author Olga Maciaszek-Sharma
*/
@RestController
public class NameController {
private static final Logger LOG = LoggerFactory.getLogger(NameController.class);
private final String hostName = System.getenv("HOSTNAME");
@GetMapping("/")
public String ribbonPing() {
LOG.info("Ribbon ping");
return hostName;
}
/**
* Endpoint to get a name with a capability to delay a response for some number of
* milliseconds.
* @param delayValue Milliseconds for how long the response should be delayed.
* @return Host name.
*/
@GetMapping("/name")
public Mono<String> getName(
@RequestParam(value = "delay", defaultValue = "0") int delayValue) {
LOG.info(String.format("Returning a name '%s' with a delay '%d'", hostName,
delayValue));
delay(delayValue);
return Mono.just(hostName);
}
private void delay(int delayValue) {
try {
Thread.sleep(delayValue);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Entry point to the application.
*
* @author Gytis Trikleris
*/
@SpringBootApplication
public class NameServiceApplication {
public static void main(String[] args) {
SpringApplication.run(NameServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,6 @@
spring:
application:
name: name-service
server:
port: 8080

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>kubernetes-loadbalancer-example</artifactId>
<name>Spring Cloud LoadBalancer Example</name>
<packaging>pom</packaging>
<profiles>
<profile>
<id>kubernetes</id>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8.maven.plugin.version}</version>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<enricher>
<config>
<fmp-service>
<type>NodePort</type>
</fmp-service>
</config>
</enricher>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8.maven.plugin.version}</version>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
<goal>helm</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>integration</id>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8.maven.plugin.version}</version>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<executions>
<execution>
<id>run-integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<skipTests>false</skipTests>
<skipITs>false</skipITs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<modules>
<module>name-service</module>
<module>greeting-service</module>
</modules>
</project>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -23,7 +23,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-kubernetes-examples</artifactId>
@@ -37,6 +37,7 @@
<module>kubernetes-reload-example</module>
<module>kubernetes-hello-world-example</module>
<module>kubernetes-leader-election-example</module>
<module>kubernetes-loadbalancer-example</module>
<!-- <module>kubernetes-circuitbreaker-ribbon-example</module>
<module>kubernetes-zipkin-example</module> -->
</modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>discovery-parent</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>discovery-client</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>discovery-parent</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>discovery-service-a</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>discovery-parent</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>discovery-service-b</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Cloud Kubernetes :: Integration Tests :: Discovery Parent</name>

View File

@@ -7,13 +7,21 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>discovery-parent</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>tests</artifactId>
<name>Spring Cloud Kubernetes :: Integration Tests :: Discovery Tests</name>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Cloud Kubernetes :: Integration Tests :: Istio</name>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Spring Cloud Kubernetes :: Integration Tests :: Load Balancer</name>
<artifactId>load-balancer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@RestController
public class SimpleLoadBalancerApp {
public static void main(String[] args) {
SpringApplication.run(SimpleLoadBalancerApp.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplateBuilder().build();
}
@GetMapping("/greeting")
public String greeting() {
return "greeting";
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.client.RestTemplate;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = "spring.cloud.kubernetes.discovery.all-namespaces=true")
@EnableKubernetesMockClient(crud = true)
class LoadBalancerAllNamespacesTests {
@Autowired
RestTemplate restTemplate;
@LocalServerPort
int randomServerPort;
static KubernetesClient client;
@BeforeAll
static void setup() {
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY,
client.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY,
"false");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
}
@Test
void testLoadBalancerDifferentNamespace() {
createTestData("service-b", "b");
String response = restTemplate.getForObject("http://service-b/greeting",
String.class);
Assertions.assertNotNull(response);
Assertions.assertEquals("greeting", response);
}
private void createTestData(String name, String namespace) {
client.services().inNamespace(namespace).createNew().withNewMetadata()
.withName(name).withNamespace(namespace).endMetadata()
.withSpec(new ServiceSpecBuilder().withPorts(new ServicePortBuilder()
.withProtocol("TCP").withPort(randomServerPort).build()).build())
.done();
client.endpoints().inNamespace(namespace).createNew().withNewMetadata()
.withName("service-a").withNamespace(namespace).endMetadata()
.addNewSubset().addNewAddress().withIp("localhost").endAddress()
.addNewPort().withName("http").withPort(randomServerPort).endPort()
.endSubset().done();
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.web.client.RestTemplate;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableKubernetesMockClient(crud = true)
class LoadBalancerTests {
@Autowired
RestTemplate restTemplate;
@LocalServerPort
int randomServerPort;
static KubernetesClient client;
@BeforeAll
static void setup() {
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY,
client.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY,
"false");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
}
@Test
void testLoadBalancerSameNamespace() {
createTestData("service-a", "test");
String response = restTemplate.getForObject("http://service-a/greeting",
String.class);
Assertions.assertNotNull(response);
Assertions.assertEquals("greeting", response);
}
@Test
void testLoadBalancerDifferentNamespace() {
createTestData("service-b", "b");
Assertions.assertThrows(IllegalStateException.class, () -> restTemplate
.getForObject("http://service-b/greeting", String.class));
}
private void createTestData(String name, String namespace) {
client.services().inNamespace(namespace).createNew().withNewMetadata()
.withName(name).endMetadata()
.withSpec(new ServiceSpecBuilder().withPorts(new ServicePortBuilder()
.withProtocol("TCP").withPort(randomServerPort).build()).build())
.done();
client.endpoints().inNamespace(namespace).createNew().withNewMetadata()
.withName("service-a").endMetadata().addNewSubset().addNewAddress()
.withIp("localhost").endAddress().addNewPort().withName("http")
.withPort(randomServerPort).endPort().endSubset().done();
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import java.util.HashMap;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.specto.hoverfly.junit.core.Hoverfly;
import io.specto.hoverfly.junit5.HoverflyExtension;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.client.RestTemplate;
import static io.specto.hoverfly.junit.core.SimulationSource.dsl;
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.service;
import static io.specto.hoverfly.junit.dsl.HttpBodyConverter.json;
import static io.specto.hoverfly.junit.dsl.ResponseCreators.success;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = { "spring.cloud.kubernetes.loadbalancer.mode=SERVICE",
"spring.cloud.kubernetes.loadbalancer.enabled=true" })
@ExtendWith(HoverflyExtension.class)
class LoadBalancerWithServiceTests {
private static final Logger LOGGER = LoggerFactory
.getLogger(LoadBalancerWithServiceTests.class);
@Autowired
RestTemplate restTemplate;
@Autowired
KubernetesClient client;
@BeforeAll
static void setup() {
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY,
"false");
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test");
}
@Test
void testLoadBalancerInServiceMode(Hoverfly hoverfly) {
LOGGER.info("Master URL: {}", client.getConfiguration().getMasterUrl());
hoverfly.simulate(
dsl(service("http://service-a.test.svc.cluster.local:8080")
.get("/greeting").willReturn(success().body("greeting"))),
dsl(service(client.getConfiguration().getMasterUrl().replace("/", "")
.replace("https:", ""))
.get("/api/v1/namespaces/test/services/service-a")
.willReturn(success().body(
json(buildService("service-a", 8080, "test"))))));
String response = restTemplate.getForObject("http://service-a/greeting",
String.class);
Assertions.assertNotNull(response);
Assertions.assertEquals("greeting", response);
}
private Service buildService(String name, int port, String namespace) {
return new ServiceBuilder().withNewMetadata().withName(name)
.withNamespace(namespace).withLabels(new HashMap<>())
.withAnnotations(new HashMap<>()).endMetadata().withNewSpec().addNewPort()
.withPort(port).endPort().endSpec().build();
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
@@ -23,6 +23,7 @@
<arquillian-cube.version>1.18.2</arquillian-cube.version>
<arquillian.version>1.4.0.Final</arquillian.version>
<rest-assured.version>3.2.0</rest-assured.version>
<okhttptests.version>3.12.12</okhttptests.version>
<!--
The port on localhost where the application will listen to from outside the cluster
@@ -125,6 +126,16 @@
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttptests.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttptests.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -133,7 +144,6 @@
<module>simple-configmap</module>
<!-- <module>istio</module>-->
<module>discovery</module>
</modules>
<module>load-balancer</module>
</modules>
</project>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Cloud Kubernetes :: Integration Tests :: Simple Configmap</name>
@@ -31,6 +31,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -58,6 +66,7 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Cloud Kubernetes :: Integration Tests :: Simple Core</name>
@@ -31,6 +31,14 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-core</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-kubernetes-leader</artifactId>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-loadbalancer</artifactId>
<name>Spring Cloud Kubernetes :: Load Balancer</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Kubernetes load balancer auto-configuration.
*
* @author Piotr Minkowski
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(KubernetesLoadBalancerProperties.class)
@ConditionalOnProperty(value = "spring.cloud.kubernetes.loadbalancer.enabled",
matchIfMissing = true)
@LoadBalancerClients(
defaultConfiguration = KubernetesLoadBalancerClientConfiguration.class)
public class KubernetesLoadBalancerAutoConfiguration {
@Bean
KubernetesServiceInstanceMapper mapper(KubernetesLoadBalancerProperties properties,
KubernetesDiscoveryProperties discoveryProperties) {
return new KubernetesServiceInstanceMapper(properties, discoveryProperties);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* Kubernetes load balancer client configuration.
*
* @author Piotr Minkowski
*/
public class KubernetesLoadBalancerClientConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.kubernetes.loadbalancer.mode",
havingValue = "SERVICE")
KubernetesServicesListSupplier kubernetesServicesListSupplier(Environment environment,
KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper,
KubernetesDiscoveryProperties discoveryProperties) {
return new KubernetesServicesListSupplier(environment, kubernetesClient, mapper,
discoveryProperties);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
/**
* Kubernetes load balancer mode enum.
*
* @author Piotr Minkowski
*/
public enum KubernetesLoadBalancerMode {
/**
* using pod ip and port.
*/
POD,
/**
* using kubernetes service name and port.
*/
SERVICE
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Kubernetes load balancer client properties.
*
* @author Piotr Minkowski
*/
@ConfigurationProperties(prefix = "spring.cloud.kubernetes.loadbalancer")
public class KubernetesLoadBalancerProperties {
/**
* Load balancer enabled,default true.
*/
private Boolean enabled = true;
/**
* {@link KubernetesLoadBalancerMode} setting load balancer server list with ip of pod
* or service name. default value is POD.
*/
private KubernetesLoadBalancerMode mode = KubernetesLoadBalancerMode.POD;
/**
* cluster domain.
*/
private String clusterDomain = "cluster.local";
/**
* service port name.
*/
private String portName = "http";
/**
* Get cluster domain.
* @return the cluster domain
*/
public String getClusterDomain() {
return clusterDomain;
}
/**
* Sets cluster domain.
* @param clusterDomain the cluster domain
*/
public void setClusterDomain(String clusterDomain) {
this.clusterDomain = clusterDomain;
}
/**
* Gets mode.
* @return the mode
*/
public KubernetesLoadBalancerMode getMode() {
return mode;
}
/**
* Sets mode.
* @param mode the mode
*/
public void setMode(KubernetesLoadBalancerMode mode) {
this.mode = mode;
}
/**
* Gets enabled.
* @return the enabled
*/
public Boolean getEnabled() {
return enabled;
}
/**
* Sets enabled.
* @param enabled the enabled
*/
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
/**
* Gets portName.
* @return portName port name
*/
public String getPortName() {
return portName;
}
/**
* Sets portName.
* @param portName port name
*/
public void setPortName(String portName) {
this.portName = portName;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.client.utils.Utils;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance;
/**
* Class for mapping Kubernetes Service object into {@link KubernetesServiceInstance}.
*
* @author Piotr Minkowski
*/
public class KubernetesServiceInstanceMapper {
private final KubernetesLoadBalancerProperties properties;
private final KubernetesDiscoveryProperties discoveryProperties;
KubernetesServiceInstanceMapper(KubernetesLoadBalancerProperties properties,
KubernetesDiscoveryProperties discoveryProperties) {
this.properties = properties;
this.discoveryProperties = discoveryProperties;
}
public KubernetesServiceInstance map(Service service) {
final ObjectMeta meta = service.getMetadata();
final List<ServicePort> ports = service.getSpec().getPorts();
ServicePort port = null;
if (ports.size() == 1) {
port = ports.get(0);
}
else if (ports.size() > 1
&& Utils.isNotNullOrEmpty(this.properties.getPortName())) {
Optional<ServicePort> optPort = ports.stream()
.filter(it -> properties.getPortName().endsWith(it.getName()))
.findAny();
if (optPort.isPresent()) {
port = optPort.get();
}
}
if (port == null) {
return null;
}
final String host = createHost(service);
final boolean secure = isSecure(service, port);
return new KubernetesServiceInstance(meta.getUid(), meta.getName(), host,
port.getPort(), getServiceMetadata(service), secure);
}
private Map<String, String> getServiceMetadata(Service service) {
final Map<String, String> serviceMetadata = new HashMap<>();
KubernetesDiscoveryProperties.Metadata metadataProps = this.discoveryProperties
.getMetadata();
if (metadataProps.isAddLabels()) {
Map<String, String> labelMetadata = getMapWithPrefixedKeys(
service.getMetadata().getLabels(), metadataProps.getLabelsPrefix());
serviceMetadata.putAll(labelMetadata);
}
if (metadataProps.isAddAnnotations()) {
Map<String, String> annotationMetadata = getMapWithPrefixedKeys(
service.getMetadata().getAnnotations(),
metadataProps.getAnnotationsPrefix());
serviceMetadata.putAll(annotationMetadata);
}
return serviceMetadata;
}
private Map<String, String> getMapWithPrefixedKeys(Map<String, String> map,
String prefix) {
if (map == null) {
return new HashMap<>();
}
if (!org.springframework.util.StringUtils.hasText(prefix)) {
return map;
}
final Map<String, String> result = new HashMap<>();
map.forEach((k, v) -> result.put(prefix + k, v));
return result;
}
private boolean isSecure(Service service, ServicePort port) {
if (service.getMetadata().getLabels() != null) {
final String securedLabelValue = service.getMetadata().getLabels()
.getOrDefault("secured", "false");
if (securedLabelValue.equals("true")) {
return true;
}
}
if (service.getMetadata().getAnnotations() != null) {
final String securedAnnotationValue = service.getMetadata().getAnnotations()
.getOrDefault("secured", "false");
if (securedAnnotationValue.equals("true")) {
return true;
}
}
return (port.getName() != null && port.getName().endsWith("https"))
|| port.getPort().toString().endsWith("443");
}
private String createHost(Service service) {
return String.format("%s.%s.svc.%s", service.getMetadata().getName(),
StringUtils.isNotBlank(service.getMetadata().getNamespace())
? service.getMetadata().getNamespace() : "default",
properties.getClusterDomain());
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import java.util.ArrayList;
import java.util.List;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.apache.commons.lang.StringUtils;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.env.Environment;
/**
* Implementation of {@link ServiceInstanceListSupplier} for load balancer in SERVICE
* mode.
*
* @author Piotr Minkowski
*/
public class KubernetesServicesListSupplier implements ServiceInstanceListSupplier {
private final Environment environment;
private final KubernetesClient kubernetesClient;
private final KubernetesDiscoveryProperties discoveryProperties;
private final KubernetesServiceInstanceMapper mapper;
KubernetesServicesListSupplier(Environment environment,
KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper,
KubernetesDiscoveryProperties discoveryProperties) {
this.environment = environment;
this.kubernetesClient = kubernetesClient;
this.discoveryProperties = discoveryProperties;
this.mapper = mapper;
}
@Override
public String getServiceId() {
return environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
}
@Override
public Flux<List<ServiceInstance>> get() {
List<ServiceInstance> result = new ArrayList<>();
if (discoveryProperties.isAllNamespaces()) {
List<Service> services = this.kubernetesClient.services().inAnyNamespace()
.withField("metadata.name", this.getServiceId()).list().getItems();
services.forEach(service -> result.add(mapper.map(service)));
}
else {
Service service = StringUtils.isNotBlank(this.kubernetesClient.getNamespace())
? this.kubernetesClient.services()
.inNamespace(this.kubernetesClient.getNamespace())
.withName(this.getServiceId()).get()
: this.kubernetesClient.services().withName(this.getServiceId())
.get();
if (service != null) {
result.add(mapper.map(service));
}
}
return Flux.defer(() -> Flux.just(result));
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.loadbalancer.KubernetesLoadBalancerAutoConfiguration

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance;
class KubernetesServiceInstanceMapperTests {
@Test
public void testMapperSimple() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
Service service = buildService("test", "abc", 8080, null, new HashMap<>());
KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper(
properties, discoveryProperties).map(service);
Assertions.assertNotNull(instance);
Assertions.assertEquals("test", instance.getServiceId());
Assertions.assertEquals("abc", instance.getInstanceId());
}
@Test
void testMapperMultiplePorts() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
properties.setPortName("http");
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
List<ServicePort> ports = new ArrayList<>();
ports.add(new ServicePortBuilder().withPort(8080).withName("web").build());
ports.add(new ServicePortBuilder().withPort(9000).withName("http").build());
Service service = buildService("test", "abc", ports, new HashMap<>());
KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper(
properties, discoveryProperties).map(service);
Assertions.assertNotNull(instance);
Assertions.assertEquals("test", instance.getServiceId());
Assertions.assertEquals("abc", instance.getInstanceId());
Assertions.assertEquals(9000, instance.getPort());
}
@Test
void testMapperSecure() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
Service service = buildService("test", "abc", 443, null, new HashMap<>());
KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper(
properties, discoveryProperties).map(service);
Assertions.assertNotNull(instance);
Assertions.assertEquals("test", instance.getServiceId());
Assertions.assertEquals("abc", instance.getInstanceId());
Assertions.assertTrue(instance.isSecure());
}
@Test
void testMapperSecureNullLabelsAndAnnotations() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
List<ServicePort> ports = new ArrayList<>();
ports.add(new ServicePortBuilder().withPort(443).build());
Service service = buildService("test", "abc", ports, null, null);
KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper(
properties, discoveryProperties).map(service);
Assertions.assertNotNull(instance);
Assertions.assertEquals("test", instance.getServiceId());
Assertions.assertEquals("abc", instance.getInstanceId());
Assertions.assertTrue(instance.isSecure());
}
@Test
void testMapperSecureWithLabels() {
KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties();
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
HashMap<String, String> labels = new HashMap<>();
labels.put("secured", "true");
labels.put("label1", "123");
Service service = buildService("test", "abc", 8080, null, labels);
KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper(
properties, discoveryProperties).map(service);
Assertions.assertNotNull(instance);
Assertions.assertEquals("test", instance.getServiceId());
Assertions.assertEquals("abc", instance.getInstanceId());
Assertions.assertTrue(instance.isSecure());
Assertions.assertEquals(2, instance.getMetadata().keySet().size());
}
private Service buildService(String name, String uid, int port, String portName,
Map<String, String> labels) {
ServicePort servicePort = new ServicePortBuilder().withPort(port)
.withName(portName).build();
return buildService(name, uid, Collections.singletonList(servicePort), labels);
}
private Service buildService(String name, String uid, List<ServicePort> ports,
Map<String, String> labels, Map<String, String> annotations) {
return new ServiceBuilder().withNewMetadata().withName(name).withNewUid(uid)
.addToLabels(labels).withAnnotations(annotations).endMetadata()
.withNewSpec().addAllToPorts(ports).endSpec().build();
}
private Service buildService(String name, String uid, List<ServicePort> ports,
Map<String, String> labels) {
return buildService(name, uid, ports, labels, new HashMap<>(0));
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.loadbalancer;
import java.util.List;
import io.fabric8.kubernetes.api.model.DoneableService;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.ServiceResource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.discovery.KubernetesServiceInstance;
import org.springframework.core.env.Environment;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class KubernetesServiceListSupplierTests {
@Mock
Environment environment;
@Mock
KubernetesServiceInstanceMapper mapper;
@Mock
KubernetesClient client;
@Mock
MixedOperation<Service, ServiceList, DoneableService, ServiceResource<Service, DoneableService>> serviceOperation;
@Mock
NonNamespaceOperation<Service, ServiceList, DoneableService, ServiceResource<Service, DoneableService>> namespaceOperation;
@Mock
ServiceResource<Service, DoneableService> serviceResource;
@Mock
FilterWatchListMultiDeletable<Service, ServiceList, Boolean, Watch, Watcher<Service>> multiDeletable;
@Test
void testPositiveMatch() {
when(environment.getProperty("loadbalancer.client.name"))
.thenReturn("test-service");
when(mapper.map(any(Service.class)))
.thenReturn(new KubernetesServiceInstance("", "", "", 0, null, false));
when(this.client.getNamespace()).thenReturn("test");
when(this.client.services()).thenReturn(this.serviceOperation);
when(this.serviceOperation.inNamespace("test")).thenReturn(namespaceOperation);
when(this.namespaceOperation.withName("test-service"))
.thenReturn(this.serviceResource);
when(this.serviceResource.get()).thenReturn(buildService("test-service", 8080));
KubernetesServicesListSupplier supplier = new KubernetesServicesListSupplier(
environment, client, mapper, new KubernetesDiscoveryProperties());
List<ServiceInstance> instances = supplier.get().blockFirst();
assert instances != null;
Assertions.assertEquals(1, instances.size());
}
@Test
void testPositiveMatchAllNamespaces() {
when(environment.getProperty("loadbalancer.client.name"))
.thenReturn("test-service");
when(mapper.map(any(Service.class)))
.thenReturn(new KubernetesServiceInstance("", "", "", 0, null, false));
when(this.client.services()).thenReturn(this.serviceOperation);
when(this.serviceOperation.inAnyNamespace()).thenReturn(this.multiDeletable);
when(this.multiDeletable.withField("metadata.name", "test-service"))
.thenReturn(this.multiDeletable);
ServiceList serviceList = new ServiceList();
serviceList.getItems().add(buildService("test-service", 8080));
when(this.multiDeletable.list()).thenReturn(serviceList);
KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties();
discoveryProperties.setAllNamespaces(true);
KubernetesServicesListSupplier supplier = new KubernetesServicesListSupplier(
environment, client, mapper, discoveryProperties);
List<ServiceInstance> instances = supplier.get().blockFirst();
assert instances != null;
Assertions.assertEquals(1, instances.size());
}
private Service buildService(String name, int port) {
return new ServiceBuilder().withNewMetadata().withName(name).endMetadata()
.withNewSpec().addNewPort().withPort(port).endPort().endSpec().build();
}
}

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -51,6 +51,11 @@
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
<name>Spring Cloud Kubernetes :: Starter :: LoadBalancer</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -22,7 +22,7 @@
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>1.1.5.BUILD-SNAPSHOT</version>
<version>1.1.8.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>