说明

此前线上正在运行的Nginx版本因为漏洞扫描或者其他原因需要对Nginx版本进行升级。需要满足以下条件:

  • 因为线上项目正在运行,所以不能进行服务停止
  • 需要编译相同的模块

Nginx命令和信号

nginx命令格式

nginx [-?hvVtTp] [-s signal] [-c filename] [-p prefix] [-g directives]
选项说明:

-? -h :帮助
-c :指定使用的配置文件
-g :指定配置指令
-p :指定运行目录
-t -T :测试配置文件是否具有语法错误
-v :输出nginx版本
-V :输出nginx版本及编译信息
-s :nginx程序发出控制信号

nginx信号选项

nginx -s signal
signal信号种类如下:

  • stop:停止服务,等同于SIGTERM,SIGINT
  • quit:优雅的停止服务,等同于SIGQUIT
  • reload:重新加载配置文件,等同于SIGHUP
  • reopen:重新开始记录日志文件,等同于SIGUSR1,配置logrotate日志切割时可通过此信号重新生成日志文件。
  • SIGUSR2:平滑升级可执行程序,设定新的子进程开始接受用户的访问请求,旧的子进程将已接收的任务处理完成后停止运行。通过kill -USR2 PID发送。
  • SIGWINCH:优雅的停止工作进程。通过kill -WINCH PID发送。

平滑升级步骤

此次升级测试将从1.20.0升级至1.24.0版本

备份

$ cd /usr/local/nginx/sbin/
$ cp nginx{,.bak}
$ ls
nginx  nginx.bak

查看nginx编译情况

$ nginx -V
nginx version: nginx/1.20.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module

编译新的nginx程序

# 下载新的nginx二进制包
$ cd /usr/local/src && wget  https://nginx.org/download/nginx-1.24.0.tar.gz
$ tar xvf nginx-1.24.0.tar.gz
$ cd nginx-1.24.0

# 根据上面查出来的编译信息重新编译
$ ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module
$ make    # 注意,此处执行后不要执行make install  !!! 
# make操作后会在objs目录下生成新的nginx程序

$ ls objs/nginx
objs/nginx

程序替换

查看程序当前的进程情况,因为环境上还有docker启动的nginx,所以过滤了一下,筛选出有用的信息。

$  ps -ef | egrep "/usr/local/nginx/sbin/nginx|^nginx" | grep -v grep
nginx     57259  79215  0 Jun19 ?        00:05:56 nginx: worker process
nginx     57260  79215  0 Jun19 ?        00:08:27 nginx: worker process
nginx     57261  79215  0 Jun19 ?        00:11:32 nginx: worker process
nginx     57262  79215  0 Jun19 ?        00:15:07 nginx: worker process
nginx     57263  79215  0 Jun19 ?        00:20:26 nginx: worker process
nginx     57264  79215  1 Jun19 ?        00:34:31 nginx: worker process
nginx     57265  79215  1 Jun19 ?        00:55:50 nginx: worker process
nginx     57266  79215  2 Jun19 ?        01:17:04 nginx: worker process
root      79215      1  0 May10 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx

需要记住上方master进程的pid:79215
替换nginx程序

$ chown nginx:nginx objs/nginx
$ cp -f objs/nginx /usr/local/nginx/sbin/nginx
cp: overwrite ‘/usr/local/nginx/sbin/nginx’? yes

# 向旧master进程发送USR2信号,让其平滑升级使用新的进行来接收处理用户的请求。
$ kill -USR2 79215

# 再次查看当前进程情况,可以发现启动了两个master进程,和不同的worker进程。新的master进程是旧master进程的子进程。
$ ps -ef | egrep "/usr/local/nginx/sbin/nginx|^nginx" | grep -v grep
nginx     57259  79215  0 Jun19 ?        00:06:00 nginx: worker process
nginx     57260  79215  0 Jun19 ?        00:08:33 nginx: worker process
nginx     57261  79215  0 Jun19 ?        00:11:39 nginx: worker process
nginx     57262  79215  0 Jun19 ?        00:15:17 nginx: worker process
nginx     57263  79215  0 Jun19 ?        00:20:38 nginx: worker process
nginx     57264  79215  1 Jun19 ?        00:34:52 nginx: worker process
nginx     57265  79215  1 Jun19 ?        00:56:19 nginx: worker process
nginx     57266  79215  2 Jun19 ?        01:17:54 nginx: worker process
root      77298  79215  0 17:12 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nginx     77299  77298  0 17:12 ?        00:00:00 nginx: worker process
nginx     77300  77298  0 17:12 ?        00:00:00 nginx: worker process
nginx     77301  77298  0 17:12 ?        00:00:00 nginx: worker process
nginx     77302  77298  0 17:12 ?        00:00:00 nginx: worker process
nginx     77303  77298  2 17:12 ?        00:00:00 nginx: worker process
nginx     77304  77298  1 17:12 ?        00:00:00 nginx: worker process
nginx     77305  77298  1 17:12 ?        00:00:00 nginx: worker process
nginx     77306  77298  0 17:12 ?        00:00:00 nginx: worker process
root      79215      1  0 May10 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx

# 向旧的master进程发送WINCH信号,使其关闭旧的worker进程
$ kill -WINCH 79215
$ ps -ef | egrep "/usr/local/nginx/sbin/nginx|^nginx" | grep -v grep
root      77298  79215  0 17:12 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nginx     77299  77298  0 17:12 ?        00:00:00 nginx: worker process
nginx     77300  77298  0 17:12 ?        00:00:01 nginx: worker process
nginx     77301  77298  0 17:12 ?        00:00:02 nginx: worker process
nginx     77302  77298  0 17:12 ?        00:00:02 nginx: worker process
nginx     77303  77298  1 17:12 ?        00:00:03 nginx: worker process
nginx     77304  77298  1 17:12 ?        00:00:03 nginx: worker process
nginx     77305  77298  2 17:12 ?        00:00:05 nginx: worker process
nginx     77306  77298  2 17:12 ?        00:00:05 nginx: worker process
root      79215      1  0 May10 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
# 此时可以看到master进程还是保留了两个,但是旧的worker进程已经被关闭了。新的worker进程是新的master进程的子进程。

测试和关闭旧master进程

此时进行测试和版本确认。

$ nginx -V
nginx version: nginx/1.24.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module
# 测试无问题后关闭旧master进程
$ kill -quit 79215

总结

nginx平滑升级主要流程:

  1. 备份nginx旧的程序文件。
  2. 查看nginx配置编译安装的模块。
  3. 下载需要的nginx二进制文件包。
  4. 根据旧版本编译参数进行编译,只编译不安装(不要执行make install)。
  5. 复制新编译的nginx程序到旧的nginx程序路径进行覆盖。
  6. 查看旧的nginx主进程的PID。
  7. 向旧的nginx主进程发送USR2信号。
  8. 向旧的nginx主进程发送WINCH信号。
  9. 查看nginx版本,确认升级成功无问题后,向旧的nginx主进程发送kill -quit PID信号。

如果升级失败(未停止旧的nginx主进程情况下),那么参考下方的平滑回滚主要流程:

  1. 将备份的旧版本nginx程序文件覆盖新版本nginx程序文件。
  2. 向旧版本nginx主进程发送HUB信号(不能使用reload),此时会将旧的nginx子进程拉起。
  3. 向新的nginx主进程发送USR2信号,此时将使用旧版本的nginx处理用户请求。
  4. 向新的nginx主进程发送WINCH信号,关闭新的nginx子进程。
  5. 向新的nginx主进程发送QUIT信号,停止新的nginx主进程。

星霜荏苒 居诸不息