缘起:
docker stop 时往往会比较慢,起原因多半是docker 容器的init进程没有正确处理信号所致;docker stop的行为为: 先发送一个SIGTERM(15) 信号给init进程,如果一段时间(默认10s)后,依然没有退出,就直接发送 -9 信号,常常我们会登上10s,最终,进程的退出依然是仓促的;所以,实现一个好的init进程非常重要。
想用bash写一个docker 的init进程,当收到SIGTERM信号时,通知容器中的其他进程退出,其他进程退出后自己再退出,这样的话,其他进程可以有时间优雅退出,而且stop操作还可能很快完成。
版本1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/bin/bash trap stop 15 PATH=$PATH:/usr/sbin/:/usr/bin function stop() { while :; do local pid=$(pidof cmd) [[ "$pid" == "" ]] && break kill $pid 2>/dev/null sleep 1 done exit 0 } cmd |
实际并不凑效; 原因: bash 在执行外部命令的时候,是不处理信号的
改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/bin/bash trap stop 15 PATH=$PATH:/usr/sbin/:/usr/bin function stop() { while :; do local pid=$(pidof cmd) [[ "$pid" == "" ]] && break kill $pid 2>/dev/null sleep 1 done } nohup cmd & while :; do sleep 10 done |
不足:当有信号到时,依然要等sleep 睡足才能处理信号,如果每次sleep 时间足够短,则总是启动sleep进程也非常不优雅,本身总是看到一个sleep进程已经够闹心的了
改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/bin/bash trap stop 15 PATH=$PATH:/usr/sbin/:/usr/bin function stop() { while :; do local pid=$(pidof cmd) [[ "$pid" == "" ]] && break kill $pid 2>/dev/null sleep 1 done } nohup cmd & while :; do read line done |
优点:
- 看不到sleep函数了
- read line 并不消耗多少资源
- 虽然 read 函数是阻塞的,但是却不影响bash实时处理信号
缺点:
- 创建容器时需要 -it 选项,因为bash标准输入需要是正常的,才能是的read不会失败而正常阻塞,否则,read会一直失败,导致死循环占用100% CPU ; 安全一些的做法是,发现read失败直接退出
测试脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/bash trap signal 15 function signal() { echo handle signal } while :; do read line echo $line sleep 10 done |
执行该脚本,然后发送信号15给bash进程,发现:
- 当执行到sleep 10 的时候,不能实时处理信号
- 当阻塞在read时,可以实时处理信号
下面这种写法也比较简单,可以实时处理信号,但是,这个bash进程会fork一个子进程,从top中看起来不够干净。
1 |
CMD [ "/bin/sh", "-c" , "trap 'exit' TERM; { while :; do sleep 100 ;done } & wait" ] |