docker容器内的信号处理

docker 关闭Container的思路:当我们使用docker stop 命令去关闭Container时,该命令会发送SIGTERM 命令到Container主进程,让主进程处理该信号,关闭Container,如果在10s内,未关闭容器,docker Damon会发送SIGKILL 信号将Container关闭。

       Signal

Signal 表示内部进程的一种通信机制,一个信号表示一个从内核发送到进程的消息,该消息表示某个事件已经发生,当进程收到该信号,进程会被打断,一个信号处理句柄会处理该信号,如果没有针对该信号的句柄,会使用默认句柄处理该信号。

进程会将自己可以处理的信号以回调函数的方式注册到系统内核,当你在终端执行一个Kill命令时,实际上你是在通知内核向其他进程发送信号。一个常见的信号时 SIGTERM,该信号时通知进程关闭并终止,当进程收到该信号时,可以执行关闭Socket、数据库连接、删除临时文件等。许多守护进程会处理SIGHUP信号,从而能够重新加载配置文件,SIGUSR1和SIGUSR2是用户自定义的信号,可以在应用中处理该信号。

举例,在Node.js中处理SIGTERM信号

1
2
3
process.on('SIGTERM', function() {
console.log('shutting down...');
});

当进程处理SIGTERM信号时,处理该信号的句柄会将程序的执行打断,当该句柄执行完成后,程序才会继续运行,常见的信号如下表所示, 除了SIGKILL 和SIGSTOP信号外,其他信号都可以被进程终止

 

docker中的信号

docker命令“docker kill”向容器内的主进程发送信号

1
2
3
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)处理,主进程可以忽略该信号,让默认的操作执行,或者为该信号提供一个回调函数。

举例,在容器内运行一个应用,检查信号处理句柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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文件

1
2
3
4
5
FROM iojs:onbuild
COPY ./program.js ./program.js
COPY ./package.json ./package.json
EXPOSE 3000
ENTRYPOINT \["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文件

1
2
3
4
5
6
7
8
dockerfile:
FROM iojs:onbuild
COPY ./program.js ./program.js
COPY ./program.sh ./program.sh
COPY ./package.json ./package.json
RUN chmod +x ./program.sh
EXPOSE 3000
ENTRYPOINT \["./program.sh"\]

查看启动脚本program.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env bash
set -x

pid=0

# SIGUSR1-handler
my\_handler() {
echo "my\_handler"
}

# SIGTERM-handler
term\_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 handler
trap 'kill ${!}; my\_handler' SIGUSR1
trap 'kill ${!}; term\_handler' SIGTERM

# run application
node program &
pid="$!"

# wait indefinetely
while true
do
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