原文连接:Docker 容器优雅终止方案html
做为一名系统重启工程师(SRE),你可能常常须要重启容器,毕竟 Kubernetes 的优点就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟便可恢复,实在不行再重启系统,这就是系统重启工程师的杀手锏。然而现实并无理论上那么美好,某些容器须要花费 10s
左右才能中止,这是为啥?有如下几种可能性:node
对于第 3 种可能性咱们无能为力,本文主要解决 1 和 2。git
若是要构建一个新的 Docker 镜像,确定但愿镜像越小越好,这样它的下载和启动速度都很快,通常咱们都会选择一个瘦了身的操做系统(例如 Alpine
,Busybox
等)做为基础镜像。github
问题就在这里,这些基础镜像的 init 系统也被抹掉了,这就是问题的根源!docker
init
系统有如下几个特色:shell
对于容器来讲,init
系统不是必须的,当你经过命令 docker stop mycontainer
来中止容器时,docker CLI 会将 TERM
信号发送给 mycontainer 的 PID
为 1 的进程。bash
ENTRYPOINT
或 CMD
指定的应用)就是 PID 1,应用进程直接负责响应 TERM
信号。这时又分为两种状况:
SIGTERM
信号,或者应用中没有实现处理 SIGTERM
信号的逻辑,应用就不会中止,容器也不会终止。docker stop mycontainer
以后,Docker 会等待 10s
,若是 10s
后容器尚未终止,Docker 就会绕过容器应用直接向内核发送 SIGKILL
,内核会强行杀死应用,从而终止容器。若是容器中的进程没有收到 SIGTERM
信号,颇有多是由于应用进程不是 PID 1
,PID 1 是 shell
,而应用进程只是 shell
的子进程。而 shell 不具有 init
系统的功能,也就不会将操做系统的信号转发到子进程上,这也是容器中的应用没有收到 SIGTERM
信号的常见缘由。post
问题的根源就来自 Dockerfile
,例如:优化
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT ./popcorn.sh
ENTRYPOINT
指令使用的是 shell 模式,这样 Docker 就会把应用放到 shell
中运行,所以 shell
是 PID 1。ui
解决方案有如下几种:
与其使用 shell 模式,不如使用 exec 模式,例如:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT ["./popcorn.sh"]
这样 PID 1 就是 ./popcorn.sh
,它将负责响应全部发送到容器的信号,至于 ./popcorn.sh
是否真的能捕捉到系统信号,那是另外一回事。
举个例子,假设使用上面的 Dockerfile 来构建镜像,popcorn.sh
脚本每过一秒打印一第二天期:
#!/bin/sh while true do date sleep 1 done
构建镜像并建立容器:
🐳 → docker build -t truek8s/popcorn . 🐳 → docker run -it --name corny --rm truek8s/popcorn
打开另一个终端执行中止容器的命令,并计时:
🐳 → time docker stop corny
由于 popcorn.sh
并无实现捕获和处理 SIGTERM
信号的逻辑,因此须要 10s 左右才能中止容器。要想解决这个问题,就要往脚本中添加信号处理代码,让它捕获到 SIGTERM
信号时就终止进程:
#!/bin/sh # catch the TERM signal and then exit trap "exit" TERM while true do date sleep 1 done
注意:下面这条指令与 shell 模式的 ENTRYPOINT 指令是等效的:
ENTRYPOINT ["/bin/sh", "./popcorn.sh"]
若是你就想使用 shell
模式的 ENTRYPOINT 指令,也不是不能够,只需将启动命令追加到 exec
后面便可,例如:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh ENTRYPOINT exec ./popcorn.sh
这样 exec
就会将 shell 进程替换为 ./popcorn.sh
进程,PID 1 仍然是 ./popcorn.sh
。
若是容器中的应用默认没法处理 SIGTERM
信号,又不能修改代码,这时候方案 1 和 2 都行不通了,只能在容器中添加一个 init
系统。init 系统有不少种,这里推荐使用 tini,它是专用于容器的轻量级 init 系统,使用方法也很简单:
tini
tini
设为容器的默认应用popcorn.sh
做为 tini
的参数具体的 Dockerfile 以下:
FROM alpine:3.7 COPY popcorn.sh . RUN chmod +x popcorn.sh RUN apk add --no-cache tini ENTRYPOINT ["/sbin/tini", "--", "./popcorn.sh"]
如今 tini
就是 PID 1,它会将收到的系统信号转发给子进程 popcorn.sh
。
若是你想直接经过 docker 命令来运行容器,能够直接经过参数
--init
来使用 tini,不须要在镜像中安装 tini。若是是Kubernetes
就不行了,还得老老实实安装 tini。
最后一个问题:若是移除 popcorn.sh
中对 SIGTERM 信号的处理逻辑,容器会在咱们执行中止命令后当即终止吗?
答案是确定的。在 Linux 系统中,PID 1
和其余进程不太同样,准确地说应该是 init
进程和其余进程不同,它不会执行与接收到的信号相关的默认动做,必须在代码中明确实现捕获处理 SIGTERM
信号的逻辑,方案 1 和 2 干的就是这个事。
普通进程就简单多了,只要它收到系统信号,就会执行与该信号相关的默认动做,不须要在代码中显示实现逻辑,所以能够优雅终止。
Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12离线安装包发布地址http://store.lameleg.com ,欢迎体验。 使用了最新的sealos v3.3.6版本。 做了主机名解析配置优化,lvscare 挂载/lib/module解决开机启动ipvs加载问题, 修复lvscare社区netlink与3.10内核不兼容问题,sealos生成百年证书等特性。更多特性 https://github.com/fanux/sealos 。欢迎扫描下方的二维码加入钉钉群 ,钉钉群已经集成sealos的机器人实时能够看到sealos的动态。