Nginx平滑升级
平滑升级说明
有时候我们需要对Nginx版本进行升级以满足对其功能的需求,例如添加新模块,需要新功能,而此时 Nginx又在跑着业务无法停掉,这时我们就可能选择平滑升级
- 平滑升级:即业务无感知的版本迭代
- 数据安全:不会强制中断事务的运行,而是等待事务完成运行后,在结束进程
平滑升级步骤流程
- 将旧Nginx二进制文件换成新Nginx程序文件(注意先备份)
- 向master进程发送USR2信号
- master进程修改pid文件名加上后缀.oldbin,成为nginx.pid.oldbin
- master进程用新Nginx文件启动新master进程成为旧master的子进程,系统中将有新旧两个Nginx,主进程共同提供Web服务,当前新的请求仍然由旧Nginx的worker进程进行处理,将新生成的master 进程的PID存放至新生成的pid文件nginx.pid
- 向旧的Nginx服务进程发送WINCH信号,使旧的Nginx worker进程平滑停止
- 向旧master进程发送QUIT信号,关闭老master,并删除Nginx.pid.oldbin文件
- 如果发现升级有问题,可以回滚∶向老master发送HUP,向新master发送QUIT
平滑升级步骤
#下载新软件包
wget -P /var/download http://nginx.org/download/nginx-1.20.2.tar.gz
#获取编译参数(推荐用变量)
Edit=$(/usr/sbin/nginx -V |& awk -F"[:]" '/^configure/{print $2}')
/usr/sbin/nginx -V |& awk -F"[:]" '/^configure/{print $2}' > /apps/oldoptions.txt
#解压包
tar -xvf /var/download/nginx-1.20.2.tar.gz -C /apps/
#编译功能参数
cd /apps/nginx-1.20.2 && ./configure $Edit
#编译安装
make
#查看当前版本
objs/nginx -v
#备份旧版本nginx命令
mv /apps/nginx/sbin/nginx{,.old}
#复制新版本nginx命令过去
cp ./objs/nginx /apps/nginx/sbin/
#测试配置
/apps/nginx/sbin/nginx -t |& grep -q "successful" || echo "失败"
#USR2 平滑升级可执行程序,将存储有旧版本主进程PID的文件重命名为nginx.pid.oldbin,并启动新的nginx
#此时两个master的进程都在运行,只是旧的master不在监听,由新的master监听80
#此时Nginx开启一个新的master进程,这个master进程会生成新的worker进程,这就是升级后的Nginx进程,此时老的进程不会自动退出,但是当接收到新的请求不作处理而是交给新的进程处理。
#发送信号USR2,即平滑升级信号,会开启新的master,但不会关闭旧的master进程
kill -USR2 `cat /apps/nginx/run/nginx.pid`
##先关闭旧nginx的worker进程,而不关闭nginx主进程方便回滚
#向原Nginx主进程发送WINCH信号,它会逐步关闭旗下的工作进程(主进程不退出),这时所有请求都会由新版Nginx处理
#如果旧版worker进程有用户的请求,会一直等待处理完后才会关闭
kill -WINCH `cat /apps/nginx/run/nginx.pid.oldbin`
#发出关闭信号,结束旧的master进程(推荐生成计划任务,等一周后执行)
date=$(date "+%u")
tee /etc/cron.d/nginx_cron <<EOF
* * * * $date root kill -QUIT `cat /apps/nginx/run/nginx.pid.oldbin`
EOF
#测试
#在nginx服务端,生成1GB大小的资源
dd if=/dev/zero of=test.img bs=1M count=1024
#在客户端限制下载速度,然后下载保持持续链接
wget --limit-rate=1k http://192.168.213.123/test.img
#回滚,结束旧的master进程才可执行
#回滚判断点:判断旧的master进程是否还存在,若不存在则不允许回退,若存在,则允许回退,并拉起旧进程,关闭新进程(前提:关闭业务)
kill -HUP `cat /apps/nginx/run/nginx.pid.oldbin`
kill -WINCH `cat /apps/nginx/run/nginx.pid`
#关闭新版的master进程
kill -QUIT `cat /usr/nginx/run/nginx.pid`
平滑升级回滚步骤
#在关闭旧master进程之前均可以回滚
#如果升级的版本发现问题需要回滚,可以重新拉起旧版本的worker进程
kill -HUP `cat /apps/nginx/run/nginx.pid.old`
#关闭新版本master
kill -QUIT `cat /apps/nginx/run/nginx.pid`
平滑升级&回滚脚本
该脚本当前只支持CentOS 7.X,且未包含回滚部分
#!/bin/bash
Install_Url=/apps/nginx
Upgrade_PkName="nginx-1.20.2"
Grt_url="http://nginx.org/download"
Pack_url="/var/download"
Suffix=tar.gz
new_version=$(echo ${Upgrade_PkName}|awk -F"[.]" '{print $2}')
old_version=$(nginx -V |& sed -rn "s/.*\/.\.(.*)\../\1/p")
function color_title()
{
title_color="echo -en \\033[01;34m"
title_end="echo -en \E[0m"
[ "$2" = 0 ] && ${title_color}
echo "$1"
${title_end}
}
function color_sign()
{
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
Clock_red="echo -en \\033[01;31m"
clock_green="echo -en \\033[01;32m"
Clock_yellow="echo -en \\033[01;33m"
Clock_end="echo -en \E[0m"
echo -n "$1" && $MOVE_TO_COL
echo -n "["
if [ "$2" = "success" ] || [ "$2" = "0" ];then
${clock_green}
echo -n $"OK "
elif [ "$2" = "failure" ] || [ "$2" = "1" ];then
${Clock_red}#!/bin/bash
Install_Url=/apps/nginx
Upgrade_PkName="nginx-1.20.2"
Grt_url="http://nginx.org/download"
Pack_url="/var/download"
Suffix=tar.gz
new_version=$(echo ${Upgrade_PkName}|awk -F"[.]" '{print $2}')
old_version=$(nginx -V |& sed -rn "s/.*\/.\.(.*)\../\1/p")
function color_title()
{
title_color="echo -en \\033[01;34m"
title_end="echo -en \E[0m"
[ "$2" = 0 ] && ${title_color}
echo "$1"
${title_end}
}
function color_sign()
{
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
Clock_red="echo -en \\033[01;31m"
clock_green="echo -en \\033[01;32m"
Clock_yellow="echo -en \\033[01;33m"
Clock_end="echo -en \E[0m"
echo -n "$1" && $MOVE_TO_COL
echo -n "["
if [ "$2" = "success" ] || [ "$2" = "0" ];then
${clock_green}
echo -n $"OK "
elif [ "$2" = "failure" ] || [ "$2" = "1" ];then
${Clock_red}
echo -n $"FAILED"
else
${Clock_yellow}
echo -n $"WARNING"
fi
${Clock_end}
echo -n "]"
echo
}
function os_type()
{
awk -F'[ "]' '/^NAME/{print $2}' /etc/os-release
}
function os_version()
{
awk -F'"' '/^VERSION_ID/{print $2}' /etc/os-release
}
function check()
{
#OS判断
if [ ! "$(os_type)" == "CentOS" ] && [ ! "$(os_version)" == "7" ];then
color_sign "当前版本不支持" 1
exit
fi
#判断版本大小,若new小于old则不升级
if [ "$new_version" -le "$old_version" ];then
color_sign "升级失败,当前已安装版本比目标版本新" 1
exit
fi
}
function Upgrade()
{
color_title "###############Nginx版本平滑升级开始###############" 0
color_title "----------获取编译参数----------" 0
Edit=$(/usr/sbin/nginx -V |& awk -F"[:]" '/^configure/{print $2}')
[ -n "$Edit" ] && echo "当前参数为$Edit"
color_title "----------软件包处理----------" 0
if [ -f "$Pack_url/$Upgrade_PkName.$Suffix" ];then
color_sign "$Upgrade_PkName已存在,正常升级" 0
else
wget -P $Pack_url "$Grt_url/$Upgrade_PkName.$Suffix" || exit 1
color_sign "下载$Upgrade_PkName完成" 0
tar -xvf /var/download/nginx-1.20.2.tar.gz -C $Pack_url && color_sign"解压$Upgrade_PkName完成" 0
fi
color_title "----------编译软件包----------" 0
cd ${Pack_url}/${Upgrade_PkName} || exit
./configure $Edit && make
#备份旧的命令文件
mv $Install_Url/sbin/nginx{,.old}
#复制新版本nginx命令
cp ./objs/nginx ${Install_Url}/sbin/
#测试
nginx -t |& grep -q "successful" && color_sign "编译完成" 0
}
function process_handling()
{
#获取旧master进程下的所有worker进程
old_subprocess=$(pstree -p "$(cat $Install_Url/run/nginx.pid)" |sed -rn "s/.*\(([0-9]+)\)$/\1/p")
#发送平滑升级信号
kill -USR2 "$(cat $Install_Url/run/nginx.pid)"
old_process=$(cat $Install_Url/run/nginx.pid.oldbin)
read -r -p "是否马上关闭旧的nginx进程(y/n)" Options_2
case $Options_2 in
Y|y)
#等待事务结束后关闭旧的Nginx进程
kill -WINCH "$(cat $Install_Url/run/nginx.pid.oldbin)"
while :;do
Count=$( echo "$old_subprocess"|wc -l )
for i in ${old_subprocess};do
pgrep nginx |grep "$i" || (( Count-- ))
done
[ "$Count" -eq 0 ] && break
sleep 3
# old_subprocess=$(pstree -p "$(cat $Install_Url/run/nginx.pid.oldbin)" |awk -F"[+|\`]" '{print $3}')
# [ -z "$old_subprocess" ] && break
done
kill -QUIT "$old_process"
sleep 10
pgrep nginx |grep -q "$old_process" || color_sign "升级完成" 0
#这里如果设置了server_tokens off这个参数,则无法获取
echo "当前版本:$(curl -I 127.0.0.1 |& sed -rn "s/^Server: (.*)/\1/p")"
;;
N|n)
#等待事务结束后关闭旧的Nginx进程
kill -WINCH "$(cat $Install_Url/run/nginx.pid.oldbin)"
#生成脚本
tee /apps/nginx_cron.sh <<EOF
while :;do
old_subprocess=$(pstree -p "$(cat $Install_Url/run/nginx.pid.oldbin)" |awk -F"[+|\`]+" '{print \$3}')
[ -z \"\$old_process\" ] && break
done
kill -QUIT "$(cat $Install_Url/run/nginx.pid.oldbin)"
echo \$\$ > /root/cron.pid
EOF
tee /apps/if_nginx_cron.sh <<EOF
while :;do
if ! pgrep sh |grep -q "$(cat /root/cron.pid)" && ! pgrep nginx |grep -q "$old_process";then
rm -f /etc/cron.d/nginx_cron
rm -f /apps/nginx_cron.sh
rm -f /root/cron.pid
break
fi
done
EOF
#一周后执行
Week=$(date "+%u")
Hour=$(date "+%H")
tee /etc/cron.d/nginx_cron <<EOF
0 $(( Hour-1 )) * * $Week root nohub sh /apps/nginx_cron.sh
0 $(( Hour-1 )) * * $Week root nohub sh /apps/if_nginx_cron.sh
EOF
;;
*)
color_sign "选项输入" 1
;;
esac
}
color_title
color_sign
os_type
os_version
check
Upgrade
process_handling