如何优雅关闭 Docker 中的 Nginx
Signal in Nginx
nginx 默认注册了自己的系统信号处理程序,nginx 的 master 进程会根据不同的接收信号量执行不同的操作。Linux 中可以通过 nginx -s <param> 选项或者 kill 命令来向 nginx master 发送系统信号,下面列出了不同 -s <param> 命令和 kill 命令与发送信号对应关系:
| -s 参数 | kill 参数 | 信号 | 含义 |
|---|---|---|---|
| stop | 15 | SIGTERM | 快速关闭 |
| quit | 3 | SIGQUIT | 优雅关闭 |
| reload | 1 | SIGHUP | 重新加载配置文件 |
| reopen | 10 | SIGUSR1 | 重新打开日志文件 |
对于 nginx 而言,优雅的关闭进程的信号是 SIGQUIT,也就是 -s quit 命令。nginx master 在接收到 SIGQUIT 信号后,nginx worker 进程将关闭监听端口,不会再接收新的请求,并且在当前处理的请求完成后退出,nginx master 会在所有的 nginx worker 进程退出后执行退出。
而 SIGTERM 信号则会调用 ngx_master_process_exit 立刻关闭 nginx worker 进程。若 1s 内 nginx worker 未退出,则 nginx master 就会发送SIGKILL 信号强制关闭 nginx worker 进程。
Signal in Docker
在 docker 中,一般会通过 docker stop 或者 docker kill 命令来停止容器。
docker stop
默认
docker stop发送的是SIGTERM信号。容器中PID为 1 的进程在接收到SIGTERM信号后,会开始执行停止进程的操作,同时docker默认会给出10s时间来等待进程中止,10s时间结束后,如果程序还没停止,docker就会发送SIGKILL信号来强行停止进程。这里还涉及一个问题,由于
docker stop命令只会向docker中PID为 1 的进程发送信号,如果PID为 1 的进程并没有注册处理Linux系统信号的程序,那么就会忽略SIGTERM信号,从而在10s后被强行Kill。比如docker启动时启动的是一个Shell脚本,真正的进程被放进了Shell脚本中运行,那么此时PID为 1 的进程就是这个Shell脚本,而不是脚本中的那个进程。对于这种情况,可以在启动Shell脚本的命令前面加上exec命令,exec命令会让真正的进程继承Shell脚本进程的PID。此外,还可以在
docker中增加专为容器而生的tinit系统,那么docker启动后,tinit会来管理docker中的进程,以及处理docker stop发送来的系统信号。docker run --initdocker kill
docker kill命令会直接向docker中的进程发出SIGKILL系统信号,没有10s的等待时间,立刻强行停止docker中的程序。docker kill命令还支持参数--signal指定发送自定义的信号,比如让Apache优雅的关闭进程:docker kill --signal=SIGWINCH apachedocker rm -f
docker rm一般用来删除一个已经停止的容器,如果加上-f参数对一个处于运行中的容器执行docker rm,就会立刻发送一个SIGKILL信号来停止进程并删除容器。
所以,默认 docker stop 会发送一个 SIGTERM 信号,这就像在 Linux 系统中发送一个 kill 命令,能做的就是通过 --time 参数延缓发送 SIGKILL 的信号时间。而 docker kill 则没有 --time 参数,它会立刻对 docker 中的 PID 1 进程发送 SIGKILL 的信号,不过 docker kill 可以通过 --signal 参数来改变发送的系统信号。
STOPSIGNAL
docker stop 命令并没有 docker kill 的 --signal 参数,那么,对于 nginx 容器来说,docker stop 命令其实就是向 nginx 发出了 -s stop 的命令,这对于 nginx 来说并不是最优雅的停止进程的方式,如何能向docker 发送 -s quit 命令,也就是 QUIT 系统信号呢?
docker 在 1.9 之后在 dockerfile 中增加了 STOPSIGNAL 这个关键字,可以自定义 docker stop 发送给容器的信号。
STOPSIGNAL signal
The STOPSIGNAL instruction sets the system call signal that will be sent to the container to exit. This signal can be a valid unsigned number that matches a position in the kernel’s syscall table, for instance 9, or a signal name in the format SIGNAME, for instance SIGKILL.
按照官网的描述,通过 STOPSIGNAL 关键字可以自定义给容器发送的系统调用信号,其中 signal 既可以设置为内核系统调用表中定义的数字,比如 3,也可以是信号名称,比如 SIGQUIT
下面引用来自Github上的一个例子来验证通过 STOPSIGNAL 关键字将 docker stop 的默认 SIGTERM 信号修改为 SIGQUIT。
- 在
dockerfile中增加STOPSIGNAL SIGQUIT,修改发送的信号量
# cat Dockerfile
FROM nginx
RUN echo 'server {\n\
listen 80 default_server;\n\
location / {\n\
proxy_pass http://httpbin.org/delay/10;\n\
}\n\
}' > /etc/nginx/conf.d/default.conf
STOPSIGNAL SIGQUIT
CMD ["nginx", "-g", "daemon off;"]
在上面 nginx 的配置中,通过 url 中的 delay/10 设置了 10s 的延迟响应
时间。
- 构建镜像
# docker build -t delay-image .
- 启动
docker
# docker run --rm --name delay-ctnr --detach --publish 80:80 delay-image
- 发送一个
HTTP请求,等待1秒中,执行停止docker的命令
# curl -I localhost 2>&1 | egrep 'curl:|HTTP' &
# time docker stop delay-ctnr
HTTP/1.1 200 OK
delay-ctnr
0:08.51
通过返回的信息,我们可以看到从发送 docker stop delay-ctnr 命令到指定 docker 经过了 8.51s。满足 nginx 配置中设置的 10s 延迟的设置。
