1. 介绍

Docker的大部分重点是在隔离的容器中打包和运行应用程序的过程。有无数的教程说明了如何在Docker容器中运行应用程序,但是很少有教程讨论如何正确停止容器化的应用程序。这似乎是一个愚蠢的话题-谁在乎您如何停止容器?

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

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

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

2. 发送信号

您可以使用许多不同的Docker命令来停止正在运行的容器。

2.1 docker stop

当您发出docker stop命令时,Docker首先会很好地要求停止该过程,如果它在10秒钟内不符合要求,它将强行杀死它。如果您曾经发出docker stop过命令,并且不得不等待10秒才能返回命令,那么您已经看到了它的作用。

docker stop命令首先尝试通过向容器中的根进程(PID 1)发送SIGTERM信号来停止正在运行的容器。如果该进程在超时时间内仍未退出,则将发送SIGKILL信号。

进程可以选择忽略SIGTERM,而SIGKILL则直接进入将终止该进程的内核。该过程甚至根本看不到信号。

docker stop您唯一可以控制的是Docker守护程序在发送SIGKILL之前将等待的秒数:

docker stop --time= foo

2.2 docker kill

默认情况下,该docker kill命令不会给容器进程提供正常退出的机会-它只是发出SIGKILL来终止容器。但是,它却可以接受一个--signal标志,该标志使您可以将SIGKILL之外的其他信号发送到容器进程。

例如,如果要将SIGINT(相当于终端上的Ctrl-C)发送到容器“ foo”,则可以使用以下命令:

docker kill --signal=SIGINT foo

docker stop命令不同,kill它没有任何超时时间。它仅发出一个信号(默认的SIGKILL或您使用--signal标志指定的任何信号)。

请注意,该docker kill命令的默认行为不同于kill其模仿的标准Linux 命令。如果未指定其他参数,则Linux kill命令将发送SIGTERM(与相似docker stop)。另一方面,使用docker kill更像是在做Linux kill -9或Linux kill -SIGKILL

2.3 docker rm -f

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

docker rm --force foo

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

3. 处理信号

虽然操作系统定义了一组信号列表,但是进程对特定信号的响应方式是特定于应用程序的。

例如,如果要启动Nginx服务器正常关机,则应发送SIGQUIT。默认情况下,所有Docker命令都不会发出SIGQUIT,因此您需要使用以下docker kill命令:

docker kill --signal=SIGQUIT nginx

收到SIGQUIT时,nginx日志输出如下所示:

// :: [notice] #: signal  (SIGQUIT) received, shutting down
// :: [notice] #: gracefully shutting down
// :: [notice] #: exiting
// :: [notice] #: exit
// :: [notice] #: signal (SIGCHLD) received
// :: [notice] #: worker process exited with code
// :: [notice] #: exit

相反,Apache使用SIGWINCH触发正常关闭

docker kill --signal=SIGWINCH apache

根据Apache文档一个SIGTERM会导致服务器立即退出和终止任何正在进行的请求,所以你可能不希望使用docker stop在Apache的容器上。

如果您在容器中运行第三方应用程序,则可能需要查看该应用程序的文档,以了解其如何响应不同的信号。简单地运行一个docker stop可能不会给您想要的结果。

在容器中运行自己的应用程序时,必须确定应用程序将如何解释不同的信号。您将需要确保在应用程序代码中捕获了相关信号,并采取了必要的措施以完全关闭该过程。

如果您知道将应用程序打包在Docker映像中,则可以考虑使用SIGTERM作为正常关闭信号,因为这是docker stop命令发送的内容。

无论您使用哪种语言,它都有可能支持某种形式的信号处理。我在以下列表中收集了一些语言的相关包/模块/库的链接:

如果您在应用程序中使用Go,请查看tylerb / graceful软件包,该软件包会自动响应SIGINT或SIGTERM信号而正常关闭http.Handler服务器。

4. 接收信号

编写应用程序以响应特定信号而正常关闭是一个不错的第一步,但是您还需要确保应用程序的打包方式使其有机会接收Docker命令发送的信号。如果您不小心启动应用程序,则它可能永远不会收到docker stop或发送的任何信号docker kill

为了演示,让我们创建一个将在Docker容器中运行的简单应用程序:

#!/usr/bin/env bash
trap 'exit 0' SIGTERM
while true; do :; done

这个琐碎的bash脚本只是进入无限循环,但是如果收到SIGTERM,它将以0状态退出。

我们将使用以下Dockerfile将其打包到Docker映像中:

FROM ubuntu:trusty
COPY loop.sh /
CMD /loop.sh

这将简单地将loop.sh bash脚本复制到基于Ubuntu的映像中,并将其设置为运行容器的默认命令。

现在,让我们构建此映像,启动一个容器,然后立即停止它。

$ docker build -t loop .
Sending build context to Docker daemon 3.072 kB
Sending build context to Docker daemon
Step : FROM ubuntu:trusty
---> 07f8e8c5e660
Step : COPY loop.sh /
---> 161f583a7028
Removing intermediate container e0988f66358a
Step : CMD /loop.sh
---> Running in 6d6664be02da
---> 18b3feccee90
Removing intermediate container 6d6664be02da
Successfully built 18b3feccee90 $ docker run -d loop
64d39c3b49147f847722dbfd0c7976315533a729d9453c34cb6cbdaa11d46c21 $ docker stop 64d39c3b

如果继续进行,您可能已经注意到docker stop上面的命令花费了大约10秒钟来完成-这通常表明您的容器没有对SIGTERM做出响应,并且必须以SIGKILL强制终止。

我们可以通过查看容器的退出状态来验证这一点。

$ docker inspect -f '{{.State.ExitCode}}' 64d39c3b

基于我们在应用程序中设置的处理程序,如果我们的容器收到SIGTERM,则应该看到0退出状态,而不是137。实际上,退出状态大于128通常表示该进程由于以下原因而终止:未处理的信号。137 = 128 + 9-表示该进程由于信号编号9(SIGKILL)而终止。

那么,这里发生了什么?我们的应用程序被编码为捕获SIGTERM并正常退出。我们知道docker stop将SIGTERM发送到容器进程。但似乎信号从未传到我们的应用程序中。

要了解这里发生的情况,让我们启动另一个容器并看一看正在运行的进程。

$ docker run -d loop
512c36b5b517b3a43246b519bc5cdb756cdbc4d2ff1e0a3984e83b094f3db136 $ docker exec 512c36b5 ps -ef
UID PID PPID C STIME TTY TIME CMD
root : ? :: /bin/sh -c /loop.sh
root : ? :: bash /loop.sh
root : ? :: ps -ef

在上面的输出中要注意的重要一点是我们的loop.sh脚本未在容器内作为PID 1运行。该脚本实际上是作为运行在PID 1 的/ bin / sh进程的子进程运行的

当您使用docker stopdocker kill向容器发出信号时,该信号仅发送到以PID 1运行的容器进程。

由于/ bin / sh不会将信号转发给任何子进程,因此我们发送的SIGTERM从未到达我们的脚本。显然,如果我们希望我们的应用程序能够接收来自主机的信号,则需要找到一种将其作为PID 1运行的方法。

为此,我们需要返回到Dockerfile,并查看用于启动脚本的CMD指令。实际上,CMD指令可以采用几种不同的形式。在上面的Dockerfile中,我们使用了如下的shell形式

CMD command param1 param2

使用shell形式时,指定的命令与/bin/sh -c shell一起执行。如果您回顾一下我们容器的进程列表,您将看到PID 1处的进程显示命令字符串“ / bin / sh -c /loop.sh”。因此,/ bin / sh作为PID 1运行,然后派生/执行我们的脚本。

幸运的是,Docker还支持CMD指令的exec形式,如下所示:

CMD ["executable","param1","param2"]

请注意,在这种情况下,出现在CMD指令之后的内容被格式化为JSON数组。

当使用CMD指令的exec形式时,该命令将在没有shell的情况下执行。

让我们更改Dockerfile来查看实际效果:

FROM ubuntu:trusty
COPY loop.sh /
CMD ["/loop.sh"]

重建映像并查看容器中运行的进程:

$ docker build -t loop .
[truncated] $ docker run -d loop
4dda905ee902c91d1f56082d1092d6d72ef54b3d4582fe6b453cba90777554e2 $ docker exec 4dda905e ps -ef
UID PID PPID C STIME TTY TIME CMD
root : ? :: bash /loop.sh
root : ? :: ps -ef

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

$ docker stop 4dda905e

$ docker inspect -f '{{.State.ExitCode}}' 4dda905e

这正是我们所期望的结果!我们的脚本收到docker stop命令发送的SIGTERM,并以0状态干净退出。

最重要的是,您应该审核容器中的进程,以确保它们能够接收要发送的信号。在您的Dockerfile中使用CMD(或ENTRYPOINT)指令的exec形式是一个好的开始。

结论

使用docker kill命令终止Docker容器非常容易,但是如果您实际上想以有序的方式关闭应用程序,则需要进行更多的工作。现在,您应该了解如何向容器发送信号,如何在自定义应用程序中处理这些信号以及如何确保应用程序甚至可以首先接收到这些信号。

文章原文链接:https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/

【译】优雅的停止docker容器的更多相关文章

  1. 教你如何修改运行中的docker容器的端口映射

    在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...

  2. 史上最全面的Docker容器引擎使用教程

    目录 1.Docker安装 1.1 检查 1.2 安装 1.3 镜像加速 1.4 卸载Docker 2.实战Nginx 3.Docker命令小结 4.DockerFile创建镜像 4.1 Docker ...

  3. linux --- 9. docker 容器 和 rabbitmq 队列

    一. docker 容器 1.docker是什么? .linux下容器技术有很多,docker是做的最杰出的一款 .docker能够支撑阿里双十一,京东618的业务,说明,性能,安全性不得差 .doc ...

  4. [转帖]教你如何修改运行中的docker容器的端口映射

    教你如何修改运行中的docker容器的端口映射   在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当dock ...

  5. 如何修改运行中的docker容器的端口映射

    在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...

  6. Docker(33)- 如何修改 docker 容器的端口映射

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 问题背景 docker run ...

  7. Docker(34)- 如何修改 docker 容器的目录映射

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 问题背景 docker run ...

  8. 3.docker容器常用命令

    docker容器的常用命令 docker有很多命令,让我们一个一个全部背下来,基本是不可能的,帮助文档的作用就很大了,想要查询那个命令,直接去找帮助文档,帮助文档地址:https://docs.doc ...

  9. Docker 容器优雅终止方案

    原文链接:Docker 容器优雅终止方案 作为一名系统重启工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复 ...

随机推荐

  1. Django_03_后台管理

    后台管理 站点分为内容发布和公共访问两部分 内容发布的部分由网站的管理员负责查看.添加.修改.删除数据,开发这些重复的功能是一件单调乏味.缺乏创造力的工作,为此,Django能够根据定义的模型类自动地 ...

  2. (3)你的第一个python程序

    Life is short,you need python!人生苦短,你需要python!好吧,干了这碗鸡汤................. hello world 没错,几乎是所有程序猿的第一个程 ...

  3. SqlServer和Oralce保留几位小数以及当末尾小数为0也显示

    需求描述:对数字类型值保留2位小数,当2位小数末尾出现0时也显示 SqlServer处理方法: 1.首先通过Round函数保留2位有效数字,多出的位数值变成0 2.通过Cast函数转成decimal( ...

  4. Spring入门篇——第6章 Spring AOP的API介绍

    第6章 Spring AOP的API介绍 主要介绍Spring AOP中常用的API. 6-1 Spring AOP API的Pointcut.advice概念及应用 映射方法是sa开头的所有方法 如 ...

  5. duilib学习领悟(1)

    学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会! 由于本人知识有限,若有错误地方,望批评指正.多谢.! 初识duilib 刚开始接触duilib的时候,觉的他好神奇,整个 ...

  6. Java 实现大文件切割并生成多个文件

    话不多说,直接上代码 import java.io.*; /*** * 分割大文件 * ( * SQL 文件太大(insert),第三方工具无法一次性读取,进行分割 * 生成 一个一个文件 * ) * ...

  7. BZOJ 3038: 上帝造题的七分钟2 / BZOJ 3211: 花神游历各国 (线段树区间开平方)

    题意 给出一些数,有两种操作.(1)将区间内每一个数开方(2)查询每一段区间的和 分析 普通的线段树保留修改+开方优化.可以知道当一个数为0或1时,无论开方几次,答案仍然相同.所以设置flag=1变表 ...

  8. i3wm脚本

    exec 执行命令 --no-startup-id 有些脚本或者程序不支持启动通知,不加命令,鼠标会长时间空转,60秒左右 exec_always 每次重启i3,使用该命令启动的程序都会重新执行一次, ...

  9. MFC 下拉框Combo Box

    下拉框常用的事件是Change事件.属性常用:Data(英文;分隔),Sort(是否排序) // OnInitDialog()中 m_cbx.SetCurSel();//设置默认选项 //OnBnCl ...

  10. springbooot+restful目录规则

    dao是访问数据层,dto是数据传出层,po实体类