avatar

目录
Docker 容器优雅的停止

介绍

Docker的大部分重点是在隔离的容器中打包和运行应用程序的过程。有无数的教程说明了如何在Docker容器中运行应用程序,但是很少有教程讨论如何正确停止容器化的应用程序。

但是在生产环境,优雅地停止应用程序的过程可能非常重要。如果您的应用程序正在处理HTTP请求,则可能需要先完成所有未完成的请求,然后再关闭容器。如果您的应用程序写入文件,则可能要确保在退出容器之前正确刷新数据并关闭文件。

如果您只是启动一个容器并永久运行,事情将会很容易,但是很有可能需要停止并重新启动您的应用程序,以方便升级或迁移到另一个主机。在那些需要停止正在运行的容器的情况下,如果进程可以平稳关闭而不是突然断开用户连接并破坏文件,那将是更好的选择。

因此,让我们来看一些可以优雅地停止Docker容器的操作。

发送信号

  1. docker stop
    通常我们使用 docker stop发出命令时,Docker首先会优雅地停止该容器,如果它在10秒钟内不符合要求,Docker 将强行杀死它。
    docker stop命令首先尝试通过向容器中的根进程(PID 1)发送SIGTERM信号来停止正在运行的容器。如果该进程在超时时间内仍未退出,则将发送SIGKILL信号。
    进程可以选择忽略SIGTERM,而SIGKILL则直接进入将终止该进程的内核。该过程甚至根本看不到信号。
    当docker stop您唯一可以控制的是Docker守护程序在发送SIGKILL之前将等待的秒数:

    1
    docker stop --time=30 hexo.xzh
  2. docker kill
    默认情况下,该docker kill命令不会给容器进程提供正常退出的机会-它只是发出SIGKILL来终止容器。但是,它却可以接受一个–signal标志,该标志使您可以将SIGKILL之外的其他信号发送到容器进程。
    例如,如果要将SIGINT(相当于终端上的Ctrl-C)发送到容器“ foo”,则可以使用以下命令:

    1
    docker kill --signal=SIGINT hexo.xzh

    docker stop命令不同,kill它没有任何超时时间。它仅发出一个信号(默认的SIGKILL或您使用--signal标志指定的任何信号)。
    请注意,该docker kill命令的默认行为不同于kill其模仿的标准 Linux 命令。如果未指定其他参数,则Linux kill命令将发送SIGTERM(与相似docker stop)。另一方面,使用docker kill更像是在做Linux kill -9或```Linux kill -SIGKILL`。

  3. docker rm -f
    停止正在运行的容器的最后一个选择是将--force-f标志与docker rm命令结合使用。通常,docker rm用于删除已经停止的容器,但是使用该-f标志会使它首先发出SIGKILL。

    1
    docker rm --force hexo.xzh

    如果你的目标是清除正在运行的容器的所有痕迹,那么这docker rm -f是最快的方法。但是,如果要允许容器正常关闭,则应避免使用此选项。

处理信号

虽然操作系统定义了一组信号列表,但是进程对特定信号的响应方式是特定于应用程序的。
例如,如果要启动 Nginx 服务器的正常关机,则应发送 SIGQUIT。默认情况下,所有 Docker 命令都不会发出SIGQUIT,因此您需要使用以下docker kill命令:

1
docker kill --signal=SIGQUIT nginx

如果你在容器中运行第三方应用程序,则可能需要查看该应用程序的文档,以了解其如何响应不同的信号。简单地运行一个docker stop可能不会给你想要的结果。
在容器中运行自己的应用程序时,必须确定应用程序将如何解释不同的信号。你将需要确保在应用程序代码中捕获了相关信号,并采取了必要的措施以完全关闭该过程。
如果你知道将应用程序打包在 Docker 镜像中,则可以考虑使用 SIGTERM 作为正常关闭信号,因为这是docker stop命令发送的内容。

自定义接收信号后执行的命令

编写应用程序以响应特定信号而正常关闭是一个不错的第一步,但是你还需要确保应用程序的打包方式使其有机会接收 Docker 命令发送的信号。如果你不小心写错应用程序,则它可能永远不会收到docker stop或发送的任何信号docker kill
为了演示,在这里我们用 canal-admin.xzh 这个容器进行操作,里面运行的是 canal-admin
vim entrypoint.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
#stop canal
function stop_canal() {
/home/apps/canal-admin/bin/stop.sh
}

#Trap SIGTERM
trap 'stop_canal' SIGTERM

# wait forever
while true
do
tail -f /dev/null & wait ${!}
done

这个简单的 Shell 脚本当收到 SIGTERM 信号时,它将以0状态退出。
我们将使用以下 Dockerfile 将其打包到 Docker 镜像中:

1
2
3
FROM canal-admin:v1.0
COPY ./entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

现在,我们的脚本以PID 1的身份运行。让我们向容器发送SIGTERM并查看退出状态:

1
2
3
$ docker stop 4dda905e
$ docker inspect -f '{{.State.ExitCode}}' canal-admin.xzh
0

可以看到,我们的脚本收到docker stop命令发送的SIGTERM,并以0状态干净退出。
注意:信号捕获的脚本必须以1号进程(PRD=1)的身份运行,否则它将无法收到docker stop发送到的信号

结论

容器中的 1 号进程是非常重要的,如果它不能正确的处理相关的信号,那么应用程序退出的方式几乎总是被强制杀死而不是优雅的退出。究竟谁是 1 号进程则主要由 EntryPoint, CMD, RUN 等指令的写法决定,所以这些指令的使用是很有讲究的。

打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论