#562 Adding module spring-cloud-kubernetes-loadbalancer with implementation for service mode
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -96,6 +96,7 @@
|
||||
<module>spring-cloud-kubernetes-istio</module>
|
||||
<module>spring-cloud-kubernetes-integration-tests</module>
|
||||
<module>docs</module>
|
||||
<module>spring-cloud-kubernetes-loadbalancer</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
45
spring-cloud-kubernetes-loadbalancer/pom.xml
Normal file
45
spring-cloud-kubernetes-loadbalancer/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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>2.0.0-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>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Kubernetes load balancer client properties.
|
||||
*
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
public class KubernetesClientConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.kubernetes.loadbalancer.mode",
|
||||
havingValue = "SERVICE")
|
||||
KubernetesServicesListSupplier kubernetesServicesListSupplier(Environment environment,
|
||||
KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper) {
|
||||
return new KubernetesServicesListSupplier(environment, kubernetesClient, mapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.cloud.kubernetes.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
@Configuration
|
||||
@LoadBalancerClients(defaultConfiguration = KubernetesClientConfiguration.class)
|
||||
public class KubernetesLoadBalancerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
KubernetesLoadBalancerProperties properties() {
|
||||
return new KubernetesLoadBalancerProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
KubernetesServiceInstanceMapper mapper(KubernetesLoadBalancerProperties properties,
|
||||
KubernetesDiscoveryProperties discoveryProperties) {
|
||||
return new KubernetesServiceInstanceMapper(properties, discoveryProperties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
public enum KubernetesLoadBalancerMode {
|
||||
|
||||
/**
|
||||
* using pod ip and port.
|
||||
*/
|
||||
POD,
|
||||
/**
|
||||
* using kubernetes service name and port.
|
||||
*/
|
||||
SERVICE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.kubernetes.loadbalancer")
|
||||
public class KubernetesLoadBalancerProperties {
|
||||
|
||||
/**
|
||||
* Ribbon enabled,default true.
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* {@link KubernetesLoadBalancerMode} setting ribbon 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
public class KubernetesServiceInstance implements ServiceInstance {
|
||||
|
||||
private String serviceId;
|
||||
|
||||
private String instanceId;
|
||||
|
||||
private int port;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
private String host;
|
||||
|
||||
private URI uri;
|
||||
|
||||
private Map<String, String> metadata;
|
||||
|
||||
KubernetesServiceInstance(String serviceId, String instanceId, int port,
|
||||
boolean secure, String host, URI uri, Map<String, String> metadata) {
|
||||
this.serviceId = serviceId;
|
||||
this.instanceId = instanceId;
|
||||
this.port = port;
|
||||
this.secure = secure;
|
||||
this.host = host;
|
||||
this.uri = uri;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.net.URI;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
public class KubernetesServiceInstanceMapper {
|
||||
|
||||
private static final String DSL = "//";
|
||||
|
||||
private static final String COLON = ":";
|
||||
|
||||
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.getName(), meta.getUid(),
|
||||
port.getPort(), secure, host,
|
||||
createUri(secure ? "https" : "http", host, port.getPort()),
|
||||
getServiceMetadata(service));
|
||||
}
|
||||
|
||||
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) {
|
||||
final String securedLabelValue = service.getMetadata().getLabels()
|
||||
.getOrDefault("secured", "false");
|
||||
if (securedLabelValue.equals("true")) {
|
||||
return true;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* @author Piotr Minkowski
|
||||
*/
|
||||
public class KubernetesServicesListSupplier implements ServiceInstanceListSupplier {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
private KubernetesServiceInstanceMapper mapper;
|
||||
|
||||
KubernetesServicesListSupplier(Environment environment,
|
||||
KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper) {
|
||||
this.environment = environment;
|
||||
this.kubernetesClient = kubernetesClient;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<List<ServiceInstance>> get() {
|
||||
List<ServiceInstance> result = new ArrayList<>();
|
||||
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.just(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.kubernetes.loadbalancer.KubernetesLoadBalancerAutoConfiguration
|
||||
Reference in New Issue
Block a user