当一个运行中的容器被终止时,如何能够执行一些预定义的操作,比如在容器彻底退出之前清理环境。这是一种类似于 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. [bzoj5510]唱跳rap和篮球

    显然答案可以理解为有(不是仅有)0对情况-1对情况+2对情况-- 考虑这个怎么计算,先计算这t对情况的位置,有c(n-3t,t)种情况(可以理解为将这4个点缩为1个,然后再从中选t个位置),然后相当于 ...

  2. 阿里云服务器的MySQL连接和vscode远程连接

    目录 一.前言 二.使用Navicat等软件连接MySQL 1. 修改服务器系统密码 2. 防火墙选项添加MySQL 3. 使用Navicat连接 三.使用vscode连接服务器 一.前言 双十一的时 ...

  3. nrf52810/52832开发板能跑,自己的PCB不能跑的原因

    1.PCB图片对比 这是我的开发板原理图: 这是我画的PCB原理图: 发现其实开发板上就是比我的多了两个外部晶振. 例程代码一般都是用外部晶振作为时钟,所以用例程的代码跑不通我的PCB. 2.解决办法 ...

  4. CSS动画--让div动起来

    CSS动画 今天在写代码时候,遇到了css动画效果如何实现的问题,经过查阅和实践,总结出一下结论. transition transition 指定动画变化的对应属性 以及动画的执行时间. 例如:tr ...

  5. SpringCloud微服务实战——搭建企业级开发框架(二十五):实现多租户多平台短信通知服务

    目前系统集成短信似乎是必不可少的部分,由于各种云平台都提供了不同的短信通道,这里我们增加多租户多通道的短信验证码,并增加配置项,使系统可以支持多家云平台提供的短信服务.这里以阿里云和腾讯云为例,集成短 ...

  6. c# Quartzs定时器的简单使用

    使用背景: 首先想到倒计时,定时任务.大家想到的肯定就是定时器.那么定时器在web和winfrom程序中有着很大的作用.那在服务器端有没有像定时器一样的存在呢. 有了这些需求,我找到了第三方的组件 Q ...

  7. AtCoder Grand Contest 055 题解

    A 赛时直到最后 10min 才做出这个 A 题,之前猜了一个结论一直没敢写,本来不抱啥希望 AC 的结果比赛结束时交了一发竟然 A 了,由此可见我的水平之菜/dk 考虑每次取出字符串开头字符,不妨设 ...

  8. Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

    Atcoder 题面传送门 & 洛谷题面传送门 震惊,我竟然能独立切掉 AGC E 难度的思维题! hb:nb tea 一道 感觉此题就是找性质,找性质,再找性质( 首先看到排列有关的问题,我 ...

  9. python故障

    问题: ImportError: No module named dns.resolver 解决: 通过包管理工具pip安装dnspython pip install dnspython

  10. ZAQI

    mysql> CREATE TABLE emploee ( -> name CHAR(64) NOT NULL, -> email CHAR(64), -> password ...