利用Nginx信号实现平滑升级

Nginx 命令和信号

Nginx 命令

可以通过nginx命令向nginx进程发送信号,实现各种功能

nginx 命令格式

nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

选项说明

帮助: -? -h
使用指定的配置文件: -c
指定配置指令:-g
指定运行目录:-p
测试配置文件是否有语法错误:-t -T
打印nginx的版本信息、编译信息等:-v -V
发送信号: -s 示例: nginx -s reload

信号说明

立刻停止服务:stop,相当于信号SIGTERM,SIGINT
优雅的停止服务:quit,相当于信号SIGQUIT
平滑重启,重新加载配置文件: reload,相当于信号SIGHUP
重新开始记录日志文件:reopen,相当于信号SIGUSR1,在切割日志时用途较大
平滑升级可执行程序:发送信号SIGUSR2,在升级版本时使用
优雅的停止工作进程:发送信号SIGWINCH,在升级版本时使用

范例:查看nginx 使用帮助

[root@centos7 ~]# nginx -h
nginx version: nginx/1.18.0
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
  -?,-h         : this help
  -v            : show version and exit
  -V            : show version and configure options then exit
  -t            : test configuration and exit
  -T            : test configuration, dump it and exit
  -q            : suppress non-error messages during configuration testing
  -s signal     : send signal to a master process: stop, quit, reopen, reload
  -p prefix     : set prefix path (default: /apps/nginx/)
  -c filename   : set configuration file (default: conf/nginx.conf)
  -g directives : set global directives out of configuration file

quit 信号

quit信号运行流程

  1. 设置定时器: worker_shutdown_timeout #一定时间后关闭work进程,防止超时。
  2. 关闭监听句柄。 #不再接收新的连接请求
  3. 关闭空闲连接
  4. 在循环中等待全部连接关闭 #待连接任务执行完毕后关闭连接
  5. 退出nginx所有进程

范例:quit 信号停止nginx进程

[root@centos7 html]# pwd  #进入nginx安装目录下
/apps/nginx/html

[root@centos7 html]# dd if=/dev/zero of=/apps/nginx/html/bigfile bs=1M count=1024  #生成1G的大文件bigfile
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 6.78492 s, 158 MB/s

[root@centos7 html]# ll -h bigfile 
-rw-r--r-- 1 root root 1.0G Jul 21 17:37 bigfile

[root@centos7 html]# hostname -I   #Nginx服务器地址
172.16.16.88

image-20220721094628824

reload 信号

reload信号运行流程

reload信号

不停机更新配置

利用 reload 可以实现平滑修改配置并生效

  1. 向master进程发送HUP信号(reload命令)
  2. master进程校验配置语法是否正确
  3. master进程打开新的监听端口
  4. master进程用新的配置启动新的worker子进程
  5. master进程向老worker子进程发送QUIT信号通知旧的worker处理完当前连接后退出,空闲worker旧进程不再处理新来的请求
  6. 旧worker进程关闭监听句柄,处理完当前连接后结束进程

范例:想nginx发送reload信号实现不停机更新配置

[root@centos7 ~]# nginx  #启动nginx
[root@centos7 ~]# ps -ef|grep nginx  #当前有一个master进程和一个worker进程
root      20091      1  0 17:51 ?        00:00:00 nginx: master process nginx
nginx     20092  20091  0 17:51 ?        00:00:00 nginx: worker process
root      20094  19981  0 17:51 pts/1    00:00:00 grep --color=auto nginx
[root@centos7 ~]# cat /apps/nginx/conf/nginx.conf | grep processes   #当前配置nginx的worker进程数为1
worker_processes  1;
[root@centos7 ~]# sed -i '/worker_processes/c\worker_processes 3;' /apps/nginx/conf/nginx.conf #将worker_processes 的值改为3
[root@centos7 ~]# grep worker_processes /apps/nginx/conf/nginx.conf
worker_processes 3;

[root@centos7 ~]# nginx -t && nginx -s reload  #检查配置并发送reload信号
nginx: the configuration file /apps/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /apps/nginx/conf/nginx.conf test is successful

[root@centos7 ~]# ps -ef | grep nginx #nginx的worker进程数量为3
root      20091      1  0 17:51 ?        00:00:00 nginx: master process nginx
nginx     20158  20091  0 18:05 ?        00:00:00 nginx: worker process
nginx     20159  20091  0 18:05 ?        00:00:00 nginx: worker process
nginx     20160  20091  0 18:05 ?        00:00:00 nginx: worker process
root      20162  19981  0 18:05 pts/1    00:00:00 grep --color=auto nginx

reopen信号

reopen 信号重写日志文件

1、nginx 当前将日志记录在access.log文件

image-20220721101352816

2、将access.log 重命名为access.log.old,日志仍记录到access.log.old文件中(因为Nginx已持有此文件的描述符

image-20220721102109230

3、新建access.log文件,向nginx发送reopen信号,使得新的日志记录在新生成的文件中(用于实现日志轮转)

image-20220721102806088

Nginx 平滑升级和回滚

工作中有时候需要对Nginx版本进行升级以满足对其功能的需求,例如添加新模块,需要新功能,而此时

Nginx又在运行着重要业务不能直接停掉。在此背景下可选择平滑升级nginx

平滑升级流程

平滑升级

不停机更新

平滑升级流程

  1. 将旧Nginx二进制文件换成新Nginx程序文件(注意先备份)
  2. 向master进程发送USR2信号,master进程自动修改pid文件名加上后缀.oldbin,成为nginx.pid.oldbin
  3. master进程用新Nginx文件启动新master进程成为旧master的子进程,系统中将有新旧两个Nginx主进程,新生成的master进程的PID存放至新生成的pid文件nginx.pid
  4. 主进程共同提供Web服务,当前新的请求仍然由旧Nginx的worker进程进行处理
  5. 向旧的Nginx服务进程发送WINCH信号,使旧的Nginx worker进程平滑停止
  6. 向旧master进程发送QUIT信号,关闭老master,进程自动删除Nginx.pid.oldbin文件
  7. 如果发现升级有问题,可以回滚∶向老master发送HUP,向新master发送QUIT

平滑升级和回滚案例

#当前nginx版本及安装路径
[root@centos7 nginx]# nginx -v 
nginx version: nginx/1.18.0
[root@centos7 nginx]# which nginx
/apps/nginx/sbin/nginx

#获取老版本编译参数  --prefix 
[root@centos7 ~]# nginx -V
nginx version: nginx/1.18.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=/apps/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-stream_ssl_module --with-stream_realip_module

#下载一个较新版本,使用上一步获得的编译参数进行编译
[root@centos7 ~]# wget http://nginx.org/download/nginx-1.20.1.tar.gz
[root@centos7 ~]# tar xf nginx-1.20.1.tar.gz 
[root@centos7 ~]# cd nginx-1.20.1/
[root@centos7 nginx-1.20.1]# ./configure --prefix=/apps/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-stream_ssl_module --with-stream_realip_module
[root@centos7 nginx-1.20.1]# make  #不需要执行make install
[root@centos7 nginx-1.20.1]# objs/nginx -v #查看编译结果
nginx version: nginx/1.20.1

#备份旧版nginx程序(用于回滚)
[root@centos7 ~]# mv /apps/nginx/sbin/{nginx,nginx.old}

#将编译后的新版nginx拷贝到旧版安装路径下
[root@centos7 ~]# cp ./nginx-1.20.1/objs/nginx /apps/nginx/sbin/
[root@centos7 ~]# ll /apps/nginx/sbin/
total 15308
-rwxr-xr-x 1 root root 7893488 Jul 21 18:49 nginx
-rwxr-xr-x 1 root root 7774224 Jul 21 00:17 nginx.old

#验证替换结果
[root@centos7 ~]# /apps/nginx/sbin/nginx -t
nginx: the configuration file /apps/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /apps/nginx/conf/nginx.conf test is successful
[root@centos7 ~]# /apps/nginx/sbin/nginx -v
nginx version: nginx/1.20.1

#nginx 默认将pid文件存放于/app/nginx/logs/路径下
[root@centos7 ~]# grep pid /apps/nginx/conf/nginx.conf
#pid        logs/nginx.pid;
[root@centos7 ~]# ll /apps/nginx/logs/nginx.pid 
-rw-r--r-- 1 root root 6 Jul 21 19:01 /apps/nginx/logs/nginx.pid

#查看当前nginx进程
[root@centos7 ~]# ps -ef | grep nginx
root      23503      1  0 19:06 ?        00:00:00 nginx: master process nginx
nginx     23504  23503  0 19:06 ?        00:00:00 nginx: worker process
root      23545  23521  0 19:07 pts/1    00:00:00 grep --color=auto nginx
#向旧版nginx发送USR2信号
[root@centos7 ~]# kill -USR2 `cat /apps/nginx/logs/nginx.pid`

#此时有两个master进程共存,其中一个master是新版nginx生成的。由新版nginx负责监听处理新的请求,旧版nginx不再处理。
[root@centos7 ~]# ps -ef | grep nginx
root      23503      1  0 19:06 ?        00:00:00 nginx: master process nginx
nginx     23504  23503  0 19:06 ?        00:00:00 nginx: worker process
root      23547  23503  0 19:07 ?        00:00:00 nginx: master process nginx
nginx     23548  23547  0 19:07 ?        00:00:00 nginx: worker process
root      23550  23521  0 19:07 pts/1    00:00:00 grep --color=auto nginx

#新版本master进程是旧版nginx进程的子进程
[root@centos7 ~]# pstree -p| grep nginx
           |-nginx(23503)-+-nginx(23547)---nginx(23548)
           |              `-nginx(23504)

#查看端口监听情况,两个版本都在监听80端口,但实际的请求是由旧版nginx的worker进程处理
[root@centos7 ~]# lsof -i :80
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   23503  root    6u  IPv4 191644      0t0  TCP *:http (LISTEN)
nginx   23504 nginx    6u  IPv4 191644      0t0  TCP *:http (LISTEN)
nginx   23547  root    6u  IPv4 191644      0t0  TCP *:http (LISTEN)
nginx   23548 nginx    6u  IPv4 191644      0t0  TCP *:http (LISTEN)

#pid文件也生成了新旧两个版本
[root@centos7 ~]# ll /apps/nginx/logs/
total 20
-rw-r--r-- 1 nginx root  172 Jul 21 18:48 access.log
-rw-r--r-- 1 root  root  258 Jul 21 18:19 access.log.old
-rw-r--r-- 1 nginx root 1879 Jul 21 19:07 error.log
-rw-r--r-- 1 root  root    6 Jul 21 19:07 nginx.pid
-rw-r--r-- 1 root  root    6 Jul 21 19:06 nginx.pid.oldbin
#向旧版nginx主进程发送WINCH信号,它会逐步关闭所有worker进程(主进程不退出,方便回滚),而后所有请求将由新版nginx处理
[root@centos7 ~]# kill -WINCH `cat /apps/nginx/logs/nginx.pid.oldbin`
[root@centos7 ~]# pstree -p| grep nginx
           |-nginx(23503)---nginx(23547)---nginx(23548)
[root@centos7 ~]# ps -ef | grep nginx
root      23503      1  0 19:06 ?        00:00:00 nginx: master process nginx
root      23547  23503  0 19:07 ?        00:00:00 nginx: master process nginx
nginx     23548  23547  0 19:07 ?        00:00:00 nginx: worker process
root      23598  23521  0 19:22 pts/1    00:00:00 grep --color=auto nginx


#如果此时新版本出现问题,可执行回滚操作
####################回滚######################
#向旧版nginx发送HUP信号,重新生成work进程处理新的请求
[root@centos7 ~]# kill -HUP `cat /apps/nginx/logs/nginx.pid.oldbin`

#关闭新版nginx进程
[root@centos7 ~]# kill -QUIT `cat /apps/nginx/logs/nginx.pid`
#####################回滚####################

#经过一段时间后,如果新版nginx正常运行,就可以关闭旧版nginx主进程master
[root@centos7 ~]# kill -QUIT `cat /apps/nginx/logs/nginx.pid.oldbin`
[root@centos7 ~]# ps -ef | grep nginx
root      23547      1  0 19:07 ?        00:00:00 nginx: master process nginx
nginx     23548  23547  0 19:07 ?        00:00:00 nginx: worker process
root      23603  23521  0 19:27 pts/1    00:00:00 grep --color=auto nginx

#版本升级成功
[root@centos7 ~]# nginx -v
nginx version: nginx/1.20.1
[root@centos7 ~]# curl localhost &> /dev/null && echo $?
0