大家好,我是stanley「史丹利」,今天来谈谈技术:容器优雅关闭方案。
1、遇到的问题
在公司某服务接入效能平台后,发布过程中,页面偶尔会出现5003报错。最初以为是Nacos没有及时将服务反注册,即POD在已经正常关闭的情况下,注册中心依然保留POD信息,导致请求依然发送到已关闭的POD中。
5003报错
5003-error-2
2、问题排查 2.1 首先,我们与开发团队合作,检查了反注册逻辑及相关日志,没有发现任何异常。
2.2 后来偶然发现POD中的主进程PID不为1,而PID为1的进程是shell进程。这会导致容器关闭时,业务进程无法接收到k8s发送的SIGTERM信号,只能在等待15秒后被强行杀死。
process-shell
2.3 我们修改了程序的启动参数,通过EXEC启动模式,使得应用主进程的PID变为1。
process-exec
2.4 重新发布验证后,5003报错问题得到了修复。
3、根因分析 3.1、SHELL 模式和 CMD 模式带来的差异 通常在Dockerfile中使用CMD和ENTRYPOINT来启动应用。启动应用有两种模式:shell模式和exec模式。在shell模式下,PID为1的进程是shell进程;在exec模式下,PID为1的进程是业务本身。
SHELL模式
FROM golang as builder WORKDIR /go/ COPY app.go . RUN go build app.go FROM ubuntu WORKDIR /root/ COPY --from=builder /go/app . CMD ./app
这种方式构建的镜像,应用启动后PID为1的进程是shell进程。
EXEC模式
FROM golang as builder WORKDIR /go/ COPY app.go . RUN go build app.go FROM ubuntu WORKDIR /root/ COPY --from=builder /go/app . CMD ["./app"]
这种方式构建的镜像,应用启动后PID为1的进程是应用进程。
3.2、直接启动应用和通过脚本启动的区别
在实际生产环境中,由于应用启动命令通常会包含许多启动参数,我们通常会使用一个启动脚本来启动应用,以便管理。相应地,在容器内,PID为1的进程会是shell进程,但shell程序不会转发信号,也不响应退出信号。因此,如果容器应用中启动了shell并占据了PID=1的位置,那么就无法接收到k8s发送的SIGTERM信号,只能在超时后被强行杀死。启动脚本 start.sh 的内容如下:
start.sh
$ cat > start.sh
Dockerfile
FROM golang as builder WORKDIR /go/ COPY app.go . RUN go build app.go FROM alpine WORKDIR /root/ COPY --from=builder /go/app . ADD start.sh /root/ CMD ["/bin/sh","/root/start.sh"]
3.2.1 解决方案
方案一:通过k8s的prestop参数调用容器内进程关闭脚本,实现优雅关闭。在前面脚本启动的Dockerfile基础上,定义一个优雅关闭的脚本,通过k8s-prestop在关闭POD前调用优雅关闭脚本,实现POD优雅关闭。
stop.sh
#!/bin/sh
ps -ef|grep app|grep -v grep|awk '{print $1}'|xargs kill -15通过yaml部署到k8s中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-prestop
labels:
app: prestop
spec:
replicas: 1
selector:
matchLabels:
app: prestop
template:
metadata:
labels:
app: prestop
spec:
containers:
- name: prestop
image: xx/app:v1.0-prestop
lifecycle:
preStop:
exec:
command:
- sh
- /root/stop.sh方案二:将shell脚本修改为exec执行。修改start.sh脚本:
#!/bin/sh exec ./app
在shell中添加一个exec命令即可让应用进程替代当前shell进程,这样可以将SIGTERM信号传递到业务层,让业务实现优雅关闭。
方案三:通过第三方init进程传递SIGTERM到进程中。使用dump-init或tini作为容器的主进程,在收到退出信号时,会将退出信号转发给进程组中的所有进程。主要适用于应用本身无关闭信号处理的场景。docker –init本身也是集成的tini。
FROM golang as builder WORKDIR /go/ COPY app.go . RUN go build app.go FROM alpine WORKDIR /root/ COPY --from=builder /go/app . ADD start.sh tini /root/ RUN chmod a+x start.sh && apk add --no-cache tini ENTRYPOINT ["/sbin/tini", "--"] CMD ["/root/tini", "--", /root/start.sh"]
4、总结 1、对于容器化应用启动命令,建议使用EXEC模式。
2、对于已经在代码层面实现了优雅关闭的业务,但有shell启动脚本的,容器化后部署到k8s上,建议使用方案一和方案二。
3、对于代码层面没有实现优雅关闭的业务,建议使用方案三。
以上就是K8S容器应用优雅关闭-修复5003 Error的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号