Nginx平滑升级
我们都知道nginx的命令执行格式是nginx 参数,参数主要有如下的:
-?, -h | 打印帮助。 |
|---|---|
-v |
打印版本。 |
-V |
打印 NGINX 版本、编译器版本并配置参数。 |
-t |
不要运行,只测试配置文件。NGINX 检查配置的语法是否正确,然后尝试打开配置中引用的文件。 |
-q |
在配置测试期间抑制非错误消息。 |
-s signal |
向主进程发送信号:stop(暴力停止)、quit(优雅退出)、reopen(重新打开日志)、reload(重新加载)。(版本 >= 0.7.53) |
-p prefix |
设置前缀路径(默认:/usr/local/nginx/)。(版本 >= 0.7.53) |
-c filename |
指定 NGINX 应该使用哪个配置文件而不是默认配置文件。 |
-g directives |
设置全局指令。(版本 >= 0.7.4) |
其他的没啥好讲的,我们只讲一个东西nginx -s 参数,如果你以yum方式安装过部分非官方的nginx你就会发现他们自带了日志切割的功能,nginx本身当然是没有这个功能的,我们经过分析发现一般是使用系统自带的logrotate实现的。我们需要了解的是,所谓的日志切割本质上就是把现有的日志重命名为备份日志,让nginx写入到一个新的文件里面去(但是如果你自己直接重命名你就会发现一个问题,日志文件虽然被重命名了,但是日志还是往老的文件里面写,当然原因也很简单:程序是通过文件描述符去操作的,你重命名不会改变这种关系,这个时候你需要让nginx重新打开即nginx -s reopen)。
这个时候重点来了,我们查看logrotate的配置文件,有如下一行:kill -USR1 'cat /var/run/nginx.pid',是不是跟你想象中不同,kill不是杀死程序吗?我们平常大部分也是这么做的,但是其实不然,我们也可以通过kill命令向程序发送某些信号。比如根据nginx程序的定义kill -USR1 pid就跟nginx -s reopen是等效的。其他的如下表:
| TERM, INT | Quick shutdown |
|---|---|
| QUIT | Graceful shutdown |
| KILL | Halts a stubborn process |
| HUP | Configuration reloadStart the new worker processes with a new configurationGracefully shutdown the old worker processes |
| USR1 | Reopen the log files |
| USR2 | Upgrade Executable on the fly |
| WINCH | Gracefully shutdown the worker processes |
ok,有了前面的知识储备,我们就可以进行后面的工作了。如何优雅地不停机的升级nginx?
假设nginx正在提供服务,一切正常,现在我们想要对nginx进行热升级,大致步骤如下:
- 最重要的一步,备份。
- 下载新版本的nginx,根据老版本的编译选项,对新版本完成编译的步骤,只对新版本进行编译操作,不执行安装操作,换句话说就是,只执行make命令,不执行make install命令,完成编译操作后,即可获取到我们需要的新版本的二进制文件,之后,我们需要根据实际情况判断哪些文件需要被替换,此处描述的”根据情况判断”在后文中会有解释,先不用纠结,此处假设,根据情况判断后,只需要替换nginx二进制文件。
- 确定已经备份老版本的nginx二进制文件,以防万一,用编译好的新版本的nginx二进制文件替换老版本的nginx二进制文件,此时老版本的nginx仍然在内存中正常运行,所以不用担心,我们替换的只是硬盘中的二进制文件,做好备份即可。
- 对nginx的master进程(正在运行的老版本的master进程)发送USR2信号,老版本的master进程收到信号后,会通过新版本的二进制文件启动新版本的master进程,新版本的master进程会启动新版本的worker进程,此时新老版本的nginx进程同时存在。
- 向老版本的master进程发送WINCH信号,以便先优雅的停止老版本的worker进程,新的请求会被新版本的worker进程处理,此时老的master进程仍然存在,留下老的master进程是为了以防万一,以便随时回滚,此时老版本的master进程、新版本的master进程和新版本的worker进程同时存在,升级过程暂且完毕。
- 如果升级后万一出现问题,则可以随时进行回滚,由于老版本的master进程并未停止,所以我们可以向老的master进程发送HUP信号,即可通过老版本的master进程重新生成老版本的worker进程,当老版本的worker进程重新被拉起后,即可向新版本的master进程发送QUIT信号,以便优雅的关闭新版本的nginx进程,回滚操作完毕。
大概的命令如下:
# 下载源码包
$ 命令省略
# 获取目前nginx的编译参数
$ nginx -V
# 解压和编译新的nginx(注意:不能使用make install)
$ 解压命令省略
$ make
# 备份nginx程序
$ 备份命令省略
# 备份modules,如果你编译的时候有模块是动态的(dynamic),可能就需要替换动态库
$ 备份命令省略
# 替换新的程序和动态库到程序目录,make之后可以在objs目录找到
$ 命令省略
# 向老版本的nginx的master进程发送USR2信号,老master进程会使用新版本的二进制文件来启动新的master进程
$ kill -USR2 $(ps aux | grep nginx | grep master | awk 'NR==1 {print $2}')
# 此时,新老版本的nginx进程同时存在,但是我们的最终目的是使用新版本的nignx提供服务,于是,我们需要先优雅的停止老版本的worker进程,此时就需要用到”WINCH”信号了,当老版本的master进程接收到”WINCH”信号后,会停止老版本的worker进程,但是老版本的master进程并不会停止,我们留下老版本的master进程是为了以防万一
$ kill -WINCH $(ps aux | grep nginx | grep master | awk 'NR==1 {print $2}')
# 老版本的worker进程已经优雅的停止了(优雅的停止是在处理完当前连接的请求后再行停止),但是老版本的master进程还在。你可以随时通过老的master进程从新启动一个老版本的worker进程,然后优雅的停止新版本的nginx进程(有问题才执行下面的)
$ kill -HUB $(ps aux | grep nginx | grep master | awk 'NR==1 {print $2}')
$ kill -QUIT $(ps aux | grep nginx | grep master | awk 'NR==2 {print $2}')
浙公网安备 33010602011771号