From d639552fb8629d6e4dfbeab2353517266d68ccf4 Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Wed, 10 Nov 2021 17:45:30 +0100 Subject: [PATCH] Support setting custom port for LB health-checks. (#1039) --- .../main/asciidoc/spring-cloud-commons.adoc | 2 +- .../loadbalancer/LoadBalancerProperties.java | 19 +++++++++++++++ ...ealthCheckServiceInstanceListSupplier.java | 13 ++++++++++- ...CheckServiceInstanceListSupplierTests.java | 23 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 10e6cd68..3927cae0 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -979,7 +979,7 @@ TIP: The `HealthCheckServiceInstanceListSupplier` relies on having updated insta `HealthCheckServiceInstanceListSupplier` uses properties prefixed with `spring.cloud.loadbalancer.health-check`. You can set the `initialDelay` and `interval` for the scheduler. You can set the default path for the healthcheck URL by setting -the value of the `spring.cloud.loadbalancer.health-check.path.default` property. You can also set a specific value for any given service by setting the value of the `spring.cloud.loadbalancer.health-check.path.[SERVICE_ID]` property, substituting `[SERVICE_ID]` with the correct ID of your service. If the `[SERVICE_ID]` is not specified, `/actuator/health` is used by default. If the `[SERVICE_ID]` is set to `null` or empty as a value, then the health check will not be executed. +the value of the `spring.cloud.loadbalancer.health-check.path.default` property. You can also set a specific value for any given service by setting the value of the `spring.cloud.loadbalancer.health-check.path.[SERVICE_ID]` property, substituting `[SERVICE_ID]` with the correct ID of your service. If the `[SERVICE_ID]` is not specified, `/actuator/health` is used by default. If the `[SERVICE_ID]` is set to `null` or empty as a value, then the health check will not be executed. You can also set a custom port for health-check requests by setting the value of `spring.cloud.loadbalancer.health-check.port`. If none is set, the port under which the requested service is available at the service instance. TIP: If you rely on the default path (`/actuator/health`), make sure you add `spring-boot-starter-actuator` to your collaborator's dependencies, unless you are planning to add such an endpoint on your own. diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index aa2d6bf6..388f99b1 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -186,8 +186,19 @@ public class LoadBalancerProperties { */ private Duration refetchInstancesInterval = Duration.ofSeconds(25); + /** + * Path at which the health-check request should be made. Can be set up per + * serviceId. A default value can be set up as well. If + * none is set up, /actuator/health will be used. + */ private Map path = new LinkedCaseInsensitiveMap<>(); + /** + * Path at which the health-check request should be made. If none is set, the port + * under which the requested service is available at the service instance. + */ + private Integer port; + /** * Indicates whether the instances should be refetched by the * HealthCheckServiceInstanceListSupplier. This can be used if the @@ -251,6 +262,14 @@ public class LoadBalancerProperties { this.interval = interval; } + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + } public static class Retry { diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java index 61182cef..18792431 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplier.java @@ -30,6 +30,7 @@ import reactor.retry.Repeat; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; @@ -156,7 +157,7 @@ public class HealthCheckServiceInstanceListSupplier extends DelegatingServiceIns return Mono.just(true); } String healthCheckPath = healthCheckPropertyValue != null ? healthCheckPropertyValue : defaultHealthCheckPath; - return aliveFunction.apply(serviceInstance, healthCheckPath); + return aliveFunction.apply(updatedServiceInstance(serviceInstance), healthCheckPath); } @Override @@ -167,4 +168,14 @@ public class HealthCheckServiceInstanceListSupplier extends DelegatingServiceIns } } + private ServiceInstance updatedServiceInstance(ServiceInstance serviceInstance) { + Integer healthCheckPort = healthCheck.getPort(); + if (serviceInstance instanceof DefaultServiceInstance && healthCheckPort != null) { + return new DefaultServiceInstance(serviceInstance.getInstanceId(), serviceInstance.getServiceId(), + serviceInstance.getHost(), healthCheckPort, serviceInstance.isSecure(), + serviceInstance.getMetadata()); + } + return serviceInstance; + } + } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java index 6814a54a..c33ece5b 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java @@ -41,6 +41,7 @@ import org.springframework.boot.web.server.LocalServerPort; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.GetMapping; @@ -50,6 +51,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -579,6 +581,27 @@ class HealthCheckServiceInstanceListSupplierTests { assertThat(alive).isTrue(); } + @Test + void shouldCheckUseProvidedPortForHealthCheckRequest() { + Throwable exception = catchThrowable(() -> { + String serviceId = "ignored-service"; + healthCheck.setPort(8888); + LoadBalancerProperties properties = new LoadBalancerProperties(); + properties.setHealthCheck(healthCheck); + LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class); + when(loadBalancerClientFactory.getProperties(serviceId)).thenReturn(properties); + ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1", serviceId, "127.0.0.1", + port, false); + listSupplier = new HealthCheckServiceInstanceListSupplier( + ServiceInstanceListSuppliers.from(serviceId, serviceInstance), loadBalancerClientFactory, + healthCheckFunction(webClient)); + + listSupplier.isAlive(serviceInstance).block(); + }); + + assertThat(exception).hasMessageContaining("Connection refused: /127.0.0.1:888"); + } + @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration @RestController