Docker 处理信号
如何处理容器的信号
关于容器与信号的关系
当你在执行
Docker容器时,主要执行程序(Process)的 PID 将会是1,只要这个程序停止,容器就会跟著停止由于容器中一直没有像
systemd或sysvinit这类的初始化系统(init system),少了初始化系统来管理程序,会导致当程序不稳定的时候,无法进一步有效的处理程序的状态,或是无法有效的控制Signal处理机制我们以
docker stop为例,这个命令实质上是对容器中的 PID1发出一个SIGTERM信号,如果程序本身并没有处理Signal的机制,就会直接忽略这类信号,这就会导致docker stop等了10秒之后还不结束,然后Docker Engine又会对 PID1送出另一个SIGKILL讯号,试图强迫杀死这个程序,这才会让容器彻底停下来
没有正确处理 Signal 的情况
我们用一个最简单的例子来说明:
1、执行简单的 sleep 命令
1 | |
2、然后我们试著停止这个容器
1 | |
3、此时你会发现要等 10 秒,容器才会结束!
4、会发生无法立刻停止的状况,其实是 /bin/sh 预设并不会处理(handle)信号,所以他会把所有不认得的信号忽略,直到系统把他沙掉为止。
正确处理 Signal 的情况
1、建立一个空文件夹,并且建立一个 test.sh 文件
脚本中使用 trap 'exit 0' SIGTERM 来处理 SIGTERM 讯号,接收到讯号就直接以状态码 0 正常的退出:
1 | |
2、撰写一个 Dockerfile 来构建一个名为 test:latest 的 Image
1 | |
3、构建容器映像
1 | |
4、执行容器
1 | |
5、然后我们试著停止这个容器
1 | |
此时你会发现,容器收到讯号之后就会立刻结束!
使用 dumb-init 控制所有启动的程序
有时候我们会通过 Shell Script 启动一些其他的程式,有些甚至是背景服务。
但是,当 Shell 接收到 SIGTERM 讯号的时候,并不会转传收到的讯号给子程序(Sub-process),所以就算你的 Shell Script 收到信号,其他子程序是不会收到信号的,所以程序并不会停止
这个状况有个非常简单的解决方式,就是把
#!/usr/bin/env sh修改成#!/usr/bin/dumb-init /bin/sh即可!
1 | |
使用 dumb-init 控制信号覆写 (Signal rewriting)
有些特定的服务并不会接收
SIGTERM讯号。例如nginx预设若要执行优雅的结束,必须对他送出SIGQUIT讯号。而Apache HTTP Server则要送出SIGWINCH讯号,才会优雅的结束。由于
docker stop预设会送出SIGTERM服务为主,所以如果你打算自己封装nginx容器的话,送出正确的讯号就十分重要。如果你直接使用
nginx容器映像,其实不用特别处理,因为你可以看nginx的Dockerfile已经设定了STOPSIGNAL SIGQUIT指令,所以当有人对这个容器送出docker stop命令时,本来就会转送SIGQUIT讯号过去,不需要靠dumb-init的帮助。当然,如果你有特别的需求,才需要把
SIGTERM (15)转送成SIGQUIT (3)这样写:1
2ENTRYPOINT [ "/usr/bin/dumb-init", "--rewrite", "15:3", "--" ]
CMD [ "curl", "http://http.speed.hinet.net/test_9216m.zip", "-o", "/dev/null" ]完整的讯号名称与编号可以透过 Linux 下的 kill -l 命令查询。
以下是
dumb-init的参数选项说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18dumb-init v1.2.2
Usage: dumb-init [option] command [[arg] ...]
dumb-init is a simple process supervisor that forwards signals to children.
It is designed to run as PID1 in minimal container environments.
Optional arguments:
-c, --single-child Run in single-child mode.
In this mode, signals are only proxied to the
direct child and not any of its descendants.
-r, --rewrite s:r Rewrite received signal s to new signal r before proxying.
To ignore (not proxy) a signal, rewrite it to 0.
This option can be specified multiple times.
-v, --verbose Print debugging information to stderr.
-h, --help Print this help message and exit.
-V, --version Print the current version and exit.
Full help is available online at https://github.com/Yelp/dumb-init
使用 tini 初始化系统
tini是一套更简单的init系统,专门用来执行一个子程序(spawn a single child),并等待子程序结束,即便子程序已经变成僵尸程序(zombie process)也能捕捉到,同时也能转送Signal给子程序。如果你使用
Docker来跑容器,可以非常简便的在docker run的时候用--init参数,就会自动注入tini程式 (/sbin/docker-init) 到容器中,并且自动取代ENTRYPOINT设定,让原本的程式直接跑在tini程序底下!注意:Docker 1.13 以后的版本才开始支持
--init参数,并内建tini在内。
1、不用 --init 的情况
直接启动 sleep 跑 100 秒
1 | |
使用 ps -ef 可以得知 sleep 100 进程会直接跑在 PID 1 底下
1 | |
1 | |
停止容器需要 10 秒才能完成
1 | |
2、使用 --init 的情况
使用 --init 启动 sleep 程式跑 100 秒
1 | |
使用 ps -ef 可以得知 sleep 100 进程会跑在 /sbin/docker-init -- 命令下
1 | |
1 | |
停止容器只需要 1 秒内就可以完成
1 | |