一般来说一个容器的生命周期是从run开始到stop结束的。 很多人在容器不用了以后就调用docker stop命令将其关闭。 例如: sudo docker pull centos:centos6 sudo docker run -t -i centos:centos6 /bin/bash 可以用docker ps查到启动成功的container的id为daf3ae88fec2 sudo docker stop daf3ae88fec2 可以直接关闭容器进程 这样看起来没有任何问题,容器进程也相应的退出了。但是真的是这样子的么,查看stop 的usage如下:
$ sudo docker stop –help
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
Stop a running container by sending SIGTERM and then SIGKILL after a grace period
-t, –time=10 Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.
可以看出当我们执行docker stop时,docker会首先向容器内的当前主程序发送一个SIGTERM信号,用于容器内程序的退出。如果容器在收到SIGTERM后没有马上退出, 那么stop命令会在等待一段时间(默认是10s)后,再向容器发送SIGKILL信号,将容器杀死,变为退出状态。 这里发现一个潜在的坑,即如果SIGTERM没有把容器内的程序杀死的话。过10s以后docker就会把容器直接kill掉,这个时候容器内部的进程很有可能成为一个孤儿进程。 将这一假象用一个多线程程序来试验一下: 1.编写一个多线程的服务:
func main() {
go sysSignalHandleDemo()
pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if err != 0 {
fmt.Printf("err fork process, err: %v\n", err)
return
}
if pid == 0 {
fmt.Printf("i am in child process, pid = %v\n", syscall.Getpid())
time.Sleep(time.Hour) // make the child process wait
}
fmt.Printf("i am parent process, pid = %v\n", syscall.Getpid())
fmt.Printf("fork ok, childpid = %v\n", pid)
time.Sleep(time.Hour) // make the main goroutine wait!
}
func sysSignalHandleDemo() {
ss := signalSetNew()
handler := func(s os.Signal, arg interface{}) {
fmt.Printf("%v: handle signal: %v\n", syscall.Getpid(), s)
if s == syscall.SIGTERM {
fmt.Printf("%v: signal termiate received, app exit normally\n", syscall.Getpid())
os.Exit(0)
}
}
ss.register(syscall.SIGINT, handler)
ss.register(syscall.SIGUSR1, handler)
ss.register(syscall.SIGUSR2, handler)
ss.register(syscall.SIGTERM, handler)
for {
c := make(chan os.Signal)
var sigs []os.Signal
for sig := range ss.m {
sigs = append(sigs, sig)
}
signal.Notify(c)
sig := <-c
err := ss.handle(sig, nil)
if err != nil {
fmt.Printf("%v: unknown signal received: %v, app exit unexpectedly\n", syscall.Getpid(), sig)
os.Exit(1)
}
}
}
2.写一个DockerFile如下,还是用centos6作为基础镜像,在里面启动上面的多线程的服务。建立~/ImagesFactory目录
进入~/ImagesFactory目录,创建Dockerfile文件,Dockerfile内容如下:
FROM centos:centos6
COPY ./dockerapp /bin/
CMD /bin/dockerapp
3.构建镜像
sudo docker build -t="test:1" ./ 4.启动容器 sudo docker run -d "test:1"
5.通过docker logs查看dockerapp的输出:
$ sudo docker logs 781cecb4b362
i am parent process, pid = 1
fork ok, childpid = 13
i am in child process, pid = 13
可以看出主进程pid为1,子进程pid为13。我们通过stop停止该容器:
$ sudo docker stop 781cecb4b362
781cecb4b362
再次通过docker logs查看:
$ sudo docker logs 781cecb4b362
i am parent process, pid = 1
fork ok, childpid = 13
i am in child process, pid = 13
1: handle signal: terminated
1: signal termiate received, app exit normally
我们可以看到主进程收到了stop发来的SIGTERM并退出,主进程的退出导致容器退出,导致子进程13也无法生存,并且没有优雅退出。 因此对于docker容器内运行的多进程程序,stop命令只会将SIGTERM发送给容器主进程,要想让其他进程也能优雅退出,需要在主进程与 其他进程间建立一种通信机制。在主进程退出前,等待其他子进程退出。待所有其他进程退出后,主进程再退出,容器停止。这样才能保证服务程序的优雅 退出。 正确的使用方式:
1.主进程退出前需要等待其他子进程退出。
2.比较直接的方式:将docker stop的time参数设置长一点,但是也不能完全保证容器内进程完全退出。
网易云容器服务为用户提供了无服务器容器,让企业能够快速部署业务,轻松运维服务。容器服务支持弹性伸缩、垂直扩容、灰度升级、服务发现、服务编排、错误恢复及性能监测等功能。点击免费试用
本文来自网易实践者社区,经作者崔晓晴授权发布。