百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何安全又优雅地退出、删除Docker容器?

nanshan 2025-02-17 13:19 12 浏览 0 评论

一、概述


不论是什么类型的应用,都会希望在服务停止前能够收到停止通知,有一定的时间做退出前的释放资源、关闭连接、不再接收外部请求等工作。比如你的应用正在处理HTTP请求,你希望在停止前能完成所有未完成的请求;如果你的应用正在写入文件,你也许希望在停止容器前能够正确的刷新数据到磁盘并关闭文件。


二、Linux常见信号


要了解应用优雅停止方法,我们先回顾一下与容器相关的Linux常见信号。


信号是一种进程间通信的形式。一个信号就是内核发送给进程的一个消息,告诉进程发生了某种事件。进程需要为自己感兴趣的信号注册处理程序,举例:


  • 为了能让程序优雅的退出(接到退出的信号后,对资源进行清理),一般程序都会处理SIGTERM信号。与SIGTERM信号不同,SIGKILL信号会粗暴的结束一个进程;

  • 许多守护进程会通过处理SIGHUP信号实现热加载配置文件。


使用 kill -l 命令会显示Linux支持的信号列表。其中编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。


下面对常用的信号进行说明:


  • SIGHUP(1)


当用户终端连接结束时,系统会像所有运行中的进程发出这个信号;通常在热加载配置文件时候也会使用该信号。wget命令就注册了SIGHUP(1)信号,这样就算你退出了Linux登录,wget也能继续下载文件。同样的,如Docker/Nginx/LVS等服务也会注册SIGHUP(1)信号,实现服务的热加载配置文件功能。


  • SIGINT(2)


程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl+C)时发出,用于通知前台进程组终止进程。


  • SIGQUIT(3)


和SIGINT类似,但由QUIT字符(通常是Ctrl+反斜杠)来控制。Nginx就是通过注册这个信号来实现优雅停止服务的。


  • SIGKILL(9)


立刻结束程序。该信号不能被阻塞、处理和忽略,不能在程序中被获取到。


  • SIGTERM(15)


程序结束(Terminate)信号,又叫请求退出信号,与SIGKILL不同的是该信号可以被阻塞和处理,我们可以通过在程序中注册该信号来实现服务的优雅停止。使用kill命令缺省会发出这个信号。


  • SIGCHLD(17)


子进程结束时,一般会向父进程发送这个信号。Nginx是个多进程程序,master进程和worker进程通信就使用的这个信号。


三、Docker容器常见退出码


Exit Code 1

  • 程序错误,或者Dockerfile中引用不存在的文件,如 entrypoint中引用了错误的包
  • 程序错误可以很简单,例如“除以0”,也可以很复杂,比如空引用或者其他程序 crash


Exit Code 137

  • 表明容器收到了SIGKILL信号,进程被杀掉,对应kill -9
  • 引发SIGKILL的是docker kill。这可以由用户或由docker守护程序来发起,手动执行:docker kill
  • 137 比较常见,如果 pod 中的limit 资源设置较小,会运行内存不足导致OOMKilled,此时state 中的”OOMKilled”值为true,你可以在系统的 dmesg 中看到 oom 日志


Exit Code 139

  • 表明容器收到了SIGSEGV信号,无效的内存引用,对应kill -11
  • 一般是代码有问题,或者 docker 的基础镜像有问题


Exit Code 143

  • 表明容器收到了SIGTERM信号,终端关闭,对应kill -15
  • 一般对应docker stop 命令
  • 有时docker stop也会导致Exit Code 137。发生在与代码无法处理SIGTERM的情况下,docker进程等待十秒钟然后发出SIGKILL强制退出。


不常用的一些 Exit Code

  • Exit Code 126: 权限问题或命令不可执行
  • Exit Code 127: Shell脚本中可能出现错字且字符无法识别的情况
  • Exit Code 1 或 255:因为很多程序员写异常退出时习惯用 exit(1) 或 exit(-1),-1 会根据转换规则转成 255。这个一般是自定义 code,要看具体逻辑


退出状态码的区间

  • 必须在 0-255 之间,0 表示正常退出
  • 外界将程序中断退出,状态码在 129-255
  • 程序自身异常退出,状态码一般在 1-128
  • 假如写代码指定的退出状态码时不在 0-255 之间,例如: exit(-1),这时会自动做一个转换,最终呈现的状态码还是会在 0-255 之间。我们把状态码记为 code,当指定的退出时状态码为负数,那么转换公式如下:256 – (|code| % 256)


对于被信号终止的进程,操作系统会将信号编号加上128,作为进程的退出状态码。这是因为在Unix系统中,信号编号的范围通常是1到31,而进程退出状态码的范围是0到255,为了区分正常的退出状态码和信号终止导致的退出状态码,就将信号编号加上128。


具体来说:


  • 如果一个进程接收到了信号编号为9(SIGKILL),那么它的退出状态码就是 128 + 9 = 137。
  • 如果一个进程接收到了信号编号为15(SIGTERM),那么它的退出状态码就是 128 + 15 = 143。


这种设计保证了在获取进程退出状态码时,可以区分出是正常退出(0到127范围内)还是信号终止(128到255范围内)。在Docker等容器环境中,这种区分特别重要,因为容器的退出状态码可以帮助用户或管理程序了解容器是如何结束的,从而采取相应的处理措施。


四、Docker容器服务对信号的支持


Docker对Linux Signal也做了很多的支持。


1、docker stop命令信号支持


当我们用docker stop命令来停掉容器的时候,docker默认会允许容器中的应用程序有10秒的时间用以终止运行。我们可以通过在执行docker stop命令时手动指定--time/-t参数来自定义一个stop时间长度。


→ docker stop --help
Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER…]
Stop one or more running containers
Options:
--help      Print usage
  -t, --time int  Seconds to wait for stop before killing it (default 10)

在docker stop命令执行的时候,会先向容器中PID为1的进程(main process)发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,如默认的10秒,会继续发送SIGKILL的系统信号强行kill掉进程。在容器中的应用程序,可以选择忽略和不处理SIGTERM信号,不过一旦达到超时时间,程序就会被系统强行kill掉。


2、docker kill命令信号支持


默认情况下,docker kill命令不会给容器中的应用程序有任何gracefully shutdown的机会,它会直接发出SIGKILL的系统信号以强行终止容器中程序的运行。


查看docker kill命令的帮助我们看到,除了默认发送SIGKILL信号外,还允许我们发送一些自定义的系统信号:


→ docker kill --help
Usage:  docker kill [OPTIONS] CONTAINER [CONTAINER…]
Kill one or more running containers
Options:
--help            Print usage
  -s, --signal string  Signal to send to the container (default "KILL")

比如,如果我们想向docker中的程序发送SIGINT信号,我们可以这样来实现:


docker kill --signal=SIGINT container_name


与docker stop命令不一样的地方在于,docker kill没有任何的超时时间设置,它会直接发送SIGKILL信号,或者用户指定的其他信号。


3、docker rm命令信号支持


docker rm命令用于删除已经停止运行的容器,我们可以添加--force或-f参数强行删除正在运行的容器。使用这个参数后,docker会先给运行中的容器发送SIGKILL信号,强制停掉容器,然后再做删除。


例如,强制删除正在运行的名称为web容器。


docker rm -fv web


4、docker daemon进程对信号支持(常用功能)


docker daemon进程会接收SIGHUP信号,接收后会重新reload daemon.json配置文件。


我们为dockerd进程发送一个SIGHUP信号:


root@vm10-1-1-28:~# kill -SIGHUP $(pidof dockerd)
root@vm10-1-1-28:~# 或者
root@vm10-1-1-28:~# systemctl reload docker

查看docker daemon的日志可以看到,docker daemon接收这个信号并重新reload daemon.json配置文件


root@vm10-1-1-28:~# journalctl -u docker.service -f
-- Logs begin at Sun 2018-01-07 09:17:01 CST. --
Jan 18 16:20:11 vm10-1-1-28.ksc.com dockerd[26668]: time="2018-01-18T16:20:11.262904839+08:00" level=info msg="Got signal to reload configuration, reloading from: /etc/docker/daemon.json"
Jan 18 16:21:41 vm10-1-1-28.ksc.com systemd[1]: Reloading Docker Application Container Engine.

所以在你修改完/etc/docker/daemon.json文件后,可以直接给Docker发送一个SIGHUP信号实现配置文件的reload,而不需要重启docker daemon。


注意:systemctl reload docker 命令通常不会导致机器上的容器重启。这个命令的作用是让 Docker 守护进程重新加载其配置文件,而不会中断正在运行的容器。它和 systemctl restart docker 是不同的,后者会停止并重新启动 Docker 服务,从而导致所有容器重启。


五、业务服务容器优雅停止案例


不论什么服务,要想实现优雅停止,都是希望在停止前告诉程序,让程序能有一定的时间处理、保存程序执行现场,优雅的退出程序。下面我们准备了一个通用案例,演示如何在程序中接收并处理TERM信号。


通过了解上面Docker容器服务对信号的支持我们知道,docker kill命令适用于强行终止程序并实现快速停止容器。而如果希望程序能够gracefully shutdown的话,docker stop才是不二之选,这样我们可以让程序在接收到SIGTERM信号后,有一定的时间处理(默认10秒)、保存程序执行现场,优雅的退出程序。


接下来我们写一个简单的Go程序来实现信号的接收与处理。程序在启动过后,会一直阻塞并监听系统信号,直到监测到对应的系统信号后,输出到控制台并退出执行。


// main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
    fmt.Println("Program started…")
    ch := make(chan os.Signal, 1)
// notify signal SIGTERM(15)
    signal.Notify(ch, syscall.SIGTERM)
// notify signal SIGINT(2)
    signal.Notify(ch, syscall.SIGINT)
    s := <-ch
switch {
case s == syscall.SIGINT:
        fmt.Println("SIGINT received!")
//Do something…
case s == syscall.SIGTERM:
        fmt.Println("SIGTERM received!")
//Do something…
    }
    fmt.Println("Exiting…")
}

接下来使用交叉编译的方式来编译程序,让程序可以在Linux下运行:


CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o graceful


编译好之后。我们做测试:


1、测试接收SIGTNT信号,在前台启动进程,然后输入Ctrl + C发送SIGINT(2)信号


zmc@ubuntu ~/r/g/s/edit> ./graceful
Program started…
^CSIGINT received!
Exiting…
zmc@ubuntu ~/r/g/s/edit>

2、测试接收SIGTERM信号


zmc@ubuntu ~/r/g/s/edit> ./graceful &
Program started…
zmc@ubuntu ~/r/g/s/edit> ps -ef | grep graceful
zmc  21223  21082  0 15:57 pts/21  00:00:00 ./graceful
zmc  21287  21082  0 15:57 pts/21  00:00:00 grep --color=auto graceful
zmc@ubuntu ~/r/g/s/edit> kill 21223
SIGTERM received!
Exiting…
“./graceful &” has ended
zmc@ubuntu ~/r/g/s/edit>

3、将上面程序打包到容器中运行


Dockerfile


FROM alpine:latest
ADD graceful /graceful
CMD ["/graceful"]

在处理SIGTERM信号常见的一个坑


我们都知道,通过在Dockerfle中使用CMD、ENTRYPOINT命令可以定义容器启动命令,关于这两个命令的区别这里就不讲了,我们只讲在使用时候一定要注意的问题。


这两个命令都支持下面几种格式:


shell 格式:CMD <命令>

exec 格式:CMD ["可执行文件", "参数1", "参数2"...]

参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。


一般推荐使用 exec格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号'。


如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:


CMD echo $HOME


在实际执行中,会将其变更为:


CMD [ "sh", "-c", "echo $HOME" ]


因此容器的主进程是sh,当给容器发送信号,接收信号的是sh进程,sh进程收到信号后会直接退出,自然就会令容器退出。我们的程序永远收不到信号。


注意:exec 格式这种写法避免了 Docker 自动将 CMD 转换为 sh -c 形式的操作,因为 JSON 数组格式的 CMD 已经明确指定了要执行的命令路径或文件。上面示例,docker在容器启动时会直接执行 /graceful(不包装任何参数)。


镜像打包过程:


zmc@ubuntu ~/r/g/s/edit> docker build -t graceful-golang-case:1.0.0 .
Sending build context to Docker daemon 1.953 MB
Step 1/4 : FROM alpine:latest
---> 3fd9065eaf02
Step 2/4 : LABEL maintainer "opl-xws@xiaomi.com"
---> Using cache
---> 6cc05b3f0ed0
Step 3/4 : ADD graceful /graceful
---> Using cache
---> 4a47b371a124
Step 4/4 : CMD /graceful
---> Using cache
---> f1841c0035af
Successfully built f1841c0035af
zmc@ubuntu ~/r/g/s/edit>

4、启动容器


zmc@ubuntu ~/r/g/s/edit> docker run -d --name graceful graceful-golang-case:1.0.0
08d871007b58e55e9552cff23960c80faf51bf8637014a745dec060b80ac9a6f
zmc@ubuntu ~/r/g/s/edit> docker ps
CONTAINER ID        IMAGE                                                    COMMAND            CREATED            STATUS              PORTS                    NAMES
08d871007b58        graceful-golang-case:1.0.0  "/graceful"        10 seconds ago      Up 9 seconds                                graceful
zmc@ubuntu ~/r/g/s/edit>

5、查看容器输出,能看到程序已经正常启动


zmc@ubuntu ~/r/g/s/edit> docker logs graceful
Program started…
zmc@ubuntu ~/r/g/s/edit>

6、接着我们要使用docker stop看程序能否响应SIGTERM信号


我们都知道docker stop默认在发出SIGTERM信号后的10秒钟,再发送SIGKILL信号强制停掉容器内所有进程,删除容器,假如我的程序处理很复杂,10秒内干不完清理工作,所以我在执行docker stop时自定义让2分钟后再强制kill掉我的容器:


zmc@ubuntu ~/r/g/s/edit> docker stop --time=120 graceful
graceful
zmc@ubuntu ~/r/g/s/edit> docker logs graceful
Program started…
SIGTERM received!
Exiting…
zmc@ubuntu ~/r/g/s/edit>

查看上面日志,我们可以看到,我们程序确实可以对Docker发来的SIGTERM信号进行处理。


六、总结


在Docker中,为了实现容器的优雅退出,确保你的应用程序能够接收和处理SIGTERM信号是至关重要的。推荐使用docker stop命令来停止容器,因为这会发送SIGTERM信号给容器内的进程,允许它们有机会完成未完成的任务并正常退出。相比之下,直接使用docker rm -f或docker kill可能会强制终止进程,导致数据丢失或不一致的状态。此外,在Dockerfile中设置ENTRYPOINT或CMD时,应使用exec格式,以确保信号正确传递给应用程序。



作者丨人艰不拆_zmc

来源丨网址:https://www.cnblogs.com/zhangmingcheng/p/18252004

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

相关推荐

教你一个解决手机卡顿的方法(10秒解决手机卡顿问题)

我们的手机天天刷头条,看视频,用了一阶段时间以后,就时不时的发生卡顿现象。昨天我的手机就发现了这个问题。友友们,你们遇到过这样的问题吗?你们都是怎样解决的?我看了一眼我的粉丝情况,头条君给我分析的很精...

手机视频缓存清理,3步彻底清空,告别卡顿

在我们使用手机观看视频的过程中,经常会产生大量的缓存垃圾,这些垃圾文件不仅占用了手机的存储空间,还可能导致手机卡顿和运行缓慢。然而,你知道如何彻底清空手机的视频缓存,让手机恢复流畅的使用体验吗?在本文...

关手机这个开关,轻松提升流畅度!

关闭手机这个开关,跟新买的一样流畅。手机不要再清理垃圾了,只要关闭这个开关,手机就会和新买的差不多,丝滑流畅不卡顿。其实抖音里就隐藏着一个小开关,每天刷过的视频都会保存在手机里,如果一直不清理,手机就...

如何清理今日头条和西瓜视频的内存,让手机流畅不卡顿?

对于老年人而言,今日头条和西瓜视频能带来丰富的资讯与娱乐。然而,随着使用时间的增加,这些应用会占用大量手机内存,致使手机运行卡顿。那该如何解决呢?接下来,我将用最简单易懂的方式教老年人清理今日头条和西...

视频在线如何转换格式?好用不卡顿的三种转换办法

转换视频格式目前来说已经是很熟练的操作了,但是还有些用户可能还是不知道,小编今天就特意给大家带来一些小众才知道的转换教程,让新手也能快速的上手去转换视频格式,以后获取到视频就不怕内容丢失了,视频的格式...

如何把视频慢放处理?这几个慢放方法记得收藏

如何把视频慢放处理?如果你想让视频慢放,可能是因为你想放慢一些精彩的瞬间,或者你想制作一个慢动作视频。在这篇文章中,我们将介绍一些调速方法,这些方法可以有效地调整视频速度,一起来学习一下吧。方法一:使...

如何清理看过的视频,释放垃圾,让手机更流畅?

现在谁的手机上没几个短视频平台,无聊时就会刷别人的视频。可您知道吗?我们看过的内容都会被自动保存在手机里,而且很耗内存。如果长时间不释放,手机就会出现各种问题,其中最突出的就是反应慢。相信很多老年人的...

手机掉帧是怎么回事?刷视频的时候经常掉帧卡顿

手机掉帧是指在运行应用或视频时,画面出现卡顿、不流畅的现象,通常由硬件性能不足、软件优化不佳、内存占用过高、网络问题或设备过热等因素引起。尤其是在刷视频时,掉帧问题可能更为明显,以下是具体原因及解决方...

拍视频画面卡顿不流畅,原来是相机设置错误 #短视频拍摄

拍摄视频时,应该选择哪种快门速度?许多新手朋友可能会认为,快门速度越高,画面就越清晰,实则不然。因为拍摄视频时,需要考虑一个问题,即动态模糊。例如,如果设置为24帧/秒,那么每秒钟会拍摄24张图片。如...

手机卡顿最大原因#视频太卡怎么变流畅

抖音这几个开关是手机卡顿的最大原因。你是不是也会经常遇到刷视频的时候,打开一个视频之后老半天还在那转着圈圈,总觉得手机没有之前流畅了。这就说明你的手机占用的内存太多了,导致手机卡顿,使用不流畅。使用手...

为啥你家的玩游戏和刷视频经常性的会卡,那是你不懂这些小妙招

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:暴走的黄小猪说到网速有不少的值友都有一个共同点,那就是“卡”,那是你根本没体验过啥叫真正的网速啊,全屋零四条网络报表也花不了几个钱你们的方法...

电脑看视频卡顿有什么解决方法?(电脑看视频画面卡顿是什么原因)

电脑看视频卡顿的原因可能多种多样,包括硬件性能不足、网络问题、软件设置不当等。以下是一些常见的解决方法,帮助你改善视频播放的流畅度:一、硬件方面1.检查硬件性能:如果电脑配置较低,尤其是CPU、内存或...

手机Wi-Fi满格但视频卡顿,你需要这样解决

累了一天的打工人回家拿出手机准备玩玩游戏,看看电影时,发现网络异常卡顿,但手机又显示Wi-Fi信号满格,当咱们遇到此类问题时,这些动作能让网络恢复正常,方法如下。一、重启路由器和光猫很多家庭在安装好路...

视频越刷越卡?原来是路由器开启了这个功能,关闭方法来了

应该很多小伙伴都有过类似的经历,就是在家里长时间刷视频或者看剧的时候,网速好像会越来越慢,视频总是要加载。手机本身可能是一部分原因,但路由器也会影响,你知道吗?当我们在刷视频的,路由器会悄悄地开启大量...

一招解决视频卡顿的问题,改变发布渠道后,结果香了

最近一段时间拍了很多美景视频,编辑发布到头条后,有时一直显示在缓冲,播放不了,有时打开断断续续的,老是卡顿。导致的后果是:要么展现量很低,要么阅读量寥寥无几,这让我非常苦恼。所以再发布作品时,我只好文...

取消回复欢迎 发表评论: