Posted in

如何安全又优雅地退出、删除 Docker 容器?_AI阅读总结 — 包阅AI

包阅导读总结

1. 关键词:Docker 容器、信号、优雅停止、删除、配置文件

2. 总结:本文介绍了与 Docker 容器相关的 Linux 常见信号,包括不同信号的作用,如 SIGHUP、SIGINT 等。重点阐述了 Docker 容器安全优雅退出和删除的方法,如 docker stop 与 docker kill 命令的使用,还通过示例展示了程序中接收和处理信号的方法。

3. 主要内容:

– Linux 信号简介:

– 信号是进程间通信形式,内核发送消息告知进程发生事件。

– 列举常见信号及其作用,如 SIGHUP、SIGINT 等。

– Docker 对信号的支持:

– docker stop 允许容器应用程序有 10 秒终止运行,可自定义超时时间。

– docker kill 直接强行终止容器运行,可发送自定义信号。

– docker rm 可删除停止运行的容器,加参数可强行删除运行中的容器。

– Docker daemon 进程对信号支持:接收 SIGHUP 信号重新加载 daemon.json 配置文件。

– 通用案例:

– 编写 Go 程序接收和处理 TERM 信号。

– 进行编译和测试,包括接收 SIGINT 和 SIGTERM 信号。

– 指出 Dockerfile 中 CMD 等命令的格式及注意事项。

– 进行镜像打包和运行测试,使用 docker stop 自定义超时时间观察程序对 SIGTERM 信号的处理。

思维导图:

文章地址:https://mp.weixin.qq.com/s/ADIGDHw1pHUzNaJ80cvrHw

文章来源:mp.weixin.qq.com

作者:人艰不拆_zmc

发布时间:2024/8/7 11:05

语言:中文

总字数:4932字

预计阅读时间:20分钟

评分:85分

标签:Docker,Linux信号,容器管理,优雅停止,退出码


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

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

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

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

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

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

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

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

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

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

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

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

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

→ docker stop Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER…]Stop one or more running containersOptions:  -t, 

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

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

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

→ docker kill Usage:  docker kill [OPTIONS] CONTAINER [CONTAINER…]Kill one or more running containersOptions:  -s, 

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

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

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

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

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

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

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

root@vm10-1-1-28:~root@vm10-1-1-28:~root@vm10-1-1-28:~

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

root@vm10-1-1-28:~-- 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程序来实现信号的接收与处理。程序在启动过后,会一直阻塞并监听系统信号,直到监测到对应的系统信号后,输出到控制台并退出执行。

package mainimport ("fmt""os""os/signal""syscall")func main() {    fmt.Println("Program started…")    ch := make(chan os.Signal, 1)    signal.Notify(ch, syscall.SIGTERM)    signal.Notify(ch, syscall.SIGINT)    s := <-chswitch {case s == syscall.SIGINT:        fmt.Println("SIGINT received!")case s == syscall.SIGTERM:        fmt.Println("SIGTERM received!")    }    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> ./gracefulProgram started…^CSIGINT received!Exiting…zmc@ubuntu ~/r/g/s/edit>

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

Dockerfile

FROM alpine:latestADD graceful /gracefulCMD ["/graceful"]

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

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

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

shell 格式:CMD <命令>

exec 格式:CMD [“可执行文件”, “参数1”, “参数2″…]

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

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

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

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

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 MBStep 1/4 : FROM alpine:latest---> 3fd9065eaf02Step 2/4 : LABEL maintainer "opl-xws@xiaomi.com"---> Using cache---> 6cc05b3f0ed0Step 3/4 : ADD graceful /graceful---> Using cache---> 4a47b371a124Step 4/4 : CMD /graceful---> Using cache---> f1841c0035afSuccessfully built f1841c0035afzmc@ubuntu ~/r/g/s/edit>

zmc@ubuntu ~/r/g/s/edit> docker run -d --name graceful graceful-golang-case:1.0.008d871007b58e55e9552cff23960c80faf51bf8637014a745dec060b80ac9a6fzmc@ubuntu ~/r/g/s/edit> docker psCONTAINER ID        IMAGE                                                    COMMAND            CREATED            STATUS              PORTS                    NAMES08d871007b58        graceful-golang-case:1.0.0  "/graceful"        10 seconds ago      Up 9 seconds                                gracefulzmc@ubuntu ~/r/g/s/edit>

zmc@ubuntu ~/r/g/s/edit> docker logs gracefulProgram 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 gracefulgracefulzmc@ubuntu ~/r/g/s/edit> docker logs gracefulProgram started…SIGTERM received!Exiting…zmc@ubuntu ~/r/g/s/edit>

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