引言
在云原生时代,Kubernetes已成为大规模部署和管理微服务的首选平台。在Kubernetes环境中,服务的生命周期管理尤为重要,其中优雅停机(Graceful Shutdown)是保证服务在收到终止信号时能够有序结束当前任务、释放资源、避免数据丢失的关键环节。本篇博客将结合Spring Boot Actuator,详细介绍如何实现一个自定义的Kubernetes优雅停机插件,以确保Spring Boot应用在Kubernetes中能够平滑、安全地关闭。
一、Spring Boot Actuator简介
Spring Boot Actuator是Spring Boot提供的一套用于监控和管理应用程序的端点(Endpoint)。它提供了诸如健康检查、度量指标、审计日志、HTTP跟踪等多种功能,帮助我们更好地了解应用运行状态,进行故障排查和运维管理。在优雅停机场景中,我们重点关注的是shutdown端点。Actuator默认并未启用此端点,但通过简单的配置,我们可以开启它,允许通过发送特定HTTP请求来触发应用的优雅停机过程。
二、开启Spring Boot Actuator的shutdown端点
在application.properties或application.yml中添加以下配置:management.endpoint.shutdown.enabled=true
management.server.port=8081 # 设置管理端口,避免与应用端口冲突
这里开启了shutdown端点,并设置了管理端口为8081。这样,通过发送POST请求到http://localhost:8081/actuator/shutdown即可触发应用的优雅停机。
三、自定义优雅停机插件实战步骤
1.引入pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.7.10</version>
</dependency>
<!--@ConditionalOnEnabledEndpoint注解只能在2.0.2这个版本以下的低版本出现-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
</dependencies>
2.插件目录
3.创建ActuatorAutoConfiguration类的bean,在spring.factories中配置这个bean的路径,例如:com.wanhengtech.actuator.ActuatorAutoConfiguration
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value ="com.wanhengtech.actuator")
public class ActuatorAutoConfiguration{
}
4.创建ActuatorConfigProperties类的bean,用于加载项目中的yaml文件配置信息
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Data
@Component
public class ActuatorConfigProperties implements BeanFactoryPostProcessor {
//开启health端点
// @Value("${management.health.refresh.enabled:true}")
private boolean managementHealthRefreshEnabled = true;
//可以指定暴露哪些actuator服务,'*'为全部,注意加上引号,被注释的写法表示只允许health,info
// @Value("${management.endpoints.web.exposure.include:*}")
private String managementEndpointsWebExposureInclude = "*";
//显示任意的应用信息,默认关闭,如果是更低一些的版本默认是开启的
// @Value("${management.info.env.enabled:true}")
private boolean managementInfoEnvEnabled = true;
//表示health的内容显示的更加详细内容,不光只status
// @Value("${management.endpoint.health.show-details:always}")
private String managementEndpointHealthShowDetails = "always";
//表示可以通过/actuator/shutdown停止服务
// @Value("${management.endpoint.shutdown.enabled:true}")
private boolean managementEndpointShutdownEnabled = true;
public void setActuatorProperty() {
Properties properties = System.getProperties();
properties.setProperty("management.health.refresh.enabled", String.valueOf(this.managementHealthRefreshEnabled));
properties.setProperty("management.endpoints.web.exposure.include", this.managementEndpointsWebExposureInclude);
properties.setProperty("management.endpoint.health.show-details", managementEndpointHealthShowDetails);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
setActuatorProperty();
}
}
5.创建ApplicationContextProvider类的bean,用于获取项目的上下文
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ConfigurableApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextProvider.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
public static ConfigurableApplicationContext getApplicationContext() {
return applicationContext;
}
}
6.创建ShutdownConfig的配置类
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.context.ShutdownEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShutdownConfig {
@Bean
//出现在2.0.2版本中
@ConditionalOnEnabledEndpoint
public ShutdownEndpoint shutdownEndpoint() {
ShutdownEndpoint shutdownEndpoint = new ShutdownEndpoint();
return shutdownEndpoint;
}
}
7.创建ShutdownEventListener类,用于实现优雅停机
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ShutdownEventListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("监听到调用/actuator/shutdown 接口");
ConfigurableApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
// 处理逻辑
int code = SpringApplication.exit(applicationContext, (ExitCodeGenerator) () -> 0);
//这个就是一个JVM的钩子,通过调用这个方法的话会把所有PreDestroy的方法执行并停止,
// 并传递给特定的退出码给所有上下文。通过调用System.exit(exitCode)可以将这个错误码也传给JVM。0,给JVM一个SIGNAL。
log.info("优雅停机成功!");
System.exit(code);
}
}
8.项目中使用说明文档
# 优雅停机插件
这个插件用于基于k8s和springboot-actuator实现优雅停机和滚动更新,springboot项目只需要引入此依赖包,剩下的工作需要运维配置
maven添加依赖
```xml
<dependency>
<groupId>com.wanhengtech</groupId>
<artifactId>wh-spring-boot-start-k8s-actuator</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
```
运维层面
1. 健康检查
``` k8s部署模版deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: {APP_NAME}
image: {IMAGE_URL}
imagePullPolicy: Always
ports:
- containerPort: {APP_PORT}
- name: management-port
containerPort: 50000 # 应用管理端口
readinessProbe: # 就绪探针
httpGet:
path: /actuator/health/readiness
port: management-port
initialDelaySeconds: 30 # 延迟加载时间
periodSeconds: 10 # 重试时间间隔
timeoutSeconds: 1 # 超时时间设置
successThreshold: 1 # 健康阈值
failureThreshold: 6 # 不健康阈值
livenessProbe: # 存活探针
httpGet:
path: /actuator/health/liveness
port: management-port
initialDelaySeconds: 30 # 延迟加载时间
periodSeconds: 10 # 重试时间间隔
timeoutSeconds: 1 # 超时时间设置
successThreshold: 1 # 健康阈值
failureThreshold: 6 # 不健康阈值
```
2. 滚动更新
``` k8s部署模版deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {APP_NAME}
labels:
app: {APP_NAME}
spec:
selector:
matchLabels:
app: {APP_NAME}
replicas: {REPLICAS} # Pod副本数
strategy:
type: RollingUpdate # 滚动更新策略
rollingUpdate:
maxSurge: 1 # 升级过程中最多可以比原先设置的副本数多出的数量
maxUnavailable: 1 # 升级过程中最多有多少个POD处于无法提供服务的状态
```
3. 优雅停机(确保dockerfile模版集成curl工具,否则无法使用curl命令)
``` k8s部署模版deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: {APP_NAME}
image: {IMAGE_URL}
imagePullPolicy: Always
ports:
- containerPort: {APP_PORT}
- containerPort: 50000 ##如果应用管理有配置端口(50000)就用此端口号,没有配置就采用应用启动的项目端口
lifecycle:
preStop: # 结束回调钩子
exec:
command: ["curl", "-XPOST", "127.0.0.1:50000/actuator/shutdown"]
```
四、结尾
在多个项目中只要将此jar打包上传到本地仓库或者私服上,然后按照说明文档进行配置,就可以使用了。个人经验,不喜勿喷,欢迎大家提供新的优化点,欢迎在评论区讨论!