原文地址:http://www.damonyi.cc/?p=197
Docker 关闭Container的思路:当我们使用docker stop 命令去关闭Container时,该命令会发送SIGTERM 命令到Container主进程,让主进程处理该信号,关闭Container,如果在10s内,未关闭容器,Docker Damon会发送SIGKILL 信号将Container关闭。
Signal 表示内部进程的一种通信机制,一个信号表示一个从内核发送到进程的消息,该消息表示某个事件已经发生,当进程收到该信号,进程会被打断,一个信号处理句柄会处理该信号,如果没有针对该信号的句柄,会使用默认句柄处理该信号。
进程会将自己可以处理的信号以回调函数的方式注册到系统内核,当你在终端执行一个Kill命令时,实际上你是在通知内核向其他进程发送信号。一个常见的信号时 SIGTERM,该信号时通知进程关闭并终止,当进程收到该信号时,可以执行关闭Socket、数据库连接、删除临时文件等。许多守护进程会处理SIGHUP信号,从而能够重新加载配置文件,SIGUSR1和SIGUSR2是用户自定义的信号,可以在应用中处理该信号。
举例,在Node.js中处理SIGTERM信号
process.on('SIGTERM', function() { console.log('shutting down...');});
当进程处理SIGTERM信号时,处理该信号的句柄会将程序的执行打断,当该句柄执行完成后,程序才会继续运行,常见的信号如下表所示, 除了SIGKILL 和SIGSTOP信号外,其他信号都可以被进程终止
Docker命令“docker kill”向容器内的主进程发送信号
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]Kill a running container using SIGKILL or a specified signal -s, --signal="KILL" Signal to send to the container
发送到容器的信号被容器的主进程(PID为1)处理,主进程可以忽略该信号,让默认的操作执行,或者为该信号提供一个回调函数。
举例,在容器内运行一个应用,检查信号处理句柄。
var http = require('http');var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n');}).listen(3000, '0.0.0.0');console.log('server started');var signals = { 'SIGINT': 2, 'SIGTERM': 15};function shutdown(signal, value) { server.close(function () { console.log('server stopped by ' + signal); process.exit(128 + value); });}Object.keys(signals).forEach(function (signal) { process.on(signal, function () { shutdown(signal, signals[signal]); });});
我们创建了一个监听3000端口的http
server,创建了两个信号处理句柄,分别处理SIGINT和SIGTERM信号,当信号句柄执行时,将会在标准输出打印:
`server stopped by [SIGNAL]`.
分两种场景描述
当应用是该容器的主进程时,他能够直接处理信号,以下该应用的Dockerfile文件
FROM iojs:onbuildCOPY ./program.js ./program.jsCOPY ./package.json ./package.jsonEXPOSE 3000ENTRYPOINT ["node", "program"]
当在编写Dockerfile文件的时候,启动应用一定要使用ENTRYPOINT或者RUN 命令,否则容器内的主进程将会是/bin/sh –c ,应用只能是主进程的子进程,如果是这样的话,应用是无法收到信号的。
构建镜像:
$ docker build -t signal-fg-app .
运行容器
$ docker run -it –rm -p 3000:3000
–name=”signal-fg-app” signal-fg-app
访问 http://localhost:3000 验证应用正常运行
打开另一个终端执行docker kill 命令
$ docker kill –signal=”SIGTERM”
signal-fg-app
或者
$ docker stop signal-fg-app
这两个命令都可以发送SIGTERM信号来停止应用
Both commands can be used to issue SIGTERM
signal and stop the application.
在运行应用的终端,可以看到下面的输出日志
server stopped by SIGTERM
无法直接将信号发送到该应用,这种场景下的一种解决方法是:以shell脚本的方式启动应用,在这个启动脚本里处理全部的信号,制作该应用的Dokcerfile文件
Dockerfile:FROM iojs:onbuildCOPY ./program.js ./program.jsCOPY ./program.sh ./program.shCOPY ./package.json ./package.jsonRUN chmod +x ./program.shEXPOSE 3000ENTRYPOINT ["./program.sh"]
查看启动脚本program.sh
#!/usr/bin/env bashset -xpid=0# SIGUSR1-handlermy_handler() { echo "my_handler"}# SIGTERM-handlerterm_handler() { if [ $pid -ne 0 ]; then kill -SIGTERM "$pid" wait "$pid" fi exit 143; # 128 + 15 -- SIGTERM}# setup handlers# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handlertrap 'kill ${!}; my_handler' SIGUSR1trap 'kill ${!}; term_handler' SIGTERM# run applicationnode program &pid="$!"# wait indefinetelywhile truedo tail -f /dev/null & wait ${!}done
这里我们创建了两个信号处理函数,一个是处理用户定义的信号,一个是处理SIGTEM信号,能够优雅的关闭应用。
在这个应用中,我们的应用是后台运行的(&),最后,我们使用“wait”来暂停运行,直到一个子进程退出,“wait”和“waitpid”这两个函数在收到信号时,会终止执行,当收到信号后,我们使用特定的句柄处理信号。
Docker的文档中说明,SIGCHLD, SIGKILL, and SIGSTOP 是无法代管的。
构建镜像:
docker build -t signal-bg-app .
运行容器:
docker run -it –rm -p 3000:3000
–name=”signal-bg-app” signal-bg-app
打开一个新的终端,发送 SIGUSR1 信号 :
docker kill –signal=”SIGUSR1″
signal-bg-app
最后停止应用
docker kill –signal=”SIGTERM”
signal-bg-app
应用能够打印相应的日志,并能够优雅的关闭
结论
信号提供了一中处理异步事件的方法,容器能运行的应用可以使用信号进行消息交互。使用信号与主机内的应用进行交互、重新加载配置文件、做一些清楚工作或者多进程协作。
英文地址:
https://medium.com/@gchudnov/trapping-signals-in-docker-containers-7a57fdda7d86#.ukb9dqt9k