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博客

相关推荐
paopaokaka_luck3 小时前
基于SpringBoot+Vue的电影售票系统(协同过滤算法)
vue.js·spring boot·后端
IT_10249 小时前
Spring Boot项目开发实战销售管理系统——系统设计!
大数据·spring boot·后端
ai小鬼头10 小时前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
Touper.10 小时前
SpringBoot -- 自动配置原理
java·spring boot·后端
一只叫煤球的猫10 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
一只鹿鹿鹿10 小时前
信息化项目验收,软件工程评审和检查表单
大数据·人工智能·后端·智慧城市·软件工程
专注VB编程开发20年11 小时前
开机自动后台运行,在Windows服务中托管ASP.NET Core
windows·后端·asp.net
程序员岳焱11 小时前
Java 与 MySQL 性能优化:MySQL全文检索查询优化实践
后端·mysql·性能优化
一只叫煤球的猫12 小时前
手撕@Transactional!别再问事务为什么失效了!Spring-tx源码全面解析!
后端·spring·面试
旷世奇才李先生12 小时前
Ruby 安装使用教程
开发语言·后端·ruby