From fe807d6c0ba9793e8447a8de42b7bfed12251be2 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 21 Jul 2020 16:02:35 +0200 Subject: [PATCH] Improve liveness/readiness health config Prior to this commit, the application availability infrastructure would mix the `AvailabilityState`, the `HealthIndicator` and the `HealthGroup` concepts and would not align with the rest. This commit auto-configures the livenessState and readinessState health indicators with the relevant configuration properties. Unlike other indicators, they are not enabled by default but might be in future versions. This also moves the `management.health.probes.enabled` property to `management.endpoint.health.probes.enabled` since "probes" here is not a health indicator but rather a configuration flag for the health endpoint. Finally, the probes auto-configuration is refined to automatically add liveness and readiness indicators for the probes group if they're not already present. Closes gh-22107 --- ...ityHealthContributorAutoConfiguration.java | 58 +++++++++++++++++ .../AvailabilityProbesAutoConfiguration.java | 45 ++++++++----- ...itional-spring-configuration-metadata.json | 23 ++++++- .../main/resources/META-INF/spring.factories | 1 + ...althContributorAutoConfigurationTests.java | 63 +++++++++++++++++++ ...ilabilityProbesAutoConfigurationTests.java | 10 +-- .../asciidoc/production-ready-features.adoc | 14 +++++ 7 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfiguration.java new file mode 100644 index 0000000000..1eef2335ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-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.boot.actuate.autoconfigure.availability; + +import org.springframework.boot.actuate.availability.AvailabilityStateHealthIndicator; +import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator; +import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.availability.ApplicationAvailability; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for + * {@link AvailabilityStateHealthIndicator}. + * vailabilityHealthContributorAutoConfigurationTests + * + * @author Brian Clozel + * @since 2.3.2 + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(ApplicationAvailabilityAutoConfiguration.class) +public class AvailabilityHealthContributorAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.health.livenessstate", name = "enabled", havingValue = "true") + public LivenessStateHealthIndicator livenessStateHealthIndicator(ApplicationAvailability applicationAvailability) { + return new LivenessStateHealthIndicator(applicationAvailability); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = "management.health.readinessstate", name = "enabled", havingValue = "true") + public ReadinessStateHealthIndicator readinessStateHealthIndicator( + ApplicationAvailability applicationAvailability) { + return new ReadinessStateHealthIndicator(applicationAvailability); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java index aea64fd36e..2863b3c1b3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.availability; -import org.springframework.boot.actuate.autoconfigure.availability.AvailabilityProbesAutoConfiguration.ProbesCondition; -import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator; import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -44,22 +42,20 @@ import org.springframework.core.type.AnnotatedTypeMetadata; * @since 2.3.0 */ @Configuration(proxyBeanMethods = false) -@Conditional(ProbesCondition.class) -@AutoConfigureAfter(ApplicationAvailabilityAutoConfiguration.class) +@Conditional(AvailabilityProbesAutoConfiguration.ProbesCondition.class) +@AutoConfigureAfter({ AvailabilityHealthContributorAutoConfiguration.class, + ApplicationAvailabilityAutoConfiguration.class }) public class AvailabilityProbesAutoConfiguration { @Bean - @ConditionalOnEnabledHealthIndicator("livenessState") @ConditionalOnMissingBean - public LivenessStateHealthIndicator livenessStateHealthIndicator(ApplicationAvailability applicationAvailability) { + public LivenessStateHealthIndicator livenessStateProbeIndicator(ApplicationAvailability applicationAvailability) { return new LivenessStateHealthIndicator(applicationAvailability); } @Bean - @ConditionalOnEnabledHealthIndicator("readinessState") @ConditionalOnMissingBean - public ReadinessStateHealthIndicator readinessStateHealthIndicator( - ApplicationAvailability applicationAvailability) { + public ReadinessStateHealthIndicator readinessStateProbeIndicator(ApplicationAvailability applicationAvailability) { return new ReadinessStateHealthIndicator(applicationAvailability); } @@ -70,20 +66,27 @@ public class AvailabilityProbesAutoConfiguration { /** * {@link SpringBootCondition} to enable or disable probes. + *

+ * Probes are enabled if the dedicated configuration property is enabled or if the + * Kubernetes cloud environment is detected/enforced. */ static class ProbesCondition extends SpringBootCondition { - private static final String ENABLED_PROPERTY = "management.health.probes.enabled"; + private static final String ENABLED_PROPERTY = "management.endpoint.health.probes.enabled"; + + private static final String DEPRECATED_ENABLED_PROPERTY = "management.health.probes.enabled"; @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); - ConditionMessage.Builder message = ConditionMessage.forCondition("Health availability"); - String enabled = environment.getProperty(ENABLED_PROPERTY); - if (enabled != null) { - boolean match = !"false".equalsIgnoreCase(enabled); - return new ConditionOutcome(match, - message.because("'" + ENABLED_PROPERTY + "' set to '" + enabled + "'")); + ConditionMessage.Builder message = ConditionMessage.forCondition("Probes availability"); + ConditionOutcome outcome = onProperty(environment, message, ENABLED_PROPERTY); + if (outcome != null) { + return outcome; + } + outcome = onProperty(environment, message, DEPRECATED_ENABLED_PROPERTY); + if (outcome != null) { + return outcome; } if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) { return ConditionOutcome.match(message.because("running on Kubernetes")); @@ -91,6 +94,16 @@ public class AvailabilityProbesAutoConfiguration { return ConditionOutcome.noMatch(message.because("not running on a supported cloud platform")); } + private ConditionOutcome onProperty(Environment environment, ConditionMessage.Builder message, + String propertyName) { + String enabled = environment.getProperty(propertyName); + if (enabled != null) { + boolean match = !"false".equalsIgnoreCase(enabled); + return new ConditionOutcome(match, message.because("'" + propertyName + "' set to '" + enabled + "'")); + } + return null; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 6d8d00e425..5f7c1faae2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -47,6 +47,12 @@ "sun.java.command" ] }, + { + "name": "management.endpoint.health.probes.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable liveness and readiness probes.", + "defaultValue": false + }, { "name": "management.endpoint.health.show-details", "defaultValue": "never" @@ -168,6 +174,12 @@ "description": "Whether to enable LDAP health check.", "defaultValue": true }, + { + "name": "management.health.livenessstate.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable liveness state health check.", + "defaultValue": false + }, { "name": "management.health.mail.enabled", "type": "java.lang.Boolean", @@ -196,7 +208,10 @@ "name": "management.health.probes.enabled", "type": "java.lang.Boolean", "description": "Whether to enable liveness and readiness probes.", - "defaultValue": false + "defaultValue": false, + "deprecation": { + "replacement": "management.endpoint.health.probes.enabled" + } }, { "name": "management.health.rabbit.enabled", @@ -204,6 +219,12 @@ "description": "Whether to enable RabbitMQ health check.", "defaultValue": true }, + { + "name": "management.health.readynessstate.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable readiness state health check.", + "defaultValue": false + }, { "name": "management.health.redis.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 5327c6df9b..d9832a774b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.availability.AvailabilityProbesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfigurationTests.java new file mode 100644 index 0000000000..9ff3b57753 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityHealthContributorAutoConfigurationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-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.boot.actuate.autoconfigure.availability; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator; +import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; +import org.springframework.boot.availability.ApplicationAvailability; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AvailabilityHealthContributorAutoConfiguration} + * + * @author Brian Clozel + */ +class AvailabilityHealthContributorAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations + .of(ApplicationAvailabilityAutoConfiguration.class, AvailabilityHealthContributorAutoConfiguration.class)); + + @Test + void probesWhenNotKubernetesAddsNoBeans() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) + .doesNotHaveBean(LivenessStateHealthIndicator.class) + .doesNotHaveBean(ReadinessStateHealthIndicator.class)); + } + + @Test + void livenessIndicatorWhenPropertyEnabledAddsBeans() { + this.contextRunner.withPropertyValues("management.health.livenessState.enabled=true") + .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) + .hasSingleBean(LivenessStateHealthIndicator.class) + .doesNotHaveBean(ReadinessStateHealthIndicator.class)); + } + + @Test + void readinessIndicatorWhenPropertyEnabledAddsBeans() { + this.contextRunner.withPropertyValues("management.health.readinessState.enabled=true") + .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) + .hasSingleBean(ReadinessStateHealthIndicator.class) + .doesNotHaveBean(LivenessStateHealthIndicator.class)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java index 59e78bb105..f44b8389d3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesAutoConfigurationTests.java @@ -34,8 +34,9 @@ import static org.assertj.core.api.Assertions.assertThat; */ class AvailabilityProbesAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations - .of(ApplicationAvailabilityAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class)); + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ApplicationAvailabilityAutoConfiguration.class, + AvailabilityHealthContributorAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class)); @Test void probesWhenNotKubernetesAddsNoBeans() { @@ -56,7 +57,7 @@ class AvailabilityProbesAutoConfigurationTests { @Test void probesWhenPropertyEnabledAddsBeans() { - this.contextRunner.withPropertyValues("management.health.probes.enabled=true") + this.contextRunner.withPropertyValues("management.endpoint.health.probes.enabled=true") .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) .hasSingleBean(LivenessStateHealthIndicator.class) .hasSingleBean(ReadinessStateHealthIndicator.class) @@ -66,7 +67,8 @@ class AvailabilityProbesAutoConfigurationTests { @Test void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() { this.contextRunner - .withPropertyValues("spring.main.cloud-platform=kubernetes", "management.health.probes.enabled=false") + .withPropertyValues("spring.main.cloud-platform=kubernetes", + "management.endpoint.health.probes.enabled=false") .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) .doesNotHaveBean(LivenessStateHealthIndicator.class) .doesNotHaveBean(ReadinessStateHealthIndicator.class) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc index 9a8b398baa..b2cf750092 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc @@ -712,6 +712,20 @@ The following `HealthIndicators` are auto-configured by Spring Boot when appropr TIP: You can disable them all by setting the configprop:management.health.defaults.enabled[] property. +Additional `HealthIndicators` are available but not enabled by default; +developers can use their configuration property to activate them: + +[cols="4,6"] +|=== +| Name | Description + +| {spring-boot-actuator-module-code}/availability/LivenessStateHealthIndicator.java[`LivenessStateHealthIndicator`] +| Checks the liveness state of the application. + +| {spring-boot-actuator-module-code}/availability/ReadinessStateHealthIndicator.java[`ReadinessStateHealthIndicator`] +| Checks the readiness state of the application. +|=== + ==== Writing Custom HealthIndicators