引言

在云原生时代,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打包上传到本地仓库或者私服上,然后按照说明文档进行配置,就可以使用了。个人经验,不喜勿喷,欢迎大家提供新的优化点,欢迎在评论区讨论!

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐