乐趣区

关于云计算:从-Docker-的信号机制看容器的优雅停止

此文是前段时间做相干工作时笔记的整顿,之前本人对这方面的关注不够,因而做下记录。


有太多的文章介绍如何 运行 容器,然而如何 进行 容器的文章绝对少很多。

依据运行的利用类型,利用的进行过程十分重要。如果利用要写文件,进行前要保障正确刷新数据并敞开文件;如果是 HTTP 服务,要确保进行前解决所有未实现的申请。

信号

信号是 Linux 内核与过程以及过程间通信的一种形式。针对每个信号过程都有个默认的动作,不过过程能够通过定义信号处理程序来笼罩默认的动作,除了 SIGSTOPSIGKILL。二者都不能被捕捉或重写,前者用来将过程暂停在以后状态,而后者则是从内核层面立刻杀掉过程。

有两个比拟重要的过程 SIGTERMSIGKILLSIGTERM 是优雅地敞开命令,SIGKILL 则是暴力的敞开命令。比方 Docker,容器会先收到 SIGTERM 信号,10s 后会收到 SIGKILL 信号。

还有很多其余的信号,只是限定于特定的上下文。

中断

硬件的中断就像操作系统的信号。通常产生在硬件想要向操作系统注册事件时。操作系统必须立刻进行运行,并解决中断。

比拟常见的中断例子就是键盘中断,比方按下 ctrl+z 或者 ctrl+c。Linux 将其别离转换成 SIGTSTPSIGINT。硬件中断过来通常用来解决键盘和鼠标输出,但现在被用作操作系统软件驱动层面的信号轮训。

Docker

后面说了这么多终于来到 Docker,容器的独特之处在于通常只运行一个过程。即便是单过程,容器内 PID 为 1 的过程也具备 init 零碎的非凡规定和职责。

PID 1 在 Linux 中十分重要,通常是 init 过程。通常过程在收到 SIGTERM 信号后,如果不对信号过程解决,会疾速退出。但 PID 1 的过程收到 SIGTERM 之后如果不对信号进行解决则 什么都不会做

容器内 PID 1 通常有两种状况:shell 过程 PID 为 1 和你的过程 PID 为 1。别离对应着 shell 和 exec 格局的命令。

shell 格局

Dockerfile 有个特点,就是如果不应用 JSON 格局 来指定容器命令,会通过 shell 以 fork 的模式来执行命令,也就是 /bin/sh -c

  • docker run(宿主机上)

    • /bin/sh -c(PID 1,容器内)

      • /loop.sh(PID 2,容器内)

这种格局的命令特点是不会向业务过程发送信号。比方发送给 shell 的 SIGTERM 信号不会转发给子过程,而是期待子过程的退出。惟一杀死容器的形式就是发送 SIGKILL 信号,或者碰巧子过程本人解体。

所以应该尽量避免应用这种形式,

exec 格局

这个就是 Dockerfile 的举荐语法了,你的过程会立刻启动并作为容器的初始化过程,而后就有了上面的过程树:

  • docker run(宿主机上)

    • /loop.sh(PID 1,容器内)

说了这么多,很多人感觉不够直观。咱们会用示例利用来进行阐明,但在这之前简略说下如何发送信号来进行容器。

发送信号

有几种形式来进行容器。

docker stop

默认状况下 docker stop 命令会向容器发送 SIGTERM 信号,而后期待 10s,如果容器没进行再发送 SIGKILL 信号。

在 Dockerfile 中,能够通过 STOPSIGNAL 指令来设置默认的退出信号,比方 STOPSIGNAL SIGKILL 将退出信号设置为 SIGKILL。或者在 docker run 是通过 --stop-signal 参数来笼罩镜像中的 STOPSIGNAL 设置。

docker kill

默认状况下 docker kill 会间接杀死容器,不给容器任何机会进行优雅进行,这里收回的就是 SIGKILL 信号。

当然 docker kill 能够通过 --signal 来指定要发送的信号,相似 Linux 的 kill 命令:

docker kill ----signal=SIGTERM foo

docker rm -f

通常状况下 docker rm 用来删除曾经进行的容器,然而加上 --force(简写 -f)会强制删除正在运行的容器。同样,也不会给容器任何优化进行的机会。

信号处理

咱们应用一个简略的利用对 shell 和 exec 两种格局做下比照。在这个利用中,对 SIGTERM 进行解决:收到信号后退出。

#!/usr/bin/env sh
trap 'exit 0' SIGTERM
while true; do :; done

接下来咱们应用两种不同格局的 CMD 来构建镜像。

shell 格局

应用上面的 Dockerfile 来构建镜像 term

FROM alpine:3.15.0
COPY loop.sh /
CMD /loop.sh

执行上面的命令构建镜像、启动容器、进行容器。

docker build -t term .
docker run --name term -d term
docker stop term

此时你会发现容器并没有立即进行,而是大概 10s 之后才被进行。能够通过命令查看容器的退出状态:

docker inspect -f '{{.State.ExitCode}}' term
137

137 = 128 + 9 阐明容器的退出信号是 SIGKILL

exec 格局

调整下 Dockerfile,将 CMD 批改为举荐的 JSON 格局:

FROM ubuntu:trusty
COPY loop.sh /
CMD ["/loop.sh"]

执行上面的命令构建镜像、启动容器、进行容器。(须要先执行 docker rm term 删除之前进行的容器)

docker build -t term .
docker run --name term -d term
docker stop term

此时容器会立即退出。查看容器的退出状态:

docker inspect -f '{{.State.ExitCode}}' term
0

总结

docker rm -fdocker kill 干掉容器很容器,然而为了实现容器的优雅退出,应该应用 docker stop 命令,同时 Dockerfile 中应尽量避免应用 shell 格局设置 ENTRYPONT 或者 CMD

文章对立公布在公众号 云原生指北

退出移动版