本文主要阐述如何让 docker 容器优雅的终止。
优雅退出定义
所谓优雅退出,指的是程序在退出之前,有清理资源、保存必要中间状态、持久化内存数据的机会。以下罗列三种需要 docker 优雅退出的具体场景。
场景A
处理各种 HTTP request 并返回结果,我们必然希望在容器被停掉的时候,能够让应用程序有时间把已经在处理中 的请求继续处理完毕,并返回给客户端。
场景B
负责写入数据到某个数据文件,我们必须希望在容器被停掉的时候,有时间将内存中缓存的数据持久化到存储设备中,以防止数据丢失。
场景C
服务注册发现场景,服务启动后会自动将自己的实例信息(服务名称、实例IP)注册到服务发现模块中,而在容器被 kill 掉时,微服务应及时从服务发现模块中注销自己,以防止从 API-GATEWAY 而来的请求错误地路由到已经停止掉的容器而引起404错误。
从以上几种场景来看,确实很有必要让 docker 容器优雅退出,在优雅退出的期间能够进行相应的操作,将容器停止或者杀死的命令是 docker stop 和 docker kill,有必要了解这两者的区别。
docker stop 命令
当我们用 docker stop 命令来停止容器时,docker 默认会允许应用程序有 10s 的时间用于终止,在 docker stop 命令执行的时候,会先容器内 PID 为 1 的进程发送 SIGTERM 信号,然后等待容器内的应用程序终止运行,如果等待的时间超过设定的超时时间,或者是 10s,会继续发送 SIGKILL 信号强行 kill 容器内的应用程序。容器内的应用程序可以选择忽略和不处理 SIGTERM 信号,不过一旦超过超时时间,程序就会被 kill 掉了。
docker kill 命令
docker kill 命令会直接发送 SIGKILL信号,强行终止应用进程。
为了实现优雅退出,容器内 PID 为 1 的进程需要能够正确接收并处理 SIGTERM 信号,PID 为 1 的进程有两种方式:
CMD /graceful
这是以 shell 的方式运行程序,/bin/bash -c 的方式运行了我们的程序,这样会导致 /bin/sh 以 PID 为 1 的进程运行,而我们的程序只不过是它 fork/execs 出来的子进程而已,这样会导致,docker stop 的 SIGTERM 信号只会发送 /bin/sh 这个 PID 为 1 的进程,应用程序无法接收和处理 SIGTERM 信号。
CMD ["/graceful"]
这是以 exec 的方式来运行程序,这样子程序被直接执行,而不是以 shell 的方法执行,程序作为 PID 为 1 的进程,自然能接收到 docker stop 发送的 SIGTERM 信号,当然程序要自定义处理逻辑,当捕获到 SIGTERM 信号时,如何进行处理,现在通常的做法是由基础镜像中的 init() 方法来进行处理,exec 这种方式也是 Dockerfile 良好实践中推荐的用来设置容器启动时执行的命令。
关于 SIGTERM 和 SIGKILL 信号,可以参考这篇博客:SIGTERM & SIGKILL 信号区别