首页 > 编程笔记 > Linux笔记 阅读:32

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,语法格式为:

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。整个平滑升级的过程对于用户来说是无感知的,升级过程并没有对网站的访问造成任何影响。

相关文章