Commit fe807d6c authored by Brian Clozel's avatar Brian Clozel

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
parent cb735588
/*
* 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);
}
}
...@@ -16,8 +16,6 @@ ...@@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.availability; 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.LivenessStateHealthIndicator;
import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
...@@ -44,22 +42,20 @@ import org.springframework.core.type.AnnotatedTypeMetadata; ...@@ -44,22 +42,20 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
* @since 2.3.0 * @since 2.3.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Conditional(ProbesCondition.class) @Conditional(AvailabilityProbesAutoConfiguration.ProbesCondition.class)
@AutoConfigureAfter(ApplicationAvailabilityAutoConfiguration.class) @AutoConfigureAfter({ AvailabilityHealthContributorAutoConfiguration.class,
ApplicationAvailabilityAutoConfiguration.class })
public class AvailabilityProbesAutoConfiguration { public class AvailabilityProbesAutoConfiguration {
@Bean @Bean
@ConditionalOnEnabledHealthIndicator("livenessState")
@ConditionalOnMissingBean @ConditionalOnMissingBean
public LivenessStateHealthIndicator livenessStateHealthIndicator(ApplicationAvailability applicationAvailability) { public LivenessStateHealthIndicator livenessStateProbeIndicator(ApplicationAvailability applicationAvailability) {
return new LivenessStateHealthIndicator(applicationAvailability); return new LivenessStateHealthIndicator(applicationAvailability);
} }
@Bean @Bean
@ConditionalOnEnabledHealthIndicator("readinessState")
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ReadinessStateHealthIndicator readinessStateHealthIndicator( public ReadinessStateHealthIndicator readinessStateProbeIndicator(ApplicationAvailability applicationAvailability) {
ApplicationAvailability applicationAvailability) {
return new ReadinessStateHealthIndicator(applicationAvailability); return new ReadinessStateHealthIndicator(applicationAvailability);
} }
...@@ -70,20 +66,27 @@ public class AvailabilityProbesAutoConfiguration { ...@@ -70,20 +66,27 @@ public class AvailabilityProbesAutoConfiguration {
/** /**
* {@link SpringBootCondition} to enable or disable probes. * {@link SpringBootCondition} to enable or disable probes.
* <p>
* Probes are enabled if the dedicated configuration property is enabled or if the
* Kubernetes cloud environment is detected/enforced.
*/ */
static class ProbesCondition extends SpringBootCondition { 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 @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment(); Environment environment = context.getEnvironment();
ConditionMessage.Builder message = ConditionMessage.forCondition("Health availability"); ConditionMessage.Builder message = ConditionMessage.forCondition("Probes availability");
String enabled = environment.getProperty(ENABLED_PROPERTY); ConditionOutcome outcome = onProperty(environment, message, ENABLED_PROPERTY);
if (enabled != null) { if (outcome != null) {
boolean match = !"false".equalsIgnoreCase(enabled); return outcome;
return new ConditionOutcome(match, }
message.because("'" + ENABLED_PROPERTY + "' set to '" + enabled + "'")); outcome = onProperty(environment, message, DEPRECATED_ENABLED_PROPERTY);
if (outcome != null) {
return outcome;
} }
if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) { if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) {
return ConditionOutcome.match(message.because("running on Kubernetes")); return ConditionOutcome.match(message.because("running on Kubernetes"));
...@@ -91,6 +94,16 @@ public class AvailabilityProbesAutoConfiguration { ...@@ -91,6 +94,16 @@ public class AvailabilityProbesAutoConfiguration {
return ConditionOutcome.noMatch(message.because("not running on a supported cloud platform")); 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;
}
} }
} }
...@@ -47,6 +47,12 @@ ...@@ -47,6 +47,12 @@
"sun.java.command" "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", "name": "management.endpoint.health.show-details",
"defaultValue": "never" "defaultValue": "never"
...@@ -168,6 +174,12 @@ ...@@ -168,6 +174,12 @@
"description": "Whether to enable LDAP health check.", "description": "Whether to enable LDAP health check.",
"defaultValue": true "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", "name": "management.health.mail.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
...@@ -196,7 +208,10 @@ ...@@ -196,7 +208,10 @@
"name": "management.health.probes.enabled", "name": "management.health.probes.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Whether to enable liveness and readiness probes.", "description": "Whether to enable liveness and readiness probes.",
"defaultValue": false "defaultValue": false,
"deprecation": {
"replacement": "management.endpoint.health.probes.enabled"
}
}, },
{ {
"name": "management.health.rabbit.enabled", "name": "management.health.rabbit.enabled",
...@@ -204,6 +219,12 @@ ...@@ -204,6 +219,12 @@
"description": "Whether to enable RabbitMQ health check.", "description": "Whether to enable RabbitMQ health check.",
"defaultValue": true "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", "name": "management.health.redis.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
......
...@@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ...@@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration,\ 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.availability.AvailabilityProbesAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\
......
/*
* 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));
}
}
...@@ -34,8 +34,9 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -34,8 +34,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class AvailabilityProbesAutoConfigurationTests { class AvailabilityProbesAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.of(ApplicationAvailabilityAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(ApplicationAvailabilityAutoConfiguration.class,
AvailabilityHealthContributorAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class));
@Test @Test
void probesWhenNotKubernetesAddsNoBeans() { void probesWhenNotKubernetesAddsNoBeans() {
...@@ -56,7 +57,7 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -56,7 +57,7 @@ class AvailabilityProbesAutoConfigurationTests {
@Test @Test
void probesWhenPropertyEnabledAddsBeans() { 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) .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.hasSingleBean(LivenessStateHealthIndicator.class) .hasSingleBean(LivenessStateHealthIndicator.class)
.hasSingleBean(ReadinessStateHealthIndicator.class) .hasSingleBean(ReadinessStateHealthIndicator.class)
...@@ -66,7 +67,8 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -66,7 +67,8 @@ class AvailabilityProbesAutoConfigurationTests {
@Test @Test
void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() { void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() {
this.contextRunner 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) .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.doesNotHaveBean(LivenessStateHealthIndicator.class) .doesNotHaveBean(LivenessStateHealthIndicator.class)
.doesNotHaveBean(ReadinessStateHealthIndicator.class) .doesNotHaveBean(ReadinessStateHealthIndicator.class)
......
...@@ -712,6 +712,20 @@ The following `HealthIndicators` are auto-configured by Spring Boot when appropr ...@@ -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. 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 ==== Writing Custom HealthIndicators
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment