问题:docker stop 容器ID
容器中的进程是如何退出的?
验证一下:
c 语言程序作为 init 进程,fork 一个子进程。使用上面的 c 程序作为容器 init 进程,构建镜像。
启动容器:
1 | [root@VM-0-2-centos fwd_sig]# docker run -d --name fwd_sig_1 fwd_sig /c-init-sig |
宿主机上查看容器中两个进程的 PID:
1 | [root@2ed370d83b45 /]# ps -ef | grep c-init-sig |
使用 strace 来监控 init 进程和另一个进程收到的信号情况:
1 | # 终端 1, strace -p <init进程pid> |
init 进程收到 SIGTERM 信号退出,这时内核处理进程退出的入口点是 do_exit()
函数,do_exit()
会释放进程的相关资源(内存、文件句柄、信号量等)。在做完这些工作之后,它会调用一个 exit_notify() 函数,用来通知和这个进程相关的父子进程等。
对于容器来说,还要考虑 Pid Namespace 里的其他进程。这里调用的就是 zap_pid_ns_processes() 这个函数,而在这个函数中,如果是处于退出状态的 init 进程,它会向 Namespace 中的其他进程都发送一个 SIGKILL 信号。
1 |
|
SIGKILL 信号是个特权信号,是无法捕获的,那么如何让容器中进程 graceful shutdown?或者说怎样让容器中的其他进程收到 SIGTERM 信号?
转发。
init 进程将收到的 SIGTERM 信号转发给子进程。比如 Docker Container 中使用的 tini 作为 init 进程,tini 会调
用 sigtimedwait()
来查看自己收到的信号然后调用 kill()
转发给子进程。
init 进程自己退出,还是会调用do_exit()的。所以呢,为了保证子进程先收到转发的SIGTERM, 类似tini的做法是,自己在收到SIGTERM的时候不退出,转发SIGTERM给子进程,子进程收到SIGTERM退出之后会给父进程发送SIGCHILD, tini是收到SIGCHILD之后主动整个程序退出。
1 |
|
胖容器/富容器。
胖容器的init进程其实是一个bash脚本run.sh, 由它来启动jvm的程序。
但是run.sh本身没有注册SIGTERM handler, 也不forward SIGTERM给子进程jvm。
当stop容器的时候,run.sh先收到一个SIGTERM, run.sh没有注册SIGTERM, 所以呢对SIGTERM没有反应,contaienrd过30秒,会发SIGKILL给run.sh, 这样run.sh退出do_exit(),在退出的时候同样给子进程jvm程序发送了SIGKILL而不是SIGTERM。其实呢,jvm的程序是注册了SIGTERM handler的,但是没有机会调用handler了。
EOF