介绍
Docker的大部分重点是在隔离的容器中打包和运行应用程序的过程。有无数的教程说明了如何在Docker容器中运行应用程序,但是很少有教程讨论如何正确停止容器化的应用程序。
但是在生产环境,优雅地停止应用程序的过程可能非常重要。如果您的应用程序正在处理HTTP请求,则可能需要先完成所有未完成的请求,然后再关闭容器。如果您的应用程序写入文件,则可能要确保在退出容器之前正确刷新数据并关闭文件。
如果您只是启动一个容器并永久运行,事情将会很容易,但是很有可能需要停止并重新启动您的应用程序,以方便升级或迁移到另一个主机。在那些需要停止正在运行的容器的情况下,如果进程可以平稳关闭而不是突然断开用户连接并破坏文件,那将是更好的选择。
因此,让我们来看一些可以优雅地停止Docker容器的操作。
发送信号
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
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`。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-adminvim entrypoint.sh
1 | !/bin/bash |
这个简单的 Shell 脚本当收到 SIGTERM 信号时,它将以0状态退出。
我们将使用以下 Dockerfile 将其打包到 Docker 镜像中:
1 | FROM canal-admin:v1.0 |
现在,我们的脚本以PID 1的身份运行。让我们向容器发送SIGTERM并查看退出状态:
1 | docker stop 4dda905e |
可以看到,我们的脚本收到docker stop
命令发送的SIGTERM,并以0状态干净退出。
注意:信号捕获的脚本必须以1号进程(PRD=1)的身份运行,否则它将无法收到docker stop
发送到的信号
结论
容器中的 1 号进程是非常重要的,如果它不能正确的处理相关的信号,那么应用程序退出的方式几乎总是被强制杀死而不是优雅的退出。究竟谁是 1 号进程则主要由 EntryPoint, CMD, RUN 等指令的写法决定,所以这些指令的使用是很有讲究的。