Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gh 3718 add zoned loadbalancer instrumentation #3720

Merged
merged 4 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
|management.metrics.binders.hystrix.enabled | true | Enables creation of OK Http Client factory beans.
|ribbon.eureka.enabled | true | Enables the use of Eureka with Ribbon.
|spring.cloud.circuitbreaker.hystrix.enabled | true | Enables auto-configuration of the Hystrix Spring Cloud CircuitBreaker API implementation.
|spring.cloud.loadbalancer.eureka.approximate-zone-from-hostname | false | Used to determine whether we should try to get the `zone` value from host name.
|spring.cloud.loadbalancer.ribbon.enabled | true | Causes `RibbonLoadBalancerClient` to be used by default.
|zuul.ribbon-isolation-strategy | |
|zuul.ribbon.eager-load.enabled | false | Enables eager loading of Ribbon clients on startup.
Expand Down
12 changes: 12 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-netflix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,18 @@ When a refresh occurs clients will be unregistered from the Eureka server and th
where all instance of a given service are not available. One way to eliminate this from happening is to disable
the ability to refresh Eureka clients. To do this set `eureka.client.refresh.enable=false`.

=== Using Eureka with Spring Cloud LoadBalancer

We offer support for the Spring Cloud LoadBalancer `ZonePreferenceServiceInstanceListSupplier`.
The `zone` value from the Eureka instance metadata (`eureka.instance.metadataMap.zone`) is used for setting the
value of `spring-clod-loadbalancer-zone` property that is used to filter service instances by zone.

If that is missing and if the `spring.cloud.loadbalancer.eureka.approximateZoneFromHostname` flag is set to `true`,
it can use the domain name from the server hostname as a proxy for the zone.

If there is no other source of zone data, then a guess is made, based on the client configuration (as opposed to the instance configuration).
We take `eureka.client.availabilityZones`, which is a map from region name to a list of zones, and pull out the first zone for the instance's own region (that is, the `eureka.client.region`, which defaults to "us-east-1", for compatibility with native Netflix).

[[spring-cloud-eureka-server]]
== Service Discovery: Eureka Server

Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-netflix-eureka-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>spring-cloud-config-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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.netflix.eureka.loadbalancer;

import javax.annotation.PostConstruct;

import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClientConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.netflix.ribbon.eureka.ZoneUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

/**
* A configuration for Spring Cloud LoadBalancer that retrieves client instance zone from
* Eureka and sets it as a property. Based on
* {@link EurekaLoadBalancerClientConfiguration}.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
* @see EurekaLoadBalancerClientConfiguration
*/
@Configuration
@ConditionalOnBean({ LoadBalancerProperties.class, EurekaLoadBalancerProperties.class })
public class EurekaLoadBalancerClientConfiguration {

private static final String LOADBALANCER_ZONE = "spring.cloud.loadbalancer.zone";

private static final Log LOG = LogFactory
.getLog(EurekaLoadBalancerClientConfiguration.class);

private final EurekaClientConfig clientConfig;

private final EurekaInstanceConfig eurekaConfig;

private final LoadBalancerProperties loadBalancerProperties;

private final EurekaLoadBalancerProperties eurekaLoadBalancerProperties;

public EurekaLoadBalancerClientConfiguration(
@Autowired(required = false) EurekaClientConfig clientConfig,
@Autowired(required = false) EurekaInstanceConfig eurekaInstanceConfig,
LoadBalancerProperties loadBalancerProperties,
EurekaLoadBalancerProperties eurekaLoadBalancerProperties) {
this.clientConfig = clientConfig;
this.eurekaConfig = eurekaInstanceConfig;
this.loadBalancerProperties = loadBalancerProperties;
this.eurekaLoadBalancerProperties = eurekaLoadBalancerProperties;
}

@PostConstruct
public void postprocess() {
if (!StringUtils.isEmpty(loadBalancerProperties.getZone())) {
return;
}
String zone = getZoneFromEureka();
if (!StringUtils.isEmpty(zone)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting the value of '" + LOADBALANCER_ZONE + "' to " + zone);
}
loadBalancerProperties.setZone(zone);
}
}

private String getZoneFromEureka() {
String zone;
boolean approximateZoneFromHostname = eurekaLoadBalancerProperties
.isApproximateZoneFromHostname();
if (approximateZoneFromHostname && eurekaConfig != null) {
return ZoneUtils.extractApproximateZone(this.eurekaConfig.getHostName(false));
}
else {
zone = eurekaConfig == null ? null
: eurekaConfig.getMetadataMap().get("zone");
if (StringUtils.isEmpty(zone)) {
String[] zones = clientConfig
.getAvailabilityZones(clientConfig.getRegion());
// Pick the first one from the regions we want to connect to
zone = zones != null && zones.length > 0 ? zones[0] : null;
}
return zone;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2012-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.netflix.eureka.loadbalancer;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* A {@link ConfigurationProperties} bean for the Eureka-specific instrumentation of
* Spring Cloud LoadBalancer.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
*/
@ConfigurationProperties("spring.cloud.loadbalancer.eureka")
public class EurekaLoadBalancerProperties {

/**
* Used to determine whether we should try to get the `zone` value from host name.
*/
private boolean approximateZoneFromHostname = false;

public boolean isApproximateZoneFromHostname() {
return approximateZoneFromHostname;
}

public void setApproximateZoneFromHostname(boolean approximateZoneFromHostname) {
this.approximateZoneFromHostname = approximateZoneFromHostname;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.netflix.eureka.loadbalancer;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfigurationRegistrar;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* An Autoconfiguration that loads default config for Spring Cloud LoadBalancer clients.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
* @see EurekaLoadBalancerClientConfiguration
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(EurekaLoadBalancerProperties.class)
@ConditionalOnClass(LoadBalancerClientConfigurationRegistrar.class)
@LoadBalancerClients(defaultConfiguration = EurekaLoadBalancerClientConfiguration.class)
public class LoadBalancerEurekaAutoConfiguration {

@Bean
@ConditionalOnMissingBean
EurekaLoadBalancerProperties eurekaLoadBalancerProperties() {
return new EurekaLoadBalancerProperties();
}

@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("spring.cloud.loadbalancer")
LoadBalancerProperties loadBalancerProperties() {
return new LoadBalancerProperties();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServi
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2015-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.netflix.eureka.loadbalancer;

import org.junit.jupiter.api.Test;

import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link EurekaLoadBalancerClientConfiguration}.
*
* @author Olga Maciaszek-Sharma
*/
class EurekaLoadBalancerClientConfigurationTests {

private EurekaClientConfigBean eurekaClientConfig = new EurekaClientConfigBean();

private EurekaInstanceConfigBean eurekaInstanceConfig = new EurekaInstanceConfigBean(
new InetUtils(new InetUtilsProperties()));

private LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties();

private EurekaLoadBalancerProperties eurekaLoadBalancerProperties = new EurekaLoadBalancerProperties();

private EurekaLoadBalancerClientConfiguration postprocessor = new EurekaLoadBalancerClientConfiguration(
eurekaClientConfig, eurekaInstanceConfig, loadBalancerProperties,
eurekaLoadBalancerProperties);

@Test
void shouldSetZoneFromInstanceMetadata() {
eurekaInstanceConfig.getMetadataMap().put("zone", "myZone");

postprocessor.postprocess();

assertThat(loadBalancerProperties.getZone()).isEqualTo("myZone");
}

@Test
public void shouldSetZoneToDefaultWhenNotSetInMetadata() {
postprocessor.postprocess();

assertThat(loadBalancerProperties.getZone()).isEqualTo("defaultZone");
}

@Test
public void shouldResolveApproximateZoneFromHost() {
eurekaInstanceConfig.setHostname("this.is.a.test.com");
eurekaLoadBalancerProperties.setApproximateZoneFromHostname(true);

postprocessor.postprocess();

assertThat(loadBalancerProperties.getZone()).isEqualTo("is.a.test.com");
}

}