工具 | 版本 |
---|---|
SpringBoot | 2.3.3 |
Docker | 19.03.12 |
Kubernetes | 1.14 |
服务端要支持 N 多个 Tcp Client 连接,所以做了负载,Tcp Client 会根据负载策略连接到不同的后端 Pod 上,这样就需要维护一个路由表:内部 ip <<==>> Tcp Client
的映射关系。所以在项目关闭的时候要有个关闭前处理
(把当前 Pod 路由从路由表中去掉)的过程,也就是优雅关闭
①。
server:
# 开启优雅关闭,默认:IMMEDIATE,立即关闭
shutdown: graceful
spring:
lifecycle:
# 配置优雅关闭宽限时间
timeout-per-shutdown-phase: 30s
GRACEFUL
:支持优雅关闭。IMMEDIATE
:不支持优雅关闭,直接关闭。30s
,那么就是在发起关闭请求②到真正关闭③项目之间,允许项目做未做完
、关闭之前要做的事
的最长时间为 30s,如果 30s 都没处理完,那么久强制关闭掉。Web Server | 开始优雅关闭之后新的接口访问行为说明 |
---|---|
tomcat 9.0.33+ | 停止接收请求,客户端新请求等待超时。 |
Reactor Netty | 停止接收请求,客户端新请求等待超时。 |
Undertow | 停止接收请求,客户端新请求直接返回 503。 |
以下操作需本地安装好 Docker 并配置好 Idea。
com.spotify
dockerfile-maven-plugin
1.4.10
${docker.image.prefix}/${project.artifactId}
${project.version}
${project.build.finalName}.jar
true
build-image
package
build
push-image
install
push
Dockerfile
文件# 这里可以使用你自己的镜像
FROM ******/centos8:jdk14-base
ARG JAR_FILE
COPY target/$JAR_FILE app.jar
RUN echo 'export LANG=zh_CN.utf8 && exec java $JAVA_OPTS -jar /app.jar' > docker-entrypoint.sh \
&& chmod +x /docker-entrypoint.sh
ENTRYPOINT ["sh", "-c", "/docker-entrypoint.sh"]
mvn clean package
即可
docker stop
+-t
参数可以指定优雅关闭容忍时间。
这里有个问题要说一下,SpringBoot 项目开启了优雅关闭,这里 Docker 容器使用了 CentOS8,也就是说想要 SpringBoot 项目想要让自己的优雅关闭生效,其进程就需要接收到系统发送的 SIGINT(kill -2)
/SIGTERM(kill -15)
信号,然后执行优雅关闭流程。
一般我们配置 ENTRYPOINT
、CMD
时会有这样几种配置方式。
ENTRYPOINT ["java", "-jar", "/app.jar"]
ENTRYPOINT ["sh", "-c", "/docker-entrypoint.sh"]
前者不能加动态参数,后者倒是可以,但是如果直接在脚本中这样调用:java -jar $OPTION /app.jar
是可以加动态参数,但是无法达到优雅关闭的目的。总结来说:前者可以不能加动态参数但是可以优雅关闭,后者可以加动态参数但是不能优雅关闭
究其原因是在使用 docker stop container_id
命令时,Docker 会把 SIGTERM
信号传递给容器内 pid 为 1
的进程。前者启动方式,java 进程的 pid 是为 1 的,后者启动 sh 的进程是为 1 的。也在网上找了不少资料。看了如下这篇:
https://my.oschina.net/u/2552286/blog/3039592
结果并不好,没有效果,也可能没配置到位。后又在官方找到了这种解决方法,完美解决:
https://spring.io/guides/topicals/spring-boot-docker
terminationGracePeriodSeconds
一旦 Kubernetes 决定终止您的 Pod,就会发生一系列事件。让我们看看 Kubernetes 终止生命周期的每一步。
Terminating
状态,并从所有服务的 Endpoints
列表中删除。此时,Pod 停止获得新的流量。但在 Pod中 运行的容器不会受到影响。preStop Hook
被执行(如果设置了)。preStop Hook 是一个发送到 Pod 中的容器特殊命令或 Http 请求。如果您的应用程序在接收 SIGTERM 时没有正常关闭,您可以使用 preStop Hook 来触发正常关闭。接收 SIGTERM 时大多数程序都会正常关闭,但如果您使用的是第三方代码或管理的系统无法控制,则 preStop Hook 是在不修改应用程序的情况下触发正常关闭的好方法。terminationGracePeriod
完成之前退出,Kubernetes 会立即进入下一步。如果您的 Pod 通常需要超过 30 秒才能关闭,请确保增加优雅终止宽限期。您可以通过在 Pod YAML 中设置 terminationGracePeriodSeconds
④选项来实现。结论:Kubernetes 可以出于各种原因终止 Pod,并确保您的应用程序优雅地处理这些终止,这是创建稳定系统和提供出色用户体验的核心。
注意 1:Kubernetes 文档指出,有些步骤是同时执行的(步骤 1,2,3)。因此有可能会导致该 Pod 仍然列在服务的 Endpoints 中并仍然接收流量,而它已经收到 SIGTERM 并且已经停止,因此负载均衡器上可能会有一些 Http 504。目前解决这个问题可以使用 preStop Hook 在容器收到 SIGTERM 时 sleep 一段时间,以确终止期间的流量可以正确处理。设置方式:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: busybox
lifecycle:
preStop:
exec:
command:
- sleep
- 30
terminationGracePeriodSeconds: 60
①:**优雅关闭:**就是在真正关闭项目之前,把未做完
、关闭之前要做的事
执行完毕。
②:**发起关闭请求:**Idea 中点击停止项目请求、Docker 中使用 docker stop container_id
、Linux 中使用 kill -2 pid
或者 kill -15 pid
、Kubernetes 中删除旧 Pod(滚动更新、删除 Pod等)。
③:**真正关闭:**项目进程已经被被关闭掉了,没了。比如:② 中的执行结果、kill -9 pid
。
④:**在 Pod YAML 中设置 terminationGracePeriodSeconds
选项:**比如你可以设置 60 秒。
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: busybox
terminationGracePeriodSeconds: 60