sleep
当然可以替代大多数复杂的过程。
这个 Dockerfile(如您所见,使用 exec 形式,因此只有一个进程正在运行并且没有子进程bash
):
FROM busybox
CMD ["/bin/sleep", "100000"]
创建一个不可中断的容器:
docker build -t kill-sleep .
docker run --rm --name kill-sleep kill-sleep
当我尝试阻止它时:
time docker stop kill-sleep
kill-sleep
real 0m10.449s
user 0m0.021s
sys 0m0.027s
在容器被终止之前,命令将在 10 秒内超时。
问题不在于sleep
不处理信号,因为如果我在主机上运行它:
sleep 100000
# in another shell
ps faxww | grep sleep
kill -TERM 31333 # the PID
该过程立即停止。
该问题可能与其在容器中作为 PID 1 运行有关,但我还没有看到有关该问题的参考文档。
答案1
当你跑步时docker stop ...
,有些事情会发生:
docker
将 发送SIGTERM
到容器的主进程。该进程能够屏蔽/忽略SIGTERM
,如果它这样做(或在不终止的情况下处理它)“没有什么“将会发生。- 超时(默认 10 秒)后,向主进程
docker
发送一个SIGKILL
。此信号不能被进程屏蔽,因此它会立即死亡,没有机会执行关闭程序。
理想情况下,在其中运行的进程将及时docker
响应,并在终止之前处理所有日常事务。SIGTERM
如果您知道该过程没有任何内部处理要执行(例如sleep
:),或者不会正确响应SIGTERM
,则可以使用标志指定更短(或更长)的超时-t
:
-t, --time=10 Seconds to wait for stop before killing it
例如,就您而言,您可能喜欢运行docker stop -t 0 ${CONTAINER}
。
该信号行为不同的原因是由于sleep
PID = 1 的运行。
通常(例如:以 PID != 1 运行),进程未明确处理的任何信号都会导致进程终止 - 尝试发送sleep
a SIGUSR1
。
但是,当以 PID = 1 运行时,未处理的信号将被忽略,否则最终会导致内核恐慌:
Kernel panic - not syncing: Attempted to kill init!
您可以使用 docker 工具向 docker 容器发送信号,例如:
docker kill -s TERM kill-sleep
我们可以看到,这并没有达到预期的效果,而下面的代码确实达到了预期的效果:
docker kill -s KILL kill-sleep
一个实验
Dockerfile
FROM busybox
COPY run.sh /run.sh
RUN chmod +x /run.sh
CMD "/run.sh"
运行
#!/bin/sh
echo "sleeping"
sleep 100000
现在,运行
docker build -t kill-sleep .
docker run --rm --name kill-sleep kill-sleep
在另一个终端上:
docker stop kill-sleep
我们观察到相同的10秒延迟/超时。
一个办法
现在让我们来处理SIGTERM
。后台处理和wait
ingsleep
取决于 POSIX shell 处理信号的方式(参见这了解更多信息)。
运行
#!/bin/sh
die_func() {
echo "oh no"
sleep 2
exit 1
}
trap die_func TERM
echo "sleeping"
sleep 100000 &
wait
再次运行命令,我们就会看到我们想要的结果!
$ time docker stop kill-sleep
kill-sleep
real 0m2.515s
user 0m0.008s
sys 0m0.044s
答案2
更多选项:
- 将开关添加
--init
到容器运行命令。这样,sleep 就不是 PID 1,并且 init 在 TERM 上执行正确的操作。 - 将其添加
--stop-signal=KILL
到容器运行命令中。不过,一般不建议将 KILL 用作某种常规操作。