Skip to content

Commit ffdf9a4

Browse files
committed
Polish Liveness and Readiness support
This commit moves the core Liveness and Readiness support to its own `availability` package. We've made this a core concept independent of Kubernetes. Spring Boot now produces `LivenessStateChanged` and `ReadinessStateChanged` events as part of the typical application lifecycle. Liveness and Readiness Probes (`HealthIndicator` components and health groups) are still configured only when deployed on Kubernetes. This commit also improves the documentation around Probes best practices and container lifecycle considerations. See gh-19593
1 parent 3edc1c3 commit ffdf9a4

File tree

30 files changed

+521
-354
lines changed

30 files changed

+521
-354
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/kubernetes/ProbesHealthContributorAutoConfiguration.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@
1717
package org.springframework.boot.actuate.autoconfigure.kubernetes;
1818

1919
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
20+
import org.springframework.boot.actuate.availability.LivenessProbeHealthIndicator;
21+
import org.springframework.boot.actuate.availability.ReadinessProbeHealthIndicator;
2022
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
21-
import org.springframework.boot.actuate.kubernetes.LivenessProbeHealthIndicator;
2223
import org.springframework.boot.actuate.kubernetes.ProbesHealthEndpointGroupsRegistrar;
23-
import org.springframework.boot.actuate.kubernetes.ReadinessProbeHealthIndicator;
2424
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2525
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
26+
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
27-
import org.springframework.boot.autoconfigure.kubernetes.ApplicationStateAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
2830
import org.springframework.boot.cloud.CloudPlatform;
29-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
3031
import org.springframework.context.annotation.Bean;
3132
import org.springframework.context.annotation.Configuration;
3233

@@ -39,21 +40,23 @@
3940
*/
4041
@Configuration(proxyBeanMethods = false)
4142
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
42-
@AutoConfigureAfter(ApplicationStateAutoConfiguration.class)
43+
@AutoConfigureAfter(ApplicationAvailabilityAutoConfiguration.class)
4344
public class ProbesHealthContributorAutoConfiguration {
4445

4546
@Bean
4647
@ConditionalOnEnabledHealthIndicator("livenessProbe")
48+
@ConditionalOnMissingBean
4749
public LivenessProbeHealthIndicator livenessProbeHealthIndicator(
48-
ApplicationStateProvider applicationStateProvider) {
49-
return new LivenessProbeHealthIndicator(applicationStateProvider);
50+
ApplicationAvailabilityProvider applicationAvailabilityProvider) {
51+
return new LivenessProbeHealthIndicator(applicationAvailabilityProvider);
5052
}
5153

5254
@Bean
5355
@ConditionalOnEnabledHealthIndicator("readinessProbe")
56+
@ConditionalOnMissingBean
5457
public ReadinessProbeHealthIndicator readinessProbeHealthIndicator(
55-
ApplicationStateProvider applicationStateProvider) {
56-
return new ReadinessProbeHealthIndicator(applicationStateProvider);
58+
ApplicationAvailabilityProvider applicationAvailabilityProvider) {
59+
return new ReadinessProbeHealthIndicator(applicationAvailabilityProvider);
5760
}
5861

5962
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.kubernetes;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.actuate.availability.LivenessProbeHealthIndicator;
22+
import org.springframework.boot.actuate.availability.ReadinessProbeHealthIndicator;
23+
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
24+
import org.springframework.boot.autoconfigure.AutoConfigurations;
25+
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
26+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
27+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* Tests fos {@link ProbesHealthContributorAutoConfiguration}.
33+
*
34+
* @author Brian Clozel
35+
*/
36+
class ProbesHealthContributorAutoConfigurationTests {
37+
38+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
39+
.of(ApplicationAvailabilityAutoConfiguration.class, ProbesHealthContributorAutoConfiguration.class));
40+
41+
@Test
42+
void probesNotConfiguredIfNotKubernetes() {
43+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)
44+
.doesNotHaveBean(LivenessProbeHealthIndicator.class)
45+
.doesNotHaveBean(ReadinessProbeHealthIndicator.class)
46+
.doesNotHaveBean(HealthEndpointGroupsRegistryCustomizer.class));
47+
}
48+
49+
@Test
50+
void probesConfiguredIfKubernetes() {
51+
this.contextRunner.withPropertyValues("spring.main.cloud-platform=kubernetes")
52+
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)
53+
.hasSingleBean(LivenessProbeHealthIndicator.class)
54+
.hasSingleBean(ReadinessProbeHealthIndicator.class)
55+
.hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class));
56+
}
57+
58+
}
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.kubernetes;
17+
package org.springframework.boot.actuate.availability;
1818

1919
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
2020
import org.springframework.boot.actuate.health.Health;
2121
import org.springframework.boot.actuate.health.HealthIndicator;
22-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
23-
import org.springframework.boot.kubernetes.LivenessState;
22+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
23+
import org.springframework.boot.availability.LivenessState;
2424

2525
/**
2626
* A {@link HealthIndicator} that checks the {@link LivenessState} of the application.
@@ -30,15 +30,15 @@
3030
*/
3131
public class LivenessProbeHealthIndicator extends AbstractHealthIndicator {
3232

33-
private final ApplicationStateProvider applicationStateProvider;
33+
private final ApplicationAvailabilityProvider applicationAvailabilityProvider;
3434

35-
public LivenessProbeHealthIndicator(ApplicationStateProvider applicationStateProvider) {
36-
this.applicationStateProvider = applicationStateProvider;
35+
public LivenessProbeHealthIndicator(ApplicationAvailabilityProvider applicationAvailabilityProvider) {
36+
this.applicationAvailabilityProvider = applicationAvailabilityProvider;
3737
}
3838

3939
@Override
4040
protected void doHealthCheck(Health.Builder builder) throws Exception {
41-
if (LivenessState.live().equals(this.applicationStateProvider.getLivenessState())) {
41+
if (LivenessState.live().equals(this.applicationAvailabilityProvider.getLivenessState())) {
4242
builder.up();
4343
}
4444
else {
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.kubernetes;
17+
package org.springframework.boot.actuate.availability;
1818

1919
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
2020
import org.springframework.boot.actuate.health.Health;
2121
import org.springframework.boot.actuate.health.HealthIndicator;
22-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
23-
import org.springframework.boot.kubernetes.ReadinessState;
22+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
23+
import org.springframework.boot.availability.ReadinessState;
2424

2525
/**
2626
* A {@link HealthIndicator} that checks the {@link ReadinessState} of the application.
@@ -30,15 +30,15 @@
3030
*/
3131
public class ReadinessProbeHealthIndicator extends AbstractHealthIndicator {
3232

33-
private final ApplicationStateProvider applicationStateProvider;
33+
private final ApplicationAvailabilityProvider applicationAvailabilityProvider;
3434

35-
public ReadinessProbeHealthIndicator(ApplicationStateProvider applicationStateProvider) {
36-
this.applicationStateProvider = applicationStateProvider;
35+
public ReadinessProbeHealthIndicator(ApplicationAvailabilityProvider applicationAvailabilityProvider) {
36+
this.applicationAvailabilityProvider = applicationAvailabilityProvider;
3737
}
3838

3939
@Override
4040
protected void doHealthCheck(Health.Builder builder) throws Exception {
41-
if (ReadinessState.ready().equals(this.applicationStateProvider.getReadinessState())) {
41+
if (ReadinessState.ready().equals(this.applicationAvailabilityProvider.getReadinessState())) {
4242
builder.up();
4343
}
4444
else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Actuator support for application availability concerns.
19+
*/
20+
package org.springframework.boot.actuate.availability;
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.kubernetes;
17+
package org.springframework.boot.actuate.availability;
1818

1919
import org.junit.jupiter.api.BeforeEach;
2020
import org.junit.jupiter.api.Test;
2121

2222
import org.springframework.boot.actuate.health.Status;
23-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
24-
import org.springframework.boot.kubernetes.LivenessState;
23+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
24+
import org.springframework.boot.availability.LivenessState;
2525

2626
import static org.assertj.core.api.Assertions.assertThat;
2727
import static org.mockito.BDDMockito.when;
@@ -34,13 +34,13 @@
3434
*/
3535
class LivenessProbeHealthIndicatorTests {
3636

37-
private ApplicationStateProvider stateProvider;
37+
private ApplicationAvailabilityProvider stateProvider;
3838

3939
private LivenessProbeHealthIndicator healthIndicator;
4040

4141
@BeforeEach
4242
void setUp() {
43-
this.stateProvider = mock(ApplicationStateProvider.class);
43+
this.stateProvider = mock(ApplicationAvailabilityProvider.class);
4444
this.healthIndicator = new LivenessProbeHealthIndicator(this.stateProvider);
4545
}
4646

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.kubernetes;
17+
package org.springframework.boot.actuate.availability;
1818

1919
import org.junit.jupiter.api.BeforeEach;
2020
import org.junit.jupiter.api.Test;
2121

2222
import org.springframework.boot.actuate.health.Status;
23-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
24-
import org.springframework.boot.kubernetes.ReadinessState;
23+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
24+
import org.springframework.boot.availability.ReadinessState;
2525

2626
import static org.assertj.core.api.Assertions.assertThat;
2727
import static org.mockito.BDDMockito.when;
@@ -34,13 +34,13 @@
3434
*/
3535
class ReadinessProbeHealthIndicatorTests {
3636

37-
private ApplicationStateProvider stateProvider;
37+
private ApplicationAvailabilityProvider stateProvider;
3838

3939
private ReadinessProbeHealthIndicator healthIndicator;
4040

4141
@BeforeEach
4242
void setUp() {
43-
this.stateProvider = mock(ApplicationStateProvider.class);
43+
this.stateProvider = mock(ApplicationAvailabilityProvider.class);
4444
this.healthIndicator = new ReadinessProbeHealthIndicator(this.stateProvider);
4545
}
4646

@@ -51,8 +51,8 @@ void readinessIsReady() {
5151
}
5252

5353
@Test
54-
void readinessIsBusy() {
55-
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.busy());
54+
void readinessIsUnready() {
55+
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.unready());
5656
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
5757
}
5858

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.autoconfigure.kubernetes;
17+
package org.springframework.boot.autoconfigure.availability;
1818

19-
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
20-
import org.springframework.boot.cloud.CloudPlatform;
21-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
22-
import org.springframework.boot.kubernetes.SpringApplicationEventListener;
19+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
2320
import org.springframework.context.annotation.Bean;
2421
import org.springframework.context.annotation.Configuration;
2522

2623
/**
2724
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration} for
28-
* {@link ApplicationStateProvider}.
25+
* {@link ApplicationAvailabilityProvider}.
2926
*
3027
* @author Brian Clozel
3128
* @since 2.3.0
3229
*/
3330
@Configuration(proxyBeanMethods = false)
34-
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
35-
public class ApplicationStateAutoConfiguration {
31+
public class ApplicationAvailabilityAutoConfiguration {
3632

3733
@Bean
38-
public ApplicationStateProvider applicationStateProvider() {
39-
return new ApplicationStateProvider();
40-
}
41-
42-
@Bean
43-
public SpringApplicationEventListener springApplicationEventListener() {
44-
return new SpringApplicationEventListener();
34+
public ApplicationAvailabilityProvider applicationAvailabilityProvider() {
35+
return new ApplicationAvailabilityProvider();
4536
}
4637

4738
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
*/
1616

1717
/**
18-
* Auto-configuration for Kubernetes application features.
18+
* Auto-configuration for application availability features.
1919
*/
20-
package org.springframework.boot.autoconfigure.kubernetes;
20+
package org.springframework.boot.autoconfigure.availability;

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
8989
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
9090
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
9191
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
92-
org.springframework.boot.autoconfigure.kubernetes.ApplicationStateAutoConfiguration,\
92+
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
9393
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
9494
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
9595
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,25 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.boot.autoconfigure.AutoConfigurations;
22-
import org.springframework.boot.kubernetes.ApplicationStateProvider;
23-
import org.springframework.boot.kubernetes.SpringApplicationEventListener;
22+
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
23+
import org.springframework.boot.availability.ApplicationAvailabilityProvider;
2424
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2525

2626
import static org.assertj.core.api.Assertions.assertThat;
2727

2828
/**
29-
* Tests for {@link ApplicationStateAutoConfiguration}
29+
* Tests for {@link ApplicationAvailabilityAutoConfiguration}
3030
*
3131
* @author Brian Clozel
3232
*/
33-
class ApplicationStateAutoConfigurationTests {
33+
class ApplicationAvailabilityAutoConfigurationTests {
3434

3535
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
36-
.withConfiguration(AutoConfigurations.of(ApplicationStateAutoConfiguration.class));
36+
.withConfiguration(AutoConfigurations.of(ApplicationAvailabilityAutoConfiguration.class));
3737

3838
@Test
39-
void disabledWhenNotDeployedOnKubernetes() {
40-
this.contextRunner.run(((context) -> assertThat(context).doesNotHaveBean(ApplicationStateProvider.class)
41-
.doesNotHaveBean(SpringApplicationEventListener.class)));
42-
}
43-
44-
@Test
45-
void enabledWhenDeployedOnKubernetes() {
46-
this.contextRunner.withPropertyValues("spring.main.cloud-platform:kubernetes")
47-
.run(((context) -> assertThat(context).hasSingleBean(ApplicationStateProvider.class)
48-
.hasSingleBean(SpringApplicationEventListener.class)));
39+
void providerIsPresent() {
40+
this.contextRunner.run(((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class)));
4941
}
5042

5143
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/websocket/servlet/WebSocketMessagingAutoConfigurationTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ void setup() {
9292

9393
@AfterEach
9494
void tearDown() {
95-
this.context.close();
95+
if (this.context.isActive()) {
96+
this.context.close();
97+
}
9698
this.sockJsClient.stop();
9799
}
98100

0 commit comments

Comments
 (0)