利用 trap 在 docker 容器优雅关闭前执行环境清理
当一个运行中的容器被终止时,如何能够执行一些预定义的操作,比如在容器彻底退出之前清理环境。这是一种类似于 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 | 不可以 |
SIGTERM 是 kill 命令默认发送的信号。当用户请求终止进程时,会产生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 容器优雅关闭前执行环境清理的更多相关文章
- Docker 容器优雅终止方案
原文链接:Docker 容器优雅终止方案 作为一名系统重启工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复 ...
- 利用 ELK 搭建 Docker 容器化应用日志中心
利用 ELK 搭建 Docker 容器化应用日志中心 概述 应用一旦容器化以后,需要考虑的就是如何采集位于 Docker 容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 ...
- 使用pipework将Docker容器配置到本地网络环境中
使用pipework将Docker容器配置到本地网络环境中 需求 在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主 ...
- 海纳百川无所不容,Win10环境下使用Docker容器式部署前后端分离项目Django+Vue.js
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_179 随着现代化产品研发的不断推进,我们会发现,几乎每个产品线都会包含功能各异的服务,而且服务与服务之间存在也会存在着错综复杂的依 ...
- 详解利用ELK搭建Docker容器化应用日志中心
概述 应用一旦容器化以后,需要考虑的就是如何采集位于Docker容器中的应用程序的打印日志供运维分析.典型的比如SpringBoot应用的日志 收集.本文即将阐述如何利用ELK日志中心来收集容器化应用 ...
- 利用pipework为docker容器设置固定IP
今天介绍如何在redhat/centos7系列机器上使用pipework为docker启动的容器指定一个固定ip,我们知道默认情况下,docker会使用 bridge网络模式为每一个启动的容器动态分配 ...
- Docker容器优雅重启
默认情况下,当 Docker 守护进程终止时,它将关闭正在运行的容器.您可以配置守护程序,以便容器在守护程序不可用时保持运行.此功能称为live-restore.live-restore选项有助于减少 ...
- 使用pipework将Docker容器桥接到本地网络环境中
在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主机的网卡桥接起来,再给Docker容器配上IP就可以了.pipew ...
- 利用dotnet-dump分析docker容器内存泄露
目录 一 运行官方示例 1,Clone代码并编译 2,创建Dockerfile构建镜像 3,启动容器 二 生成dump转储文件 1,制造问题 2,创建dump文件 三 分析dump文件 1,创建一个用 ...
随机推荐
- [loj2393]门票安排
为了方便,不妨假设$a_{i}\le b_{i}$,并将问题转换为以下形式: $\forall 1\le i\le m$,将$[a_{i},b_{i})$或$[1,a_{i})\cup [b_{i}, ...
- [bzoj2789]Letters
考虑A中第i次出现的j字符,最终位置一定是在B中第i次出现的j字符的位置,然后即求逆序对数量,cdq/线段树即可 1 #include<bits/stdc++.h> 2 using nam ...
- c语言是如何解析表达式语句"2+3*4;"的?
1. 要编译的测试代码: int main(void) { 2+3*4; } 2. 词法分析 词法分析将字符变成token,其中很重要的是token的类型,如字符2的token类型为TK_NUM,这 ...
- C/C++ Qt 给ListWidget增加右键菜单
在上一篇博文<C/C++ Qt ListWidget 列表框组件应用>中介绍了ListWidget组件的基本使用技巧,本次将给ListWidget组件增加一个右键菜单,当用户在ListWi ...
- BehaviorTree.CPP行为树BT的介绍(一)
节点类型 ControlNode是可以具有1到N个子节点的节点.一旦接收到tick,tick可以传播到一个或多个子节点. DecoratorNodes与ControlNode相似,但只能有一个子节点. ...
- Redis | 第7章 Redis 服务器《Redis设计与实现》
目录 前言 1. 命令请求的执行过程 1.1 发送命令请求 1.2 读取命令请求 1.3 命令执行器(1):查找命令实现 1.4 命令执行器(2):执行预备操作 1.5 命令执行器(3):调用命令的实 ...
- c++基础知识-数据类型
1.每次新建项都可需写内容 #include <iostream> using namespace std; int main() //main函数有且只有一个 { system(&quo ...
- R语言与医学统计图形-【19】ggplot2坐标轴调节
ggplot2绘图系统--坐标轴调节 scale函数:图形遥控器.坐标轴标度函数: scale_x_continous scale_y_continous scale_x_discrete scale ...
- 如何利用nrfjprog.exe读写nrf51的flash
版权声明:本文为博主原创文章,未经博主允许不得转载. 1.目的 为了方便平时在开发中的调试,验证一些想法是否正确. 2.平台: Jlink version:v5.02c nrf51822硬件板等. ...
- 25-ZigZag Conversion
The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like ...