【译】优雅的停止docker容器
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容器的更多相关文章
- 教你如何修改运行中的docker容器的端口映射
在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...
- 史上最全面的Docker容器引擎使用教程
目录 1.Docker安装 1.1 检查 1.2 安装 1.3 镜像加速 1.4 卸载Docker 2.实战Nginx 3.Docker命令小结 4.DockerFile创建镜像 4.1 Docker ...
- linux --- 9. docker 容器 和 rabbitmq 队列
一. docker 容器 1.docker是什么? .linux下容器技术有很多,docker是做的最杰出的一款 .docker能够支撑阿里双十一,京东618的业务,说明,性能,安全性不得差 .doc ...
- [转帖]教你如何修改运行中的docker容器的端口映射
教你如何修改运行中的docker容器的端口映射 在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当dock ...
- 如何修改运行中的docker容器的端口映射
在docker run创建并运行容器的时候,可以通过-p指定端口映射规则.但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改.当docker start运行容器后并没有提供一个-p选项或设 ...
- Docker(33)- 如何修改 docker 容器的端口映射
如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 问题背景 docker run ...
- Docker(34)- 如何修改 docker 容器的目录映射
如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 问题背景 docker run ...
- 3.docker容器常用命令
docker容器的常用命令 docker有很多命令,让我们一个一个全部背下来,基本是不可能的,帮助文档的作用就很大了,想要查询那个命令,直接去找帮助文档,帮助文档地址:https://docs.doc ...
- Docker 容器优雅终止方案
原文链接:Docker 容器优雅终止方案 作为一名系统重启工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复 ...
随机推荐
- 客户端升级为select网路模型
服务器端: #include<WinSock2.h> #include<Windows.h> #include<vector> #include<stdio. ...
- python 判断数据类型及释疑
Python 判断数据类型有type和isinstance 基本区别在于: type():不会认为子类是父类 isinstance():会认为子类是父类类型 class Color(object): ...
- MyBatis-05-解决属性名和字段名不一致的问题
5.解决属性名和字段名不一致的问题 1.问题 数据库中的字段 新建一个项目,拷贝之前的,测试实体类字段不一致的情况. public class User { private int id; priva ...
- ubuntu NGINX uwsgi https 部署Django 遇到的问题
搞了3天终于把Django成功部署到Ubuntu,记录一下: 引用来自泡泡茶壶: Ubuntu下的Nginx + Uwsgi + Django项目部署详细流程 前提说明: Django作为小程序的后端 ...
- Codeforces Round #589 (Div. 2) E. Another Filling the Grid(DP, 组合数学)
链接: https://codeforces.com/contest/1228/problem/E 题意: You have n×n square grid and an integer k. Put ...
- Codeforces Round #590 (Div. 3) B2. Social Network (hard version)
链接: https://codeforces.com/contest/1234/problem/B2 题意: The only difference between easy and hard ver ...
- 洛谷P4317 花神的数论题
洛谷题目链接 数位$dp$ 我们对$n$进行二进制拆分,于是就阔以像十进制一样数位$dp$了,基本就是套模板.. 接下来是美滋滋的代码时间~~~ #include<iostream> #i ...
- Django基础之form表单
1. form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时, 我们在好多场景下都需要对用户的输入做校验, 比如 ...
- Navicat连接的某个表一直加载并且不能关闭
问题: 今天下午突然发现数据库的一张表一直加载,也出不来数据,并且也不能关闭.解决办法: 在Navicat中中执行如下命令: SHOW PROCESSLIST; 如果state列中有lock字眼,通过 ...
- POJ 1741 Tree ——(树分治)
思路参考于:http://blog.csdn.net/yang_7_46/article/details/9966455,不再赘述. 复杂度:找树的重心然后分治复杂度为logn,每次对距离数组dep排 ...