SpringCloudAlibaba + k8s实现优雅停机

背景

在k8s+SpringCloudAlibaba技术体系下,实现后台服务的优雅停机。注册中心和配置中心都是nacos,该方案中要使用Nacos的API所以需要Nacos的版本 >= 2.2.0

目的

服务部署上线停机时,不能出现异常。为实现这样的目的,需要同时满足以下两个条件:

  1. 新的请求不能打到要停机的服务上。为实现这样的效果需要先切流量。
  2. 已经持有的请求可以在预期时间内正常返回。这需要在停机前要等待一段时间。

出发点

尽量少的改动

原理

当新Pod就绪之后,k8s会向旧Pod发送停机信号,Pod中的线程要在指定的时间内退出,否则就会被强制杀掉,所以要实现优雅停机具体流程如下:

  1. 接收到停机信号后将服务的权重降为0,为的是不再接收新的请求。通过entrypoint.sh脚本,调用NacosAPI实现,API中的参数通过Dockerfile设置。
  2. 等待客户端的缓存过期。等待的逻辑也在entrypoint.sh脚本。这里的缓存是指负载均衡的缓存,SpringCloudAlibaba体系下默认的是LoadBalancer,通过参数spring.cloud.loadbalancer.cache.ttl设置,默认35s。
  3. 等待已经持有的线程结束。
  4. 执行停机逻辑,从注册中心摘除。

具体实现

出于最小改动的目的,需要了解的参数如下:

  1. k8s参数terminationGracePeriodSeconds,默认为30s,如果向Pod发出停机信号后,在30s内没有退出,会被强制杀掉。
  2. LoadBalancer参数spring.cloud.loadbalancer.cache.ttl,负载缓存更新时间。
  3. 业务允许的最大请求时延,根据业务调整,如果超过30s,就需要调整 参数1
  4. 停机等待时间T。要满足(参数2+参数3) < T < 参数1

假设业务允许的最大请求时延是10s,LoadBalancer的过期时间也可以设置为10s,这样停机等待时间可以设置为23s,这样我们只需要通过Dockerfile设置ENV SERVER_SLEEP_TIME="23"并且添加启动参数--spring.cloud.loadbalancer.cache.ttl=10即可实现服务的优雅停机。所需脚本如下:

Dockerfile 复制代码
FROM registry.cn-hangzhou.aliyuncs.com/supx/openjdk:17-ea-slim
ENV SERVER_NAME="smooth-server"
ENV SERVER_PORT="1002"

ENV NACOS_SERVER_ADDR="main.supx.tech:8848"
ENV NACOS_NAMESPPACE_ID="f5ae6f36-5ed6-46ae-b07a-4c9f86f1f286"

# 权重降为0后等待停机时间
ENV SERVER_SLEEP_TIME="23"

#nacos 基础配置
ENV NACOS_BASE_CONF="--spring.cloud.nacos.server-addr=${NACOS_SERVER_ADDR} --spring.cloud.nacos.config.namespace=${NACOS_NAMESPPACE_ID} --spring.cloud.nacos.discovery.namespace=${NACOS_NAMESPPACE_ID}"
# nacos其他配置
ENV NACOS_CONF=""

ENV JAVA_OPTS="-Xms256m -Xmx256m"
ENV SPRING_OPTS="--spring.cloud.loadbalancer.cache.ttl=10 --spring.application.name=${SERVER_NAME} --server.port=${SERVER_PORT}"

WORKDIR /${SERVER_NAME}
COPY ./target/${SERVER_NAME}-1.0.0-SNAPSHOT.jar ${SERVER_NAME}-1.0.0-SNAPSHOT.jar
COPY entrypoint.sh entrypoint.sh

EXPOSE ${SERVER_PORT}
ENTRYPOINT ["./entrypoint.sh"]
#CMD ["/bin/bash","-c","java -jar ${SERVER_NAME}-1.0.0-SNAPSHOT.jar"]

entrypoint脚本内容如下:

shell 复制代码
#!/bin/bash

# 定义应用启动命令
start_command="java ${JAVA_OPTS} -jar ${SERVER_NAME}-1.0.0-SNAPSHOT.jar ${SPRING_OPTS} ${NACOS_BASE_CONF} ${NACOS_CONF}" "$@"
echo "启动命令:$start_command"
# 启动Java应用并获取其PID
$start_command &
pid=$!

# 定义一个函数来处理信号,并将其发送给Java进程
term_handler() {
    echo "Caught SIGTERM/SIGINT, stopping the application..."
    #服务降级处理
    ifconfig_output=$(ifconfig)
    ip_address=$(echo "$ifconfig_output" | grep -oE 'inet (addr:)?([0-9]{1,3}.){3}[0-9]{1,3}' | grep -oE '([0-9]{1,3}.){3}[0-9]{1,3}' | grep -v '127.0.0.1')
    curl -d "serviceName=${SERVER_NAME}" -d "ip=${ip_address}" -d "port=${SERVER_PORT}" -d "weight=0" -d "namespaceId=${NACOS_NAMESPPACE_ID}" -X PUT "${NACOS_SERVER_ADDR}/nacos/v2/ns/instance"
    echo "等待${SERVER_SLEEP_TIME}s..."
    sleep ${SERVER_SLEEP_TIME}
    #关闭程序
    kill -TERM "$pid"
    wait "$pid"
    exit 0
}

# 捕获SIGTERM和SIGINT信号
trap term_handler SIGTERM SIGINT

# 等待子进程结束或接收到信号
wait "$pid"

echo "Application has stopped."

优缺点

优点 很明显,只需要修改Dockerfile就能实现优雅停机,不需要改动应用。
缺点主要表现在以下几点

  1. NacosAPI,不能保证可用。测试时偶尔会出现API不可用的情况,需要删除/data/protocol数据。如果调用失败了,脚本不能阻止Pod被杀掉,也就不能实现优雅停机。这是这个方案最大的问题,依赖NacosAPI!
  2. 基础镜像需要安装curl net-tools,用来获取IP、调用NacoAPI。
  3. 默认情况下,本地配置无法覆盖配置中心的配置,需要在配置中心添加以下配置
yaml 复制代码
spring:
  cloud:
    config:
      overrideSystemProperties: false

思考

  1. 因为NacosAPI存在偶发问题,可以通过添加自定义接口的方式,用代码来控制停机逻辑。也可以重写spring-boot-starter-actuatorshutdownEndpoint。因为nacos的停机逻辑是在静态代码块中注册的退出钩子,因此只能通过外部调用触发停机逻辑,否则无法实现自定义停机。
  2. 有人建议直接使用反注册的接口,不要先降级,因为这样会有两次交互。但是实际测试发现,NacosAPI的注销实例接口达不到预期效果,请求还是会被路由到要下线的节点上,不知道用代码控制会不会好点。

参考: Nacos 实现服务平滑上下线(Ribbon 和 LB)_java控制nacos服务下线-CSDN博客

相关推荐
wn53124 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1231 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___13 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server13 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php