平滑升级到底是个什么东西呢?说白了就是在不停止公司业务或者不停止公司网站的前提下对Nginx服务进行版本的升级,而且升级的过程呢正在访问的用户是感觉不到的,他们该怎么访问就怎么访问,一切都是那么正常,一点感觉都没有,这就是所谓的Nginx无感知升级。

:获取演示过程中用到的安装包请访问www.noylinux.com或加Qun:962822359

接下来呢,就该给大家讲讲这个平滑升级的实现过程了。

Nginx非常方便地帮我们实现了平滑升级这个操作,其中的过程也非常简单,这里先给大家总体概括一下:

Ø 在不停掉Nginx老进程的情况下,启动新版本的进程(master和worker进程)。

Ø 老进程还是去负责处理那些之前没有处理完的用户请求,但是不再接受新的用户请求。那些正在处理中的用户请求还是继续让这个老版本的Nginx去处理。

Ø 那谁来接受新进来的用户请求呢?新启动的高版本Nginx来接受并处理新进来的用户请求。

Ø 老进程处理完所有旧的用户请求之后就会关闭所有连接并且退出。

Ø 这样我们这台服务器上就只存在一个新的高版本的Nginx服务了

这样就很方便地实现了平滑升级,整个大体流程大家先看一遍,理解不了没关系,等会做实验的时候会把这里的整个过程结合起来再讲一遍。

一般有两种情况下需要升级Nginx,一种是本身nginx存在高危漏洞,必须要升级高版本的Nginx来修复这个漏洞,另一种就是要用到Nginx新增加的功能模块的时候。

在做平滑升级的时候,有段操作需要大家理解,大家想想怎么样才能让Nginx乖乖听我们的话?也就是说在合适的时间,怎么样才能说让新版本Nginx启动它就启动,让旧版本Nginx退出它就退出,

这里给各位读者朋友说说这一块的知识点,讲完之后咱们就动手开始演示示例。

大家还记得我之前讲的关于nginx的主进程,也就是master的主要功能嘛?

Ø master启动完成后会进入等待状态,然后管理分发请求和事件信号接收

分发用户请求想必大家都明白,就是将接收到的用户请求交给worker进程去处理,那接收管理事件信号呢?接收管理事件信号干啥用的?

现在我们就来细讲一下它的这个作用,Nginx一旦运行之后,它是可以通过两种方式来进行控制的。

第一种方式是使用“-s”命令行选项再次调用Nginx。例如通过“nginx -s stop”命令停止Nginx服务。

-s signal 是用于向主进程发送信号。参数是信号(signal),可以是以下几种:

Ø stop :快速关闭

Ø quit :优雅地关闭

Ø reload :重新加载配置,使用新配置启动新的工作进程,正常关闭旧工作进程。

Ø reopen :重新打开日志文件

注:stop是快速停止Nginx服务,可能并不保存相关信息;quit是完整有序的停止Nginx服务,并保存相关信息。

第二种方式是向Nginx进程发送信号。默认情况下,Nginx将其主进程ID会写入nginx.pid文件中,那我们就可以通过PID向Nginx主进程发送各种信号来达到控制Nginx的目的。

Nginx主进程可以处理那些信号呢?这里也给各位读者朋友罗列出来了,Nginx主进程(Master)可以处理以下信号:

Ø TERM, INT: 立刻退出

Ø QUIT: 等待工作进程忙完结束后再退出

Ø KILL: 强制终止进程

Ø HUP: 重新加载配置文件,使用新的配置启动新的工作进程,正常关闭旧的工作进程

Ø USR1: 重新打开日志文件

Ø USR2: 执行升级,并启动新的主进程,实现热升级

Ø WINCH: 逐步关闭工作进程或者说正常关闭工作进程

正常情况下我们是不需要自己控制工作进程的,但是它们也支持一些信号:

Ø TERM, INT: 立刻退出

Ø QUIT: 等待请求处理结束后再退出

Ø USR1: 重新打开日志文件

刚才也给大家讲了,Nginx是可以通过发送信号的方式来进行控制的,那有哪些信号大家也都知道了,剩下的还有什么?是不是怎么将这些信号给它发过去?

接下来简单给大家介绍一下怎么用发送信号的方式控制Nginx的,这个操作跟我们后面动手做平滑升级的实验有非常重要的关联。

Ø 语法格式:kill -信号 进程id(PID)

n 示例:kill -QUIT 16396

kill命令可以将指定的信号根据PID号发送至指定的程序中,默认的信号为SIGTERM(15),也就是将指定的程序终止。

接下来将给各位读者朋友演示Nginx平滑升级的过程。

【示例】Nginx平滑升级

目前在我的虚拟机中已经运行着一个旧版本的Nginx(1.16.1)服务,通过nginx命令的-V选项我们可以看到Nginx的版本号以及当初在编译时使用到的编译选项以及其他的信息。

[root@nylinux nginx]# pwd
/usr/local/nginx
[root@nylinux 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
--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
--http-client-body-temp-path=/usr/local/nginx/client_body_temp
--http-proxy-temp-path=/usr/local/nginx/proxy_temp
--http-fastcgi-temp-path=/usr/local/nginx/fastcgi_temp
--http-uwsgi-temp-path=/usr/local/nginx/uwsgi_temp
--http-scgi-temp-path=/usr/local/nginx/scgi_temp
--with-pcre=/root/pcre-8.42
[root@nylinux 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

目前Nginx服务正在正常的运行,这是我们要将最新版的Nginx源码包上传至服务器中,我在这里准备的Nginx安装包的版本号是1.20.2,我们将它解压并进入到解压后的目录中去。别忘了还有一个在编译Nginx时用到的pcre压缩包,也需要将它解压。

[root@nylinux ~]# tar xf pcre-8.42.tar.gz
[root@nylinux ~]# tar xf nginx-1.20.2.tar.gz
[root@nylinux ~]# cd nginx-1.20.2/
[root@nylinux nginx-1.20.2]#

开始准备编译新版本的Nginx,注意在编译新版本Nginx源码包时,安装路径和编译时的选项需要与旧版一致,安装路径和编译选项的信息都可以通过“nginx -V”获取到,我们直接复制“configure arguments:”中的内容即可。还有一点要嘱咐大家,千万不要执行make install 命令。正常的安装流程 ./configure ... à make à make install,这里大家不要执行make install了。

[root@nylinux 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 --http-client-body-temp-path=/usr/local/nginx/client_body_temp --http-proxy-temp-path=/usr/local/nginx/proxy_temp --http-fastcgi-temp-path=/usr/local/nginx/fastcgi_temp --http-uwsgi-temp-path=/usr/local/nginx/uwsgi_temp --http-scgi-temp-path=/usr/local/nginx/scgi_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
checking for -Wl,-E switch ... found
checking for gcc builtin atomic operations ... found
-----省略部分内容-----
nginx http proxy temporary files: "/usr/local/nginx/proxy_temp"
nginx http fastcgi temporary files: "/usr/local/nginx/fastcgi_temp"
nginx http uwsgi temporary files: "/usr/local/nginx/uwsgi_temp"
nginx http scgi temporary files: "/usr/local/nginx/scgi_temp"

[root@nylinux nginx-1.20.2]#
[root@nylinux 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 \
&& CC="cc" CFLAGS="-O2 -fomit-frame-pointer -pipe " \
./configure --disable-shared
make[2]: 进入目录“/root/pcre-8.42”
rm -f pcretest pcregrep
-----省略部分内容-----
objs/src/http/modules/ngx_http_upstream_keepalive_module.o \
objs/src/http/modules/ngx_http_upstream_zone_module.o \
objs/src/http/modules/ngx_http_stub_status_module.o \
objs/ngx_modules.o \
-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@nylinux nginx-1.20.2]#

这时已经编译完成了,千万不要去执行make install,接下来就要去替换二进制文件了,二进制文件也成为可执行文件,说白了就是sbin目录下的这个nginx命令,建议替换之前先将原先的旧版Nginx二进制文件备份。

替换的时候建议使用cp命令,而且还要加上“-rf”选项进行强制替换。

[root@nylinux nginx-1.20.2]# cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx-bak
[root@nylinux nginx-1.20.2]# cp -rf objs/nginx /usr/local/nginx/sbin/
cp:是否覆盖'/usr/local/nginx/sbin/nginx'? yes
[root@nylinux nginx-1.20.2]#

Nginx的二进制文件已经替换完毕,现在我先执行“-t”选项检查一下Nginx服务是否正常。

[root@nylinux 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命令可以正常使用,而且配置文件都很正常,其实我们也没有动过Nginx配置文件,这儿只是让大家看看nginx这个命令是否能正常使用。

接下来就是整个演示过程中最精彩的地方了,向主进程(master)发送USR2信号,Nginx会启动一个新版本的master进程和相对应工作进程,和旧版一起处理请求。

此时新版本的master进程 和 老版本的 master进程会同时存在,老版本的master进程不再接收新的请求,继续处理完剩余的请求并退出,新版本的master进程开始接收用户请求并接替老版本的进程进行服务。正常情况下,在这个阶段发送过来的请求是不存在失败的情况。也就是说用户该咋访问网站就咋访问网站,对用户来说这个Nginx升级操作是无感知的。

在发送信号之前我们先看一下目前老版本正在运行的进程,注意看进程PID的变化!

[root@nylinux 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@nylinux nginx-1.20.2]# kill -USR2 58903 #向master进程发送USR2信号

[root@nylinux 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@nylinux nginx-1.20.2]#

可以看到旧版本的进程依然存在,但是又新启动了两个新版本的Nginx进程(master和worker),旧版本的master进程号是58903,新版本的master进程号是65787。

接下来向旧版本的Nginx主进程(master)发送WINCH信号,它会逐步关闭自己的工作进程(主进程不退出),这时所有的用户请求都会由新版的Nginx进程处理。

[root@nylinux 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@nylinux nginx-1.20.2]# kill -WINCH 58903 #向旧版本的master进程发送WINCH信号

[root@nylinux 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 65874 2217 0 23:06 pts/0 00:00:00 grep --color=auto nginx
[root@nylinux nginx-1.20.2]#

如果这时后悔了,需要回退版本继续使用旧版本Nginx,可向旧的Nginx主进程发送HUP信号,它会重新启动工作进程,而且仍使用旧版配置文件。然后可以将新版Nginx进程使用信号进行杀死(使用QUIT、TERM、或者KILL)。

若想继续进行平滑升级的操作,那就向旧版的Nginx主进程(master)发送(QUIT、TERM、或者KILL)信号,使旧版的master进程退出。

[root@nylinux 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@nylinux nginx-1.20.2]# kill -QUIT 58903 #向旧版本的master进程发送QUIT信号

[root@nylinux 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@nylinux nginx-1.20.2]#

目前只剩下新版本的Nginx进程在正常运行,老版本的Nginx进程已经全被替换下来的,都没有了,最后我们再通过“nginx -V”来验证目前Nginx服务的版本信息。

[root@nylinux 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 --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 --http-client-body-temp-path=/usr/local/nginx/client_body_temp --http-proxy-temp-path=/usr/local/nginx/proxy_temp --http-fastcgi-temp-path=/usr/local/nginx/fastcgi_temp --http-uwsgi-temp-path=/usr/local/nginx/uwsgi_temp --http-scgi-temp-path=/usr/local/nginx/scgi_temp --with-pcre=/root/pcre-8.42

可以看到Nginx程序的版本号已经变成了1.20.2。再嗦一句,整个平滑升级的过程对于用户来讲是无感知的,悄悄的就将Nginx服务升级了,用户在这个过程中该咋访问网站咋访问网站,升级过程并没有对网站的访问造成任何影响。

最后我们来总结一下整个过程:

Ø Nginx在升级过程中,涉及到了3个信号(USR2、WINCH和QUIT);

Ø 首先发送USR2信号给旧版master进程,旧版master进程会额外启动一个master进程和若干个worker进程,新旧worker进程同时对外提供服务;

Ø 第二步发送WINCH信号,旧版worker进程停止服务并且退出;

Ø 最后发送QUIT信号给旧版master进程使其退出,只保留新版的master和worker在正常运行。

至此,整个平滑升级的操作就完成了,是不是特别的简单?希望各位读者朋友看完之后自己动手做做,毕竟熟能生巧嘛。


                                                                                                                       ---摘自《零基础趣学Linux》

Linux运维技术企业交流q:962822359