当一个运行中的容器被终止时,如何能够执行一些预定义的操作,比如在容器彻底退出之前清理环境。这是一种类似于 pre stop 的钩子体验。但 docker 本身无法提供这种能力,本文结合 Linux 内置命令 trap ,实现在容器优雅关闭之前,可以执行自定义的操作。

如何关闭容器

我了解有三种方式可以关闭一个正在运行中的容器,三者都是由 docker 命令行发起的。

  • 第一种是较为优雅的方式 docker stop ContainerID
  • 第二种看起来就比较武断 docker rm -f ContainerID
  • 第三种用的人会少很多 docker kill --signal=KILL ContainerID

docker 的设计者自然不会平白无故的设计三种命令组合来做关闭容器这件事,三种方式都应该在什么场景下被使用呢?

这三种终止容器的方式之间是略有不同的,在讲解这些不同之前,需要提及一些看似和容器不相关的知识点——SIGNAL 。

进程与信号

用户是可以通过发送信号,来和进程通信的。

基本上每一个运维工程师都执行过如下命令来杀死一个进程:

kill -9 PID

这个命令看起来恰如其分,我 "杀死" 了一个进程,但是,为什么是 "-9" ?

9 是信号 SIGKILL 的代号,上述命令实际上是向对应的进程发送了一个信号,一个可以杀死进程的信号。

kill 命令的真正意义,是向进程发送指定的信号,除了SIGKILL(9) 之外,还可以发送其他多种信号:

root@ubuntuserver:~# kill --help
kill: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
Send a signal to a job.
root@ubuntuserver:~# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

我无意去详解每一个信号的意义,我的功力还差得远,在这里只拣取和我们主题相关的知识来进行阐述。

有两个信号和我们的主题相关, SIGTERM. SIGKILL

信号名称 代号 可否被捕获或忽略
SIGTERM 15 可以
SIGKILL 9 不可以

SIGTERMkill 命令默认发送的信号。当用户请求终止进程时,会产生SIGTERM信号。SIGTERM信号可以被捕获或无视。这允许该进程在结束前释放掉所占用的资源并保存其状态。

SIGKILL 发送SIGKILL信号到一个进程可以使其立即终止(KILL)。与SIGTERM不同的是,这个信号不能被捕获或忽略,接收过程在接收到这个信号时不能执行任何清理。但有时候 kill -9 并非一定可以杀死进程,释放资源。还是有一些特殊情况:

  • 僵尸进程不能被杀死,因为它们已经死了,正在等待它们的父进程来收获它们。
  • 处于阻塞状态的进程不会死亡,直到它们再次醒来。
  • init 进程是特殊的: init不接收任何它不打算处理的信号,因此它会忽略SIGKILL。这条规则有一个例外,Linux 上的 init 如果被 ptrace 了,那么它是可以接收 SIGKILL 并被杀死的。
  • 处于不可中断的睡眠的进程即使发送了SIGKILL,也有可能不会终止(并释放其资源)。这是少数 Unix 系统必须重新启动才能解决临时软件问题的几种情况之一。

容器与信号

容器的本质,是一组被封装起来的进程。所以通过开头讲到的三种命令行方式关闭一个运行中的容器,其本质也是在通过发送信号的方式与容器中的进程进行交互,使之被 "杀死" 的过程。

  • docker stop

执行 docker stop ContainerID ,会向容器中的主进程先发送一个 SIGTERM 信号,在一段时间的宽限期后,发送 SIGKILL 信号彻底杀死容器。

Docker 手册原文如下:

The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL

  • docker rm -f

执行 docker rm -f ContainerID ,会向容器中的主进程直接发送SIGKILL 信号,在容器杀死之后,也会把容器删除掉。从删除容器这个操作看来,这个命令是用来删除一个已停止的容器,而非用于停止运行中的容器。

  • docker kill

执行 docker kill --signal=KILL ContainerID ,是专门向容器主进程发送各种自定义信号的方式。换言之,它就是面向容器的 kill 命令。当前命令是在向容器主进程发送一个 SIGKILL 信号。

通过比对,docker rm -f ContainerID 这种方式是不应该用于停止运行中容器的。而剩余两种方式之间, docker stop ContainerID 也明显要优雅一些,它既可以保证容器会被最终杀死,也会提供 SIGTERM 供用户后续捕获处理。

接下来终于要进入正题了。

捕获信号并处理

信号 SIGTERM 是一种可以被捕获的信号。当容器主进程捕获到这个信号之后,可以触发事先设计好的逻辑,在彻底退出之前完成预定的任务。比如可以执行环境的清理、数据的保存、关闭其他不受主进程控制的进程等等。在某些场景下,这种需求非常突出。

Linux 提供内置的 trap 命令,负责捕获信号,并确保在进程彻底退出前,执行某些任务。

root@ubuntuserver:~# trap --help
trap: trap [-lp] [[arg] signal_spec ...]
Trap signals and other events.

其基本的使用方式如下:

trap do_some_things SIGSPEC

思路已经清晰了,我们需要在容器的启动脚本中,加入 trap 指令,来完成容器在退出前需要做的所有事情。

以下是一个脚本示例,这个脚本被作为容器的入口(ENTRYPOINT)执行。

#!/bin/bash

function clean_up_term {
rm -rf /data/tmp
echo "clean_up_term in execution"
} trap clean_up_term SIGTERM for ((i=1;i<=1000;i++))
do
echo "Wait for $i"
sleep 1
done

容器启动后,从其他终端执行了 docker stop ContainerID 命令,可以观察到以下结果。

guox@MacBook-Pro-For-Guox: /Users/guox/GitHub/test-plugin git:(master) ✗
➜ docker run -ti --name=clean -v $(pwd)/data:/data clean
Wait for 1
Wait for 2
Wait for 3
Wait for 4
Wait for 5
Wait for 6
Wait for 7
Wait for 8
Wait for 9
Wait for 10
Wait for 11
Wait for 12
Wait for 13
clean_up_term in execution
Wait for 14
Wait for 15
Wait for 16
Wait for 17
Wait for 18
Wait for 19
Wait for 20
Wait for 21
Wait for 22
Wait for 23
guox@MacBook-Pro-For-Guox: /Users/guox/GitHub/test-plugin git:(master) ✗

信号 SIGTERM 的确被容器捕获,并进行了相关的清理操作。 docker stop ContainerID 提供了一段宽限期,所以在执行了清理操作后,容器主进程还是继续执行了一会才退出。

利用 trap 在 docker 容器优雅关闭前执行环境清理的更多相关文章

  1. Docker 容器优雅终止方案

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

  2. 利用 ELK 搭建 Docker 容器化应用日志中心

    利用 ELK 搭建 Docker 容器化应用日志中心 概述 应用一旦容器化以后,需要考虑的就是如何采集位于 Docker 容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 ...

  3. 使用pipework将Docker容器配置到本地网络环境中

    使用pipework将Docker容器配置到本地网络环境中 需求 在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主 ...

  4. 海纳百川无所不容,Win10环境下使用Docker容器式部署前后端分离项目Django+Vue.js

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_179 随着现代化产品研发的不断推进,我们会发现,几乎每个产品线都会包含功能各异的服务,而且服务与服务之间存在也会存在着错综复杂的依 ...

  5. 详解利用ELK搭建Docker容器化应用日志中心

    概述 应用一旦容器化以后,需要考虑的就是如何采集位于Docker容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 收集.本文即将阐述如何利用ELK日志中心来收集容器化应用 ...

  6. 利用pipework为docker容器设置固定IP

    今天介绍如何在redhat/centos7系列机器上使用pipework为docker启动的容器指定一个固定ip,我们知道默认情况下,docker会使用 bridge网络模式为每一个启动的容器动态分配 ...

  7. Docker容器优雅重启

    默认情况下,当 Docker 守护进程终止时,它将关闭正在运行的容器.您可以配置守护程序,以便容器在守护程序不可用时保持运行.此功能称为live-restore.live-restore选项有助于减少 ...

  8. 使用pipework将Docker容器桥接到本地网络环境中

    在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主机的网卡桥接起来,再给Docker容器配上IP就可以了.pipew ...

  9. 利用dotnet-dump分析docker容器内存泄露

    目录 一 运行官方示例 1,Clone代码并编译 2,创建Dockerfile构建镜像 3,启动容器 二 生成dump转储文件 1,制造问题 2,创建dump文件 三 分析dump文件 1,创建一个用 ...

随机推荐

  1. Java安全之Axis漏洞分析

    Java安全之Axis漏洞分析 0x00 前言 看到个别代码常出现里面有一些Axis组件,没去仔细研究过该漏洞.研究记录一下. 0x01 漏洞复现 漏洞版本:axis=<1.4 Axis1.4 ...

  2. 互联网java面试宝典

    1.为什么使用消息队列啊? 答题: 消息队列的核心功能就是:解耦合,异步,流量削峰解耦:接口调用发送,那如果E系统也要这个数据呢?那如果C系统现在不需要了呢?现在A系统又要发送第二种数据了呢?A系统负 ...

  3. CURL常用参数

    1. CURL简介 cURL是一个利用URL语法在命令行下工作的文件传输工具.它支持文件上传和下载,是综合传输工具.cURL就是客户端(client)的URL工具的意思. 2. 常用参数 -k:不校验 ...

  4. plyr包使用

    #-------------------------------- # plyr包使用# 建议直接保存为R文件到Rstudio中运行 #-------------------------------- ...

  5. expr计算字符串长度

    命令:expr length "quanzhiqinag" #!/bin/bash for N in quan zhi qiang do if [ `expr length $N ...

  6. mysql 多表关联查询

    多个表右链接查询 名字,学校名称,学校类型,城市名称,国家地区 左链接查询 子查询 索引 #创建MySQL时添加索引 mysql> create table userIndex( id int ...

  7. day12 函数嵌套

    day12 函数嵌套 一. args与kwargs def index(a,b,c): print(a,b,c) def wrapper(*args,**kwargs): # args=(1,2,3) ...

  8. day02 Rsyuc备份服务器

    day02 Rsyuc备份服务器 一.备份 1.什么是备份 备份就是把重要的数据或者文件复制一份保存到另一个地方,实现不同主机之间的数据同步 一般数据比较重要的情况下,数据如果丢失很容易找不回来了的, ...

  9. C语言大小端判定

    要判定大小端?需要弄清以下几个问题: 1.当一个变量占多个字节时,变量的指针指向的是低地址 2.什么是大小端? 大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中. 小 ...

  10. 零基础学习java------day27-28---------电影评分数据案例,. RPC案例

    一.  电影评分数据案例 movie:电影id rate:用户评分 timeStamp:评分时间 uid:用户id 简化数据: 需求: (1)每个用户评分最高的3部电影 (2)每个用户评分的平均值 ( ...