Enable liveness and readiness by default

Closes gh-22825
This commit is contained in:
Stéphane Nicoll 2025-09-15 10:46:35 +02:00
parent 8f0d87a964
commit 83ad15ba3b
6 changed files with 18 additions and 80 deletions

View File

@ -672,7 +672,7 @@ TIP: The `ssl` javadoc:org.springframework.boot.actuate.health.HealthIndicator[]
If an SSL certificate will be invalid within the time span defined by this threshold, the javadoc:org.springframework.boot.actuate.health.HealthIndicator[] will warn you but it will still return HTTP 200 to not disrupt the application. If an SSL certificate will be invalid within the time span defined by this threshold, the javadoc:org.springframework.boot.actuate.health.HealthIndicator[] will warn you but it will still return HTTP 200 to not disrupt the application.
You can use this threshold to give yourself enough lead time to rotate the soon to be expired certificate. You can use this threshold to give yourself enough lead time to rotate the soon to be expired certificate.
Additional javadoc:org.springframework.boot.actuate.health.HealthIndicator[] beans are available but are not enabled by default: Additional javadoc:org.springframework.boot.actuate.health.HealthIndicator[] beans are enabled by default:
[cols="3,4,6"] [cols="3,4,6"]
|=== |===
@ -687,6 +687,8 @@ Additional javadoc:org.springframework.boot.actuate.health.HealthIndicator[] bea
| Exposes the "`Readiness`" application availability state. | Exposes the "`Readiness`" application availability state.
|=== |===
These can be disabled by using the configprop:management.endpoint.health.probes.enabled[] configuration property.
[[actuator.endpoints.health.writing-custom-health-indicators]] [[actuator.endpoints.health.writing-custom-health-indicators]]
@ -952,8 +954,8 @@ readinessProbe:
NOTE: `<actuator-port>` should be set to the port that the actuator endpoints are available on. NOTE: `<actuator-port>` should be set to the port that the actuator endpoints are available on.
It could be the main web server port or a separate management port if the `"management.server.port"` property has been set. It could be the main web server port or a separate management port if the `"management.server.port"` property has been set.
These health groups are automatically enabled only if the application xref:how-to:deployment/cloud.adoc#howto.deployment.cloud.kubernetes[runs in a Kubernetes environment]. These health groups are automatically enabled.
You can enable them in any environment by using the configprop:management.endpoint.health.probes.enabled[] configuration property. You can disable them by using the configprop:management.endpoint.health.probes.enabled[] configuration property.
NOTE: If an application takes longer to start than the configured liveness period, Kubernetes mentions the `"startupProbe"` as a possible solution. NOTE: If an application takes longer to start than the configured liveness period, Kubernetes mentions the `"startupProbe"` as a possible solution.
Generally speaking, the `"startupProbe"` is not necessarily needed here, as the `"readinessProbe"` fails until all startup tasks are done. Generally speaking, the `"startupProbe"` is not necessarily needed here, as the `"readinessProbe"` fails until all startup tasks are done.

View File

@ -16,26 +16,18 @@
package org.springframework.boot.actuate.autoconfigure.availability; package org.springframework.boot.actuate.autoconfigure.availability;
import org.jspecify.annotations.Nullable;
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.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.availability.ApplicationAvailability; import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Health;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for availability probes. * {@link EnableAutoConfiguration Auto-configuration} for availability probes.
@ -47,7 +39,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
@AutoConfiguration(after = { AvailabilityHealthContributorAutoConfiguration.class, @AutoConfiguration(after = { AvailabilityHealthContributorAutoConfiguration.class,
ApplicationAvailabilityAutoConfiguration.class }) ApplicationAvailabilityAutoConfiguration.class })
@ConditionalOnClass(Health.class) @ConditionalOnClass(Health.class)
@Conditional(AvailabilityProbesAutoConfiguration.ProbesCondition.class) @ConditionalOnBooleanProperty(name = "management.endpoint.health.probes.enabled", matchIfMissing = true)
public final class AvailabilityProbesAutoConfiguration { public final class AvailabilityProbesAutoConfiguration {
@Bean @Bean
@ -68,49 +60,4 @@ public final class AvailabilityProbesAutoConfiguration {
return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment); return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment);
} }
/**
* {@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 {
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("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"));
}
if (CloudPlatform.getActive(environment) == CloudPlatform.CLOUD_FOUNDRY) {
return ConditionOutcome.match(message.because("running on Cloud Foundry"));
}
return ConditionOutcome.noMatch(message.because("not running on a supported cloud platform"));
}
private @Nullable 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;
}
}
} }

View File

@ -34,7 +34,7 @@
"name": "management.endpoint.health.probes.enabled", "name": "management.endpoint.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": true
}, },
{ {
"name": "management.endpoint.health.status.order", "name": "management.endpoint.health.status.order",
@ -137,6 +137,7 @@
"description": "Whether to enable liveness and readiness probes.", "description": "Whether to enable liveness and readiness probes.",
"defaultValue": false, "defaultValue": false,
"deprecation": { "deprecation": {
"level": "error",
"replacement": "management.endpoint.health.probes.enabled" "replacement": "management.endpoint.health.probes.enabled"
} }
}, },

View File

@ -41,18 +41,8 @@ class AvailabilityProbesAutoConfigurationTests {
AvailabilityHealthContributorAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class)); AvailabilityHealthContributorAutoConfiguration.class, AvailabilityProbesAutoConfiguration.class));
@Test @Test
void probesWhenNotKubernetesAddsNoBeans() { void probesWhenDefaultAddsBeans() {
this.contextRunner.run(this::doesNotHaveProbeBeans); this.contextRunner.run(this::hasProbesBeans);
}
@Test
void probesWhenKubernetesAddsBeans() {
this.contextRunner.withPropertyValues("spring.main.cloud-platform=kubernetes").run(this::hasProbesBeans);
}
@Test
void probesWhenCloudFoundryAddsBeans() {
this.contextRunner.withPropertyValues("spring.main.cloud-platform=cloud_foundry").run(this::hasProbesBeans);
} }
@Test @Test
@ -62,17 +52,14 @@ class AvailabilityProbesAutoConfigurationTests {
} }
@Test @Test
void probesWhenPropertyEnabledButNoHealthDependencyDoesNotAddBeans() { void probesWhenNoHealthDependencyDoesNotAddBeans() {
this.contextRunner.withPropertyValues("management.endpoint.health.probes.enabled=true") this.contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.boot.health"))
.withClassLoader(new FilteredClassLoader("org.springframework.boot.health"))
.run(this::doesNotHaveProbeBeans); .run(this::doesNotHaveProbeBeans);
} }
@Test @Test
void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() { void probesWhenPropertyDisabledAddsNotBeans() {
this.contextRunner this.contextRunner.withPropertyValues("management.endpoint.health.probes.enabled=false")
.withPropertyValues("spring.main.cloud-platform=kubernetes",
"management.endpoint.health.probes.enabled=false")
.run(this::doesNotHaveProbeBeans); .run(this::doesNotHaveProbeBeans);
} }

View File

@ -67,7 +67,8 @@ abstract class AbstractManagementPortAndPathSampleActuatorApplicationTests {
ResponseEntity<String> entity = new TestRestTemplate().withBasicAuth("user", "password") ResponseEntity<String> entity = new TestRestTemplate().withBasicAuth("user", "password")
.getForEntity("http://localhost:" + this.managementPort + "/admin/health", String.class); .getForEntity("http://localhost:" + this.managementPort + "/admin/health", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("{\"groups\":[\"comp\",\"live\",\"ready\"],\"status\":\"UP\"}"); assertThat(entity.getBody())
.isEqualTo("{\"groups\":[\"comp\",\"live\",\"liveness\",\"readiness\",\"ready\"],\"status\":\"UP\"}");
} }
@Test @Test

View File

@ -87,7 +87,7 @@ class SampleSecureWebFluxApplicationTests {
.header("Authorization", getBasicAuth()) .header("Authorization", getBasicAuth())
.exchange() .exchange()
.expectBody(String.class) .expectBody(String.class)
.isEqualTo("{\"status\":\"UP\"}"); .isEqualTo("{\"groups\":[\"liveness\",\"readiness\"],\"status\":\"UP\"}");
} }
private String getBasicAuth() { private String getBasicAuth() {