如何优雅关闭 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 --init
docker kill
docker kill
命令会直接向docker
中的进程发出SIGKILL
系统信号,没有10s
的等待时间,立刻强行停止docker
中的程序。docker kill
命令还支持参数--signal
指定发送自定义的信号,比如让Apache
优雅的关闭进程:docker kill --signal=SIGWINCH apache
docker 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
延迟的设置。