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 stop或docker 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. ELK 错误: retrying failed action with response code: 403, kibana无log显示

    今天10点时候同事报出kibana突然不显示log了,开始紧急排查 1. 从数据源头查起,先看被filebeat监视的log文件是否在更新(一般只要log对应服务在正常运行,log文件中就会有数据持续 ...

  2. shell脚本编程进阶及RAID和LVM应用1

    bash脚本编程 脚本文件格式: 第一行,顶格写: #!/bin/bash 注释行:#开头 代码注释:写清楚注释 规范写脚本:适度缩进,添加空白行 编程语言:有编程语法格式,库,算法和数据结构 编程思 ...

  3. PAT Basic 1064 朋友数 (20 分)

    如果两个整数各位数字的和是一样的,则被称为是“朋友数”,而那个公共的和就是它们的“朋友证号”.例如 123 和 51 就是朋友数,因为 1+2+3 = 5+1 = 6,而 6 就是它们的朋友证号.给定 ...

  4. SCU 4442 party 二分图最大点权独立集

    每个青蛙喝黑茶或者红茶或者都可以喝 M个矛盾关系 有矛盾的不能喝同种茶 但你可以花费Wi使得这个青蛙消除所有矛盾 把矛盾当作边 青蛙当作点 如果这两个青蛙只喝不同的一种茶就不建边 题目中保证了不存在奇 ...

  5. Python3学习笔记37-LeetCode刷题

    LeetCode中国官网一个用来刷编程题的网站,收录了很多面试题.感觉还是学习到很多.记录一下思路.代码还是要多敲. 建议编写完后直接在LeetCode上运行和提交.提交时会有不同的测试用例来测试代码 ...

  6. luogu4422 [COCI2017-2018#1] Deda[线段树二分]

    讨论帖:线段树二分的题..我还考场切过..白学 这题我一年前的模拟赛考场还切过,现在就不会了..好菜啊. 显然直接线段树拆成$\log n$个区间,然后每个区间在进行线段树二分即可. UPD:复杂度分 ...

  7. asyncio模块实现线程的嵌套和穿插

    import asyncio import time now = lambda :time.time() async def cpc_1(x): print('正在烙比萨饼,预计{}分钟'.forma ...

  8. 享元模式(Flyweight)---结构型

    1 基础知识 定义:提供了减少对象数量从而改善应用所需的对象结构的方式.特征:运用共享技术有效支持大量细粒度的对象. 本质:分离与共享. 使用场景: (1)如果一个应用程序使用了大量的细粒度对象,可以 ...

  9. 02 CSS和DIV对界面优化

    01 网站首页的优化 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...

  10. 了解Springboot加载文件机制

    https://blog.csdn.net/u014044812/article/details/84256764(