我们可能都使用过 docker stop 命令来停止正在运行的容器,有时可能会使用 docker kill 命令强行关闭容器或者把某个信号传递给容器中的进程。这些操作的本质都是通过从主机向容器发送信号实现主机与容器中程序的交互。比如我们可以向容器中的应用发送一个重新加载信号,容器中的应用程序在接到信号后执行相应的处理程序完成重新加载配置文件的任务。本文将介绍在 docker 容器中捕获信号的基本知识。

信号(linux)
信号是一种进程间通信的形式。一个信号就是内核发送给进程的一个消息,告诉进程发生了某种事件。当一个信号被发送给一个进程后,进程会立即中断当前的执行流并开始执行信号的处理程序。如果没有为这个信号指定处理程序,就执行默认的处理程序。
进程需要为自己感兴趣的信号注册处理程序,比如为了能让程序优雅的退出(接到退出的请求后能够对资源进行清理)一般程序都会处理 SIGTERM 信号。与 SIGTERM 信号不同,SIGKILL 信号会粗暴的结束一个进程。因此我们的应用应该实现这样的目录:捕获并处理 SIGTERM 信号,从而优雅的退出程序。如果我们失败了,用户就只能通过 SIGKILL 信号这一终极手段了。除了 SIGTERM 和 SIGKILL ,还有像 SIGUSR1 这样的专门支持用户自定义行为的信号。下面的代码简单的说明在 nodejs 中如何为一个信号注册处理程序:
process.on('SIGTERM', function() {
console.log('shutting down...');
});
关于信号的更多信息,笔者在《linux kill 命令》一文中有所提及,这里不再赘述。
容器中的信号
Docker 的 stop 和 kill 命令都是用来向容器发送信号的。注意,只有容器中的 1 号进程能够收到信号,这一点非常关键!
stop 命令会首先发送 SIGTERM 信号,并等待应用优雅的结束。如果发现应用没有结束(用户可以指定等待的时间),就再发送一个 SIGKILL 信号强行结束程序。
kill 命令默认发送的是 SIGKILL 信号,当然你可以通过 -s 选项指定任何信号。
下面我们通过一个 nodejs 应用演示信号在容器中的工作过程。创建 app.js 文件,内容如下:
'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]);
});
});
这个应用是一个 http 服务器,监听端口 3000,为 SIGINT 和 SIGTERM 信号注册了处理程序。接下来我们将介绍以不同的方式在容器中运行程序时信号的处理情况。
应用程序作为容器中的 1 号进程
创建 Dockerfile 文件,把上面的应用打包到镜像中:
FROM iojs:onbuild COPY ./app.js ./app.js COPY ./package.json ./package.json EXPOSE 3000 ENTRYPOINT ["node", "app"]
请注意 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:
现在我们让程序退出,执行命令:
$ docker container kill --signal="SIGTERM" my-app
此时应用会以我们期望的方式退出:
应用程序不是容器中的 1 号进程
创建一个启动应用程序的脚本文件 app1.sh,内容如下:
#!/usr/bin/env bash node app
然后创建 Dockerfile1 文件,内容如下:
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"]
接下来创建镜像:
$ docker build --no-cache -t signal-app1 -f Dockerfile1 .
然后启动容器运行应用程序:
$ docker run -it --rm -p 3000:3000 --name="my-app1" signal-app1
此时 node 应用在容器中的进程号不再是 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
# 容器捕获信号
# 捕获信号
# docker容器如何优雅的终止详解
# Docker 容器操作退出后进入解决办法
# Docker常用的清除容器镜像命令小结
# Docker 解决容器时间与主机时间不一致的问题三种解决方案
# Docker为网络bridge模式指定容器ip的方法
# 如何在Docker容器内外互相拷贝数据
# Docker容器中文乱码(修改docker容器编码格式)的解决方案
# Docker 容器内存监控原理及应用
# 详解挂载运行的docker容器中如何挂载文件系统
# 两种方式创建docker镜像的启动容器时区别介绍(总结篇)
# 应用程序
# 都是
# 镜像
# 用在
# 我们可以
# 发送给
# 的是
# 是一个
# 加载
# 这一
# 是一种
# 在这个
# 出了
# 你可以
# 很有
# 感兴趣
# 能让
# 这不是
# 如果没有
# 会让
相关文章:
高防服务器:AI智能防御DDoS攻击与数据安全保障
香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南
如何在宝塔面板中修改默认建站目录?
,购物网站怎么盈利呢?
建站之星免费模板:自助建站系统与智能响应式一键生成
深入理解Android中的xmlns:tools属性
怎么用手机制作网站链接,dw怎么把手机适应页面变成网页?
太原网站制作公司有哪些,网约车营运证查询官网?
香港服务器租用每月最低只需15元?
江苏网站制作公司有哪些,江苏书法考级官方网站?
如何快速搭建虚拟主机网站?新手必看指南
如何在阿里云购买域名并搭建网站?
北京网站制作网页,网站升级改版需要多久?
PHP 500报错的快速解决方法
开源网站制作软件,开源网站什么意思?
上海网站制作网站建设公司,建筑电工证网上查询系统入口?
如何解决ASP生成WAP建站中文乱码问题?
开心动漫网站制作软件下载,十分开心动画为何停播?
营销式网站制作方案,销售哪个网站招聘效果最好?
在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?
如何用狗爹虚拟主机快速搭建网站?
高防服务器如何保障网站安全无虞?
宝盒自助建站智能生成技巧:SEO优化与关键词设置指南
如何在橙子建站上传落地页?操作指南详解
建站主机服务器选型指南与性能优化方案解析
如何选择香港主机高效搭建外贸独立站?
css网站制作参考文献有哪些,易聊怎么注册?
如何用低价快速搭建高质量网站?
如何在建站之星绑定自定义域名?
建站主机无法访问?如何排查域名与服务器问题
建站ABC备案流程中有哪些关键注意事项?
如何设置并定期更换建站之星安全管理员密码?
建站之星如何快速生成多端适配网站?
威客平台建站流程解析:高效搭建教程与设计优化方案
小建面朝正北,A点实际方位是否存在偏差?
如何零成本快速生成个人自助网站?
官网网站制作腾讯审核要多久,联想路由器newifi官网
c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】
如何挑选高效建站主机与优质域名?
c# Task.ConfigureAwait(true) 在什么场景下是必须的
深圳网站制作费用多少钱,读秀,深圳文献港这样的网站很多只提供网上试读,但有些人只要提供试读的文章就能全篇下载,这个是怎么弄的?
如何快速搭建自助建站会员专属系统?
,网页ppt怎么弄成自己的ppt?
建站之星如何配置系统实现高效建站?
建站之星如何助力网站排名飙升?揭秘高效技巧
简历在线制作网站免费版,如何创建个人简历?
网站制作多少钱一个,建一个论坛网站大约需要多少钱?
公司网站制作需要多少钱,找人做公司网站需要多少钱?
手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?
建站之星价格显示格式升级,你的预算足够吗?
*请认真填写需求信息,我们会在24小时内与您取得联系。