diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc
index e97bf0fac..30851f6d9 100644
--- a/docs/src/main/asciidoc/_configprops.adoc
+++ b/docs/src/main/asciidoc/_configprops.adoc
@@ -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.
diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc
index 1a1cd3f98..b6361ecb3 100755
--- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc
@@ -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
diff --git a/spring-cloud-netflix-eureka-client/pom.xml b/spring-cloud-netflix-eureka-client/pom.xml
index 7a89d496c..e9f1337de 100644
--- a/spring-cloud-netflix-eureka-client/pom.xml
+++ b/spring-cloud-netflix-eureka-client/pom.xml
@@ -51,6 +51,11 @@
spring-cloud-config-server
true
+
+ org.springframework.cloud
+ spring-cloud-loadbalancer
+ true
+
com.netflix.eureka
eureka-client
diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfiguration.java
new file mode 100644
index 000000000..36890dd65
--- /dev/null
+++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfiguration.java
@@ -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;
+ }
+ }
+
+}
diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerProperties.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerProperties.java
new file mode 100644
index 000000000..be5355900
--- /dev/null
+++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerProperties.java
@@ -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;
+ }
+
+}
diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/LoadBalancerEurekaAutoConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/LoadBalancerEurekaAutoConfiguration.java
new file mode 100644
index 000000000..6f9473568
--- /dev/null
+++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/loadbalancer/LoadBalancerEurekaAutoConfiguration.java
@@ -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();
+ }
+
+}
diff --git a/spring-cloud-netflix-eureka-client/src/main/resources/META-INF/spring.factories b/spring-cloud-netflix-eureka-client/src/main/resources/META-INF/spring.factories
index 09ccf355e..12659725c 100644
--- a/spring-cloud-netflix-eureka-client/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-netflix-eureka-client/src/main/resources/META-INF/spring.factories
@@ -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
diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfigurationTests.java
new file mode 100644
index 000000000..1eeff8827
--- /dev/null
+++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/loadbalancer/EurekaLoadBalancerClientConfigurationTests.java
@@ -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");
+ }
+
+}