Nginx平滑升级(热部署)详解(附带实例)
平滑升级在企业中应用的次数并不频繁,但是作为 Linux 运维工程师,也必须掌握。
Nginx 从 2002 年问世至今,越来越流行,用户也越来越广泛,同时在社区和开发人员的维护下,Nginx 版本的升级迭代也开启了加速模式,下图是 Nginx 版本更新发布记录,从图中可以看到,基本上一个月就要更新四五次。

图 1 Nginx版本更新发布记录
大家试想一下,一旦一个增加了重要功能或者修复严重 Bug 的版本发布,用户大概率会更新这个版本,而且随着版本升级的频率越来越快,用户进行更新的频率也会越来越快。
此时就会出现一个矛盾:企业中正在运行的业务系统是不能停的,例如购物网站,随时都会有很多用户访问,若直接将网站关停进行更新,用户便无法访问,就算发布了系统升级通知,用户会选择访问其他购物网站,这种停服更新的操作是很容易流失用户的。怎么办呢?平滑升级技术就是这一矛盾的最佳解决方案,平滑升级也是 Linux 运维工程师必备的技能之一。
平滑升级就是在不停止公司业务或者不停止公司网站的前提下对 Nginx 服务进行版本的升级,而且正在访问的用户也感觉不到升级过程,这就是所谓的 Nginx 无感知升级。
Nginx 平滑升级的过程也非常简单,总体概括如下:
一般有在两种情况下需要升级 Nginx。一种是现版本 Nginx 存在高危漏洞,必须升级高版本的 Nginx 来修复;另一种就是要用到 Nginx 新增加的功能模块。
Nginx 一旦运行,可以通过两种方式来进行控制。第一种方式是使用 -s 命令行选项再次调用 Nginx。例如,通过 nginx-s stop 命令停止 Nginx 服务;通过 nginx-s signal 命令向主进程发送信号,参数是信号(signal)。
-s 命令行选项的参数包括以下几种:
stop 的作用是快速停止 Nginx 服务,可能并不保存相关信息;quit 的作用是完整有序地停止 Nginx 服务,并保存相关信息。
第二种方式是向 Nginx 进程发送信号。默认 Nginx 会将其主进程 ID 写入 nginx.pid 文件中,我们可以通过 PID 向 Nginx 主进程发送各种信号来达到控制 Nginx 的目的。
Nginx 主进程可以处理的信号如下:
接下来简单介绍一下如何用发送信号的方式控制 Nginx,语法格式为:
【实例】Nginx平滑升级。
1) 目前在虚拟机中已经运行着一个旧版本的 Nginx(1.16.1) 服务,通过 -V 命令行选项可以看到 Nginx 的版本号和当初在编译时使用到的编译选项及其他信息。
2) Nginx 服务正在正常运行,将最新版的 Nginx 源码包(此处使用的版本号为 1.20.2)上传至服务器,解压并进入解压后的目录中。别忘了还有一个在编译 Nginx 时用到的 pcre 压缩包,也需要将它解压。
3) 编译新版本的 Nginx,注意在编译新版本 Nginx 源码包时,安装路径和编译时的选项需要与旧版保持一致,安装路径和编译选项的信息都可以通过 nginx -V 命令获取,我们直接复制“configure arguments:”中的内容即可。还有一点要嘱咐大家,千万不要执行 make install 命令(正常的安装流程为./configure...→ make → make install)。
4) 替换二进制文件,此时二进制文件也成为可执行文件,也就是 sbin 目录下的 nginx 命令,建议替换之前先将原先的二进制文件备份。替换时建议使用 cp 命令,而且还要加上 -rf 选项进行强制替换。
5) 二进制文件替换完毕,执行 -t 选项检查 Nginx 服务是否正常。
6) 向 master 进程发送 USR2 信号,Nginx 会启动一个新版本的 master 进程和相对应的 worker 进程,和旧版本的 master 进程一起处理请求。
此时新版本的 master 进程和旧版本的 master 进程会同时存在,旧版本的 master 进程不再接收新的请求,继续处理完现有请求并退出,新版本的 master 进程接收新的用户请求并接替老版本的进程进行服务。
在发送信号之前我们先看一下目前旧版本 Nginx 正在运行的进程,注意看进程 PID 的变化!
7) 向旧版本的 master 进程发送 WINCH 信号,并逐步关闭自己的工作进程(master 进程不退出),这时所有的用户请求都会由新版本的 master 进程处理。
若想继续进行平滑升级的操作,则可以对旧版本的 master 进程发送信号(QUIT、TERM 或 KILL),使旧版本的 master 进程退出。
8) 目前只剩下新版本的 Nginx 进程在正常运行,旧版本的 Nginx 进程已经全被替换,最后我们再通过 nginx -V 命令验证目前 Nginx 服务的版本信息。
Nginx 从 2002 年问世至今,越来越流行,用户也越来越广泛,同时在社区和开发人员的维护下,Nginx 版本的升级迭代也开启了加速模式,下图是 Nginx 版本更新发布记录,从图中可以看到,基本上一个月就要更新四五次。

图 1 Nginx版本更新发布记录
大家试想一下,一旦一个增加了重要功能或者修复严重 Bug 的版本发布,用户大概率会更新这个版本,而且随着版本升级的频率越来越快,用户进行更新的频率也会越来越快。
此时就会出现一个矛盾:企业中正在运行的业务系统是不能停的,例如购物网站,随时都会有很多用户访问,若直接将网站关停进行更新,用户便无法访问,就算发布了系统升级通知,用户会选择访问其他购物网站,这种停服更新的操作是很容易流失用户的。怎么办呢?平滑升级技术就是这一矛盾的最佳解决方案,平滑升级也是 Linux 运维工程师必备的技能之一。
平滑升级就是在不停止公司业务或者不停止公司网站的前提下对 Nginx 服务进行版本的升级,而且正在访问的用户也感觉不到升级过程,这就是所谓的 Nginx 无感知升级。
Nginx 平滑升级的过程也非常简单,总体概括如下:
- 在不停止 Nginx 现有进程的情况下,启动新版本的进程(master 和 worker 进程);
- 原有 Nginx 进程处理之前未处理完的用户请求,但不再接受新的用户请求;
- 新启动的高版本 Nginx 接受并处理新进来的用户请求;
- 原有 Nginx 进程处理完之前未处理完的用户请求后就关闭所有连接并且退出;
- 此时服务器上就只存在一个新的高版本的 Nginx 服务了。
一般有在两种情况下需要升级 Nginx。一种是现版本 Nginx 存在高危漏洞,必须升级高版本的 Nginx 来修复;另一种就是要用到 Nginx 新增加的功能模块。
Nginx 一旦运行,可以通过两种方式来进行控制。第一种方式是使用 -s 命令行选项再次调用 Nginx。例如,通过 nginx-s stop 命令停止 Nginx 服务;通过 nginx-s signal 命令向主进程发送信号,参数是信号(signal)。
-s 命令行选项的参数包括以下几种:
- stop:快速关闭;
- quit:正常关闭;
- reload:重新加载配置,使用新配置启动新的工作进程,正常关闭旧工作进程;
- reopen:重新打开日志文件。
stop 的作用是快速停止 Nginx 服务,可能并不保存相关信息;quit 的作用是完整有序地停止 Nginx 服务,并保存相关信息。
第二种方式是向 Nginx 进程发送信号。默认 Nginx 会将其主进程 ID 写入 nginx.pid 文件中,我们可以通过 PID 向 Nginx 主进程发送各种信号来达到控制 Nginx 的目的。
Nginx 主进程可以处理的信号如下:
- TERM,INT:立刻退出;
- QUIT:等待工作进程结束后再退出;
- KILL:强制终止进程;
- HUP:重新加载配置文件,使用新的配置启动新的工作进程,正常关闭旧的工作进程;
- USR1:重新打开日志文件;
- USR2:执行升级,并启动新的主进程,实现热升级;
- WINCH:逐步关闭工作进程或正常关闭工作进程。
接下来简单介绍一下如何用发送信号的方式控制 Nginx,语法格式为:
kill -信号 进程id(PID)
例如,kill-QUIT 16396。kill 命令可以将指定的信号根据 PID 发送至指定的程序中,默认的信号为 SIGTERM(15),也就是将指定的程序终止。【实例】Nginx平滑升级。
1) 目前在虚拟机中已经运行着一个旧版本的 Nginx(1.16.1) 服务,通过 -V 命令行选项可以看到 Nginx 的版本号和当初在编译时使用到的编译选项及其他信息。
[root@linux nginx]# pwd /usr/local/nginx [root@linux nginx]# ./sbin/nginx -V #查看 Nginx 的版本号及其他信息 nginx version: nginx/1.16.1 built by gcc 8.5.0 20210514 (Red Hat 8.5.0-4) (GCC) built with OpenSSL 1.1.1k FIPS 25 Mar 2021 TLS SNI support enabled configure arguments: --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/usr/local/nginx/log/error.log ----省略部分内容---- [root@linux nginx]# ps -ef | grep nginx #查看 Nginx 运行中的进程 root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 58904 58903 0 22:19 ? 00:00:00 nginx: worker process root 58909 2366 0 22:20 pts/1 00:00:00 grep --color=auto nginx
2) Nginx 服务正在正常运行,将最新版的 Nginx 源码包(此处使用的版本号为 1.20.2)上传至服务器,解压并进入解压后的目录中。别忘了还有一个在编译 Nginx 时用到的 pcre 压缩包,也需要将它解压。
[root@linux ~]# tar xf pcre-8.42.tar.gz [root@linux ~]# tar xf nginx-1.20.2.tar.gz [root@linux ~]# cd nginx-1.20.2/ [root@linux nginx-1.20.2]#
3) 编译新版本的 Nginx,注意在编译新版本 Nginx 源码包时,安装路径和编译时的选项需要与旧版保持一致,安装路径和编译选项的信息都可以通过 nginx -V 命令获取,我们直接复制“configure arguments:”中的内容即可。还有一点要嘱咐大家,千万不要执行 make install 命令(正常的安装流程为./configure...→ make → make install)。
[root@linux nginx-1.20.2]# ./configure --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/usr/local/nginx/log/error.log --http-log-path=/usr/local/nginx/log/access.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-http_client_body_temp_path=/usr/local/nginx/client_body_temp --with-http_proxy_temp_path=/usr/local/nginx/proxy_temp --with-http_fastcgi_temp_path=/usr/local/nginx/fastcgi_temp --with-http_uwsgi_temp_path=/usr/local/nginx/uwsgi_temp --with-pcre=/root/pcre-8.42 checking for OS + Linux 4.18.0-305.3.1.el8.x86_64 x86_64 checking for C compiler ... found + using GNU C compiler + gcc version: 8.5.0 20210514 (Red Hat 8.5.0-4) (GCC) checking for gcc -pipe switch ... found ----省略部分内容---- nginx http uwsgi temporary files: "/usr/local/nginx/uwsgi_temp" nginx http scgi temporary files: "/usr/local/nginx/scgi_temp" [root@linux nginx-1.20.2]# [root@linux nginx-1.20.2]# make make -f objs/Makefile make[1]: 进入目录“/root/nginx-1.20.2” cd /root/pcre-8.42 \ && if [ -f Makefile ]; then make distclean; fi \ ----省略部分内容---- ldl -lpthread -lcrypt /root/pcre-8.42/.libs/libpcre.a -lssl -lcrypto -ldl -lpthread -lz \ -Wl,-E \ sed -e "s|%%PREFIX%%|/usr/local/nginx|" \ -e "s|%%PID_PATH%%|/var/run/nginx/nginx.pid|" \ -e "s|%%CONF_PATH%%|/usr/local/nginx/conf/nginx.conf|" \ -e "s|%%ERROR_LOG_PATH%%|/usr/local/nginx/log/error.log|" \ < man/nginx.8 > objs/nginx.8 make[1]: 离开目录“/root/nginx-1.20.2” [root@linux nginx-1.20.2]#
4) 替换二进制文件,此时二进制文件也成为可执行文件,也就是 sbin 目录下的 nginx 命令,建议替换之前先将原先的二进制文件备份。替换时建议使用 cp 命令,而且还要加上 -rf 选项进行强制替换。
[root@linux nginx-1.20.2]# cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx-bak [root@linux nginx-1.20.2]# cp -rf objs/nginx /usr/local/nginx/sbin/ cp: 是否覆盖 '/usr/local/nginx/sbin/nginx'? yes [root@linux nginx-1.20.2]#
5) 二进制文件替换完毕,执行 -t 选项检查 Nginx 服务是否正常。
[root@linux nginx-1.20.2]# /usr/local/nginx/sbin/nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful可以明显看到,新版本的 nginx 命令可以正常使用,而且配置文件都很正常。
6) 向 master 进程发送 USR2 信号,Nginx 会启动一个新版本的 master 进程和相对应的 worker 进程,和旧版本的 master 进程一起处理请求。
此时新版本的 master 进程和旧版本的 master 进程会同时存在,旧版本的 master 进程不再接收新的请求,继续处理完现有请求并退出,新版本的 master 进程接收新的用户请求并接替老版本的进程进行服务。
在发送信号之前我们先看一下目前旧版本 Nginx 正在运行的进程,注意看进程 PID 的变化!
[root@linux nginx-1.20.2]# ps -ef | grep nginx #目前旧版本 Nginx 的进程 root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 58904 58903 0 22:19 ? 00:00:00 nginx: worker process root 65782 2217 0 23:01 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]# kill -USR2 58903 #向 master 进程发送 USR2 信号 [root@linux nginx-1.20.2]# ps -ef | grep nginx root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 58904 58903 0 22:19 ? 00:00:00 nginx: worker process root 65787 58903 0 23:01 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65788 65787 0 23:01 ? 00:00:00 nginx: worker process root 65790 2217 0 23:01 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]#可以看到旧版本的进程依然存在,但是又新启动了两个新版本的 Nginx 进程(master 进程和 worker 进程),旧版本 master 进程的 PID 是 58903,新版本 master 进程的 PID 是 65787。
7) 向旧版本的 master 进程发送 WINCH 信号,并逐步关闭自己的工作进程(master 进程不退出),这时所有的用户请求都会由新版本的 master 进程处理。
[root@linux nginx-1.20.2]# ps -ef | grep nginx root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 58904 58903 0 22:19 ? 00:00:00 nginx: worker process root 65787 58903 0 23:01 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65788 65787 0 23:01 ? 00:00:00 nginx: worker process root 65790 2217 0 23:01 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]# kill -WINCH 58903 #向旧版本的 master 进程发送 WINCH 信号 [root@linux nginx-1.20.2]# ps -ef | grep nginx #发送完信号之后所有 Nginx 进程的状态 root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65787 58903 0 23:01 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65788 65787 0 23:01 ? 00:00:00 nginx: worker process root 65847 2217 0 23:06 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]#如果这时后悔了,需要回退版本继续使用旧版本的 Nginx,可向旧版本的 master 进程发送 HUP 信号,它会重新启动工作进程,而且仍使用旧版配置文件,再使用信号(QUIT、TERM 或 KILL)将新版本的 Nginx 进程杀死。
若想继续进行平滑升级的操作,则可以对旧版本的 master 进程发送信号(QUIT、TERM 或 KILL),使旧版本的 master 进程退出。
[root@linux nginx-1.20.2]# ps -ef | grep nginx root 58903 1 0 22:19 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65787 58903 0 23:01 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65788 65787 0 23:01 ? 00:00:00 nginx: worker process root 65874 2217 0 23:06 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]# kill -QUIT 58903 #向旧版本的 master 进程发送 QUIT 信号 [root@linux nginx-1.20.2]# ps -ef | grep nginx #发送完信号之后所有 Nginx 进程的状态 root 65787 1 0 23:01 ? 00:00:00 nginx: master process ./sbin/nginx -c conf/nginx.conf root 65788 65787 0 23:01 ? 00:00:00 nginx: worker process root 65926 2217 0 23:11 pts/0 00:00:00 grep --color=auto nginx [root@linux nginx-1.20.2]#
8) 目前只剩下新版本的 Nginx 进程在正常运行,旧版本的 Nginx 进程已经全被替换,最后我们再通过 nginx -V 命令验证目前 Nginx 服务的版本信息。
[root@linux nginx-1.20.2]# /usr/local/nginx/sbin/nginx -V nginx version: nginx/1.20.2 built by gcc 8.5.0 20210514 (Red Hat 8.5.0-4) (GCC) built with OpenSSL 1.1.1k FIPS 25 Mar 2021 TLS SNI support enabled configure arguments: --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/usr/local/nginx/log/error.log ----省略部分内容----可以看到,Nginx 程序的版本号已经变成了 1.20.2。整个平滑升级的过程对于用户来说是无感知的,升级过程并没有对网站的访问造成任何影响。