Home >Operation and Maintenance >Docker >Do you know how to catch signals in docker containers?
I believe you must have used the docker stop command to stop a running container. Sometimes we may also use the docker kill command to forcefully close the container or pass a signal to the process in the container.
In fact, the operations we perform are essentially the interaction between the host and the program in the container by sending signals from the host to the container. For example, if we send a reload signal to the application in the container, then the application in the container will execute the corresponding handler to complete the task of reloading the configuration file after receiving the signal.
Signal (linux)
Signal is a form of inter-process communication. A signal is a message sent by the kernel to a process to tell the process that a certain event has occurred. When a signal is sent to a process, the process will immediately interrupt the current execution flow and start executing the signal handler (it is not accurate to say that the signal is processed at a specific time). If no handler is specified for this signal, the default handler is executed.
The process needs to register handlers for the signals it is interested in. For example, in order to allow the program to exit gracefully (to clean up resources after receiving the exit request), generally the program will handle the SIGTERM signal. Unlike the SIGTERM signal, the SIGKILL signal will violently end a process. Therefore, our application should implement a directory that captures and handles the SIGTERM signal to exit the program gracefully. If we fail, the user will have to resort to the SIGKILL signal as a last resort. In addition to SIGTERM and SIGKILL, there are signals like SIGUSR1 that specifically support user-defined behavior. The following code simply explains how to register a handler for a signal in nodejs:
process.on('SIGTERM', function() { console.log('shutting down...'); });
For more information about signals, the author mentioned it in the article "linux kill command" and will not be repeated here.
Signals in the container
Docker’s stop and kill commands are used to send signals to the container. Note that only process No. 1 in the container can receive the signal, this is very critical!
The stop command will first send the SIGTERM signal and wait for the application to end gracefully. If it is found that the application has not ended (the user can specify the waiting time), then send another SIGKILL signal to forcefully end the program.
The kill command sends the SIGKILL signal by default. Of course, you can specify any signal through the -s option.
Below we use a nodejs application to demonstrate the working process of signals in the container. Create an app.js file with the following content:
'use strict'; 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]); }); });
This application is an http server, listening on port 3000, with handlers registered for the SIGINT and SIGTERM signals. Next we will introduce how signals are handled when running programs in containers in different ways.
The application is used as process No. 1 in the container
Create a Dockerfile file and package the above application into the image:
FROM iojs:onbuild COPY ./app.js ./app.js COPY ./package.json ./package.json EXPOSE 3000ENTRYPOINT ["node", "app"]
Please pay attention to the way of writing the ENTRYPOINT instruction. This The way it is written will make node run as process No. 1 in the container.
Next create the image:
$ docker build --no-cache -t signal-app -f Dockerfile .
Then start the container and run the application:
请注意 ENTRYPOINT 指令的写法,这种写法会让 node 在容器中以 1 号进程的身份运行。 接下来创建镜像: $ docker build --no-cache -t signal-app -f Dockerfile . 然后启动容器运行应用程序: $ docker run -it --rm -p 3000:3000 --name="my-app" signal-app 此时 node 应用在容器中的进程号为 1:
At this time, the process number of the node application in the container is 1:
Now we let the program exit and execute the command:
$ docker container kill --signal="SIGTERM" my-app
At this time the application will exit in the way we expect:
The application is not process No. 1 in the container
Create a script file app1.sh that starts the application, with the following content:
#!/usr/bin/env bash node app
Then create a Dockerfile1 file with the following content:
FROM iojs:onbuild COPY ./app.js ./app.js COPY ./app1.sh ./app1.sh COPY ./package.json ./package.json RUN chmod +x ./app1.sh EXPOSE 3000 ENTRYPOINT ["./app1.sh"]
Next create the image:
$ docker build --no-cache -t signal-app1 -f Dockerfile1 .
Then start the container and run the application:
$ docker run -it --rm -p 3000:3000 --name="my-app1" signal-app1
At this time, the process number of the node application in the container is no longer 1:
现在给 my-app1 发送 SIGTERM 信号试试,已经无法退出程序了!在这个场景中,应用程序由 bash 脚本启动,bash 作为容器中的 1 号进程收到了 SIGTERM 信号,但是它没有做出任何的响应动作。
我们可以通过:
$ docker container stop my-app1 # or $ docker container kill --signal="SIGKILL" my-app1
退出应用,它们最终都是向容器中的 1 号进程发送了 SIGKILL 信号。很显然这不是我们期望的,我们希望程序能够收到 SIGTERM 信号优雅的退出。
在脚本中捕获信号
创建另外一个启动应用程序的脚本文件 app2.sh,内容如下:
#!/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 app & pid="$!" # wait forever while true do tail -f /dev/null & wait ${!} done
这个脚本文件在启动应用程序的同时可以捕获发送给它的 SIGTERM 和 SIGUSR1 信号,并为它们添加了处理程序。其中 SIGTERM 信号的处理程序就是向我们的 node 应用程序发送 SIGTERM 信号。
然后创建 Dockerfile2 文件,内容如下:
FROM iojs:onbuild COPY ./app.js ./app.js COPY ./app2.sh ./app2.sh COPY ./package.json ./package.json RUN chmod +x ./app2.sh EXPOSE 3000 ENTRYPOINT ["./app2.sh"]
接下来创建镜像:
$ docker build --no-cache -t signal-app2 -f Dockerfile2 .
然后启动容器运行应用程序:
$ docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2
此时 node 应用在容器中的进程号也不是 1,但是它却可以接收到 SIGTERM 信号并优雅的退出了:
结论
容器中的 1 号进程是非常重要的,如果它不能正确的处理相关的信号,那么应用程序退出的方式几乎总是被强制杀死而不是优雅的退出。究竟谁是 1 号进程则主要由 EntryPoint, CMD, RUN 等指令的写法决定,所以这些指令的使用是很有讲究的。
相关推荐:docker入门教程
The above is the detailed content of Do you know how to catch signals in docker containers?. For more information, please follow other related articles on the PHP Chinese website!