#562 Adding module spring-cloud-kubernetes-loadbalancer with implementation for service mode

This commit is contained in:
piomin
2020-06-26 14:23:03 +02:00
parent 285d13e924
commit 3732c0df4f
10 changed files with 568 additions and 0 deletions

View File

@@ -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>

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

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