Nginx平滑升级

目录

一. 为什么要对 nginx 平滑升级

二. Nginx 平滑升级原理

三. 前提准备

四. 安装一个模拟被升级旧Nginx版本

五. 模拟真实业务场景

六. 下载获取一个扩展模块

七. 模拟升级Nginx 


一. 为什么要对 nginx 平滑升级

随着 nginx 越来越流行,并且 nginx 的优势也越来越明显,nginx 的版本迭代也来时加速模式,1.9.0版本的nginx更新了许多新功能,例如 stream 四层代理功能,伴随着 nginx 的广泛应用,版本升级必然越来越快,线上业务不能停,此时 nginx 的升级就是运维的工作了

nginx 方便地帮助我们实现了平滑升级。其原理简单概括,就是: (1)在不停掉老进程的情况下,启动新进程。 (2)老进程负责处理仍然没有处理完的请求,但不再接受处理请求。 (3)新进程接受新请求。 (4)老进程处理完所有请求,关闭所有连接后,停止。 这样就很方便地实现了平滑升级。一般有两种情况下需要升级 nginx,一种是确实要升级 nginx 的版本,另一种是要为 nginx 添加新的模块。

二. Nginx 平滑升级原理

多进程模式下的请求分配方式

nginx 默认工作在多进程模式下,即主进程(master process)启动后完成配置加载和端口绑定等动作,fork出指定数量的工作进程(worker process),这些子进程会持有监听端口的文件描述符(fd),并通过在该描述符上添加监听事件来接受连接(accept)。

信号的接收和处理

nginx 主进程在启动完成后会进入等待状态,负责响应各类系统消息,如SIGCHLD、SIGHUP、SIGUSR2等。

Nginx信号简介

主进程支持的信号

  • TERM, INT: 立刻退出

  • QUIT: 等待工作进程结束后再退出

  • KILL: 强制终止进程

  • HUP: 重新加载配置文件,使用新的配置启动工作进程,并逐步关闭旧进程。

  • USR1: 重新打开日志文件

  • USR2: 启动新的主进程,实现热升级

  • WINCH: 逐步关闭工作进程

工作进程支持的信号

  • TERM, INT: 立刻退出

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

  • USR1: 重新打开日志文件

三. 前提准备

准备一台初始化的虚拟机

localhostRocky_linux9.4192.168.226.20

关闭防火墙和SElinux 

虚拟机基础配置脚本如下进行步骤前,先跑一遍 

#!/bin/bash
# **********************************************************
# * File Name     : rocky_linux
# * Author        : Elk
# * Email         : zzdict@gmail.com / elk_deer@foxmail.com
# * Create time   : 2024-06-15 20:12
# * Description   : 
# **********************************************************
# 检查是否以 root 用户运行脚本
if [ "$(id -u)" -ne 0 ]; then
  tput bold
  tput setaf 1
  tput setaf 3
  echo "请以 root 用户运行此脚本。"
  tput sgr0
  exit 1
fi

# 启用网络接口
enable_network_interface() {
  local interface=$1
  if ip link set "$interface" up; then
    tput bold
	tput setaf 2
    echo "网络接口 $interface 已启用。"
	tput sgr0
  else
    tput bold
	tput setaf 1
    echo "无法启用网络接口 $interface,请检查接口名称。"
	tput sgr0
    exit 1
  fi
}

# 配置 YUM 源
configure_yum_repos() {
  sed -e 's|^mirrorlist=|#mirrorlist=|g' \
      -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
      -i.bak \
      /etc/yum.repos.d/Rocky-*.repo	  
  tput bold
  tput setaf 2
  echo "YUM 源配置已更新。"
  tput sgr0
  dnf makecache
  yum -y install epel-release
}

# 停止和禁用防火墙,禁用 SELinux
configure_security() {
  systemctl stop firewalld && systemctl disable firewalld
  firewall-cmd --reload
  tput bold
  tput setaf 2
  echo "防火墙已停止并禁用。"
  tput sgr0
  sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
  tput bold
  tput setaf 2
  echo "SELinux 已禁用。"
  tput sgr0
}

# 检查并安装chrony,进行时间同步
function install_and_sync_time_with_chrony() {
    # 检查是否已经安装chrony
    if ! command -v chronyd &>/dev/null; then
        sudo dnf install -y chrony &> /dev/null
        # 检查安装是否成功
        if ! command -v chronyd &>/dev/null; then
            tput bold
            tput setaf 1
            echo "安装 chrony 失败。请检查您的包管理器并重试。"
            tput sgr0
            exit 1
        else
            tput bold
            tput setaf 2
            echo "chrony 安装成功。"
            tput sgr0
        fi
    else
        tput bold
        tput setaf 2
        echo "chrony 已安装。"
        tput sgr0
    fi

    # 确保安装其他必要的软件包
    sudo dnf install -y vim wget unzip tar lrzsz &> /dev/null
    if [ $? -eq 0 ]; then
        tput bold
        tput setaf 2
        echo "其他软件包安装成功。"
        tput sgr0
    else
        tput bold
        tput setaf 1
        echo "安装其他软件包失败。"
        tput sgr0
        exit 1
    fi
}


    # 启动 chronyd 服务并启用开机启动
    if sudo systemctl start chronyd && sudo systemctl enable chronyd; then
        tput bold
        tput setaf 2
		echo "chronyd 服务已成功启动并设置为开机启动。"
		tput sgr0
    else
		tput bold
		tput setaf 1
        echo "启动或启用 chronyd 服务失败。请检查 systemctl 状态。"
		tput sgr0
        exit 1
    fi

    # 强制同步时间
    sudo chronyc -a makestep
	tput bold
    tput setaf 2
    echo "时间同步已成功完成。"
	tput sgr0
}


# 自定义 IP 地址
configure_ip_address() {
  tput bold
  tput blink
  tput setaf 1
  read -p "******输入你要设置的IP >>>  : " ip_a
  tput sgr0
  tput bold
  tput blink
  tput setaf 6
  read -p "******输入你要设置的网关>>> : " gat
  tput sgr0
  tput bold
  tput blink
  tput setaf 3
  read -p "******输入你要设置的DNS>>>  : " dnns
  tput sgr0

  # 判断当前连接的名字
  connection_name=$(nmcli -t -f NAME,DEVICE con show --active | grep -E "ens33|Wired connection 1" | cut -d: -f1)

  if [[ "$connection_name" == "ens33" ]]; then
    # 针对 ens33 连接进行配置
    nmcli con mod "ens33" ipv4.method manual ipv4.addresses "${ip_a}/24" ipv4.gateway "${gat}" ipv4.dns "${dnns}" autoconnect yes
  elif [[ "$connection_name" == "Wired connection 1" ]]; then
    # 针对 Wired connection 1 连接进行配置
    nmcli con mod "Wired connection 1" ipv4.method manual ipv4.addresses "${ip_a}/24" ipv4.gateway "${gat}" ipv4.dns "${dnns}" autoconnect yes
  else
	tput bold
	tput setaf 1
    echo "无法识别的网络连接名称:$connection_name"
	tput sgr0
    return 1
  fi

  tput setab 5
  tput setaf 15
  tput bold
  echo "IP 地址配置成功,即将重启系统。"
  tput sgr0

  nmcli con up "$connection_name"
  reboot
}


# 主函数
main() {
  local interface="ens33"
  enable_network_interface "$interface"
  configure_yum_repos
  configure_security
  install_and_sync_time_with_chrony
  configure_ip_address "$interface"
}

# 调用主函数
main

来到官网下载两个不同的版本

官网:nginx: download

下载两个不同版本进行,从低版本升级高版本实验。

将下载好的两个安装包上传到虚拟机中,如下所示两个安装包

[root@localhost ~]# ll
total 2268
-rw-------. 1 root root     815 Jun  6 14:00 anaconda-ks.cfg
-rw-r--r--  1 root root 1062124 Jun 22 00:38 nginx-1.20.2.tar.gz
-rw-r--r--  1 root root 1244738 Jun 21 21:19 nginx-1.26.1.tar.gz
-rw-r--r--  1 root root    4251 Jun 17 23:50 rocky_linux.sh

四. 安装一个模拟被升级旧Nginx版本

1.安装依赖工具 

[root@localhost ~]# yum install -y gcc gcc-c++ pcre-devel openssl-devel zlib-devel

2.解压1.20.2版本安装包

[root@localhost ~]# ls
anaconda-ks.cfg  nginx-1.20.2.tar.gz  nginx-1.26.1.tar.gz  rocky_linux.sh
[root@localhost ~]# tar -zxf nginx-1.20.2.tar.gz 

 3.进入解压后的目录

[root@localhost ~]# ls
anaconda-ks.cfg  nginx-1.20.2  nginx-1.20.2.tar.gz  nginx-1.26.1.tar.gz  rocky_linux.sh
[root@localhost ~]# cd nginx-1.20.2
[root@localhost nginx-1.20.2]# ls
CHANGES  CHANGES.ru  LICENSE  README  auto  conf  configure  contrib  html  man  src

4.预编译 

在这里面,是少两个模块,没写进代码里,用来模拟这样少模块的场景,便于后面升级来扩展。

[root@localhost nginx-1.20.2]# ./configure --prefix=/usr/local/nginx --group=nginx --user=nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/tmp/nginx/client_body --http-proxy-temp-path=/tmp/nginx/proxy --http-fastcgi-temp-path=/tmp/nginx/fastcgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-pcre --with-http_realip_module

5.编译安装

[root@localhost nginx-1.20.2]# make && make install

6.创建一个nginx的系统用户

[root@localhost nginx-1.20.2]# useradd --system --no-create-home --shell /sbin/nologin nginx

7.创建目录用于临时存储客户端请求数据和缓存文件

[root@localhost nginx-1.20.2]# mkdir -p /tmp/nginx/client_body

8.启动nginx

[root@localhost nginx-1.20.2]# /usr/local/nginx/sbin/nginx

9.验证nginx正常工作

[root@localhost nginx-1.20.2]# curl -Ik 192.168.226.20
HTTP/1.1 200 OK
Server: nginx/1.20.2
Date: Sat, 22 Jun 2024 05:16:03 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Sat, 22 Jun 2024 01:00:47 GMT
Connection: keep-alive
ETag: "6676223f-264"
Accept-Ranges: bytes

 或者浏览器打开访问改主机IP地址

五. 模拟真实业务场景

使用go代码对该主机地址进行不间断请求,模拟用户访问,模拟真实业务场景。

通过观察升级过程中是否会有失败,即业务中断的访问。当然,极少数个例的失败是可接受的。 

package main

import (
	"fmt"
	"io"
	"net/http"
	"time"
)

// ANSI color codes
const (
	RedColor    = "\033[31m"
	GreenColor  = "\033[32m"
	YellowColor = "\033[33m"
	ResetColor  = "\033[0m"
)

// 记录成功、失败、无法响应以及服务暂时不可用(503)的请求数量
var successCount int
var failureCount int
var unresponsiveCount int
var tempUnavailableCount int // 新增变量来跟踪503 Service Unavailable状态的请求数量

// makeRequest 向给定的 URL 发送一个 GET 请求,并根据响应的成功与否以不同颜色打印出响应状态和所花费的时间。
func makeRequest(url string, attempt int) {
	startTime := time.Now()
	resp, err := http.Get(url)
	elapsedTime := time.Since(startTime)

	if err != nil {
		// 如果因为网络问题或服务器问题导致请求失败,我们将其计为“无法响应”
		fmt.Printf("[%d] %sRequest unresponsive: %s %.4f seconds%s\n", attempt, RedColor, err, elapsedTime.Seconds(), ResetColor)
		unresponsiveCount++
		return
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			fmt.Printf("Error closing response body: %s\n", err)
		}
	}(resp.Body)

	color := GreenColor
	if resp.StatusCode == 503 {
		color = YellowColor
		tempUnavailableCount++ // 对返回503状态码的请求进行统计
	} else if resp.StatusCode != 200 {
		color = RedColor
		failureCount++
	} else {
		successCount++
	}

	// 根据状态码,以对应的颜色打印响应状态码和耗时
	fmt.Printf("[%d] %s%d %.4f seconds%s\n", attempt, color, resp.StatusCode, elapsedTime.Seconds(), ResetColor)
}

func main() {
	url := "http://192.168.226.20" //选择你要测试的地址
	totalAttempts := 20000         //自定义请求次数
	startTime := time.Now()

	for attempt := 1; attempt <= totalAttempts; attempt++ {
		makeRequest(url, attempt)
		time.Sleep(1 * time.Second) // 在每次请求之间等待1秒
	}

	totalElapsedTime := time.Since(startTime)
	successRate := (float64(successCount) / float64(totalAttempts)) * 100
	failureRate := (float64(failureCount) / float64(totalAttempts)) * 100
	unresponsiveRate := (float64(unresponsiveCount) / float64(totalAttempts)) * 100
	tempUnavailableRate := (float64(tempUnavailableCount) / float64(totalAttempts)) * 100 // 计算503状态码比率

	fmt.Printf("%sTotal elapsed time: %.4f seconds%s\n", ResetColor, totalElapsedTime.Seconds(), ResetColor)
	fmt.Printf("成功率: %.2f%%, 失败率: %.2f%%, 无法响应的请求比: %.2f%%, 服务暂时不可用请求比率(503): %.2f%%\n", successRate, failureRate, unresponsiveRate, tempUnavailableRate)
}

如图: 

六. 下载获取一个扩展模块

1.下载扩展模块 

[root@localhost ~]# cd
[root@localhost ~]# wget https://github.com/zls0424/ngx_req_status/archive/master.zip -O ngx_req_status.zip

2. 解压并查看

[root@localhost ~]# unzip ngx_req_status.zip
Archive:  ngx_req_status.zip
428ffbb511fcb456218b4d2e6fb2f6f3e5abcf08
   creating: ngx_req_status-master/
  inflating: ngx_req_status-master/README  
  inflating: ngx_req_status-master/README.md  
  inflating: ngx_req_status-master/config  
  inflating: ngx_req_status-master/module_patch.sh  
  inflating: ngx_req_status-master/ngx_http_req_status_module.c  
  inflating: ngx_req_status-master/write_filter-1.7.11.patch  
  inflating: ngx_req_status-master/write_filter.patch  
[root@localhost ~]# ll
total 2280
-rw-------. 1 root root      815 Jun  6 14:00 anaconda-ks.cfg
drwxr-xr-x  9 1001  1001     186 Jun 22 00:58 nginx-1.20.2
-rw-r--r--  1 root root  1062124 Jun 22 00:38 nginx-1.20.2.tar.gz
drwxr-xr-x  8  502 games     158 May 29 22:30 nginx-1.26.1
-rw-r--r--  1 root root  1244738 Jun 21 21:19 nginx-1.26.1.tar.gz
drwxr-xr-x  2 root root      169 Oct 21  2015 ngx_req_status-master
-rw-r--r--  1 root root    11237 Jun 22 13:50 ngx_req_status.zip
-rw-r--r--  1 root root     4251 Jun 17 23:50 rocky_linux.sh

3.解压升级的nginx版本压缩包

[root@localhost ~]# cd

[root@localhost ~]# tar -zxf nginx-1.26.1.tar.gz 

4.将该模块拷贝到升级的nginx版本解压后的目录里

[root@localhost ~]# cp -r ngx_req_status-master/ nginx-1.26.1/

5.安装一些依赖包,并确保都已安装

[root@localhost ~]# yum -y install pcre pcre-devel openssl openssl-devel gcc gcc-c++   zlib zlib-devel
[root@localhost ~]# yum -y install patch.x86_64

七. 模拟升级Nginx 

1.进入升级的nginx版本目录里,可以看到刚拷贝进来的扩展模块也在

[root@localhost ~]# cd nginx-1.26.1/
[root@localhost nginx-1.26.1]# ll
total 828
-rw-r--r-- 1  502 games 327587 May 29 22:30 CHANGES
-rw-r--r-- 1  502 games 501144 May 29 22:30 CHANGES.ru
-rw-r--r-- 1  502 games   1397 May 28 21:28 LICENSE
-rw-r--r-- 1  502 games     49 May 28 21:28 README
drwxr-xr-x 6  502 games   4096 Jun 22 13:29 auto
drwxr-xr-x 2  502 games    168 Jun 22 13:29 conf
-rwxr-xr-x 1  502 games   2611 May 28 21:28 configure
drwxr-xr-x 4  502 games     72 Jun 22 13:29 contrib
drwxr-xr-x 2  502 games     40 Jun 22 13:29 html
drwxr-xr-x 2  502 games     21 Jun 22 13:29 man
drwxr-xr-x 2 root root     169 Jun 22 13:58 ngx_req_status-master
drwxr-xr-x 9  502 games     91 May 29 22:30 src

2.应用相应的补丁

[root@localhost nginx-1.26.1]# patch -p1 < ./ngx_req_status-master/write_filter-1.7.11.patch

3.预编译

和上面那个预编译的步骤相比,多加了两个模块 

[root@localhost nginx-1.26.1]# ./configure --prefix=/usr/local/nginx --group=nginx --user=nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/tmp/nginx/client_body --http-proxy-temp-path=/tmp/nginx/proxy --http-fastcgi-temp-path=/tmp/nginx/fastcgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-pcre --with-http_realip_module --with-stream --with-http_image_filter_module --add-module=./ngx_req_status-master

 4.编译

[root@localhost nginx-1.26.1]# make

升级不要再执行make install

5.备份旧版本nginx

[root@localhost nginx]# cd /usr/local/nginx/sbin

[root@localhost sbin]# mv nginx nginx.bak
[root@localhost sbin]# ll
total 4268
-rwxr-xr-x 1 root root 4368512 Jun 22 09:00 nginx.bak

 6.拷贝新版本的nginx到当前目录

[root@localhost sbin]# cp /root/nginx-1.26.1/objs/nginx ./
[root@localhost sbin]# ll
total 9336
-rwxr-xr-x 1 root root 5186960 Jun 22 14:20 nginx
-rwxr-xr-x 1 root root 4368512 Jun 22 09:00 nginx.bak

7.测试新版本的nginx是否正常

[root@localhost sbin]# ./nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

 8.给nginx发送平滑迁移信号(若不清楚pid路径,请查看nginx配置文件)

[root@localhost sbin]# kill -USR2 `cat /var/run/nginx.pid`

9.查看nginx pid,会出现一个nginx.pid.oldbin

[root@localhost sbin]# ll /var/run/nginx.pid*
-rw-r--r-- 1 root root 5 Jun 22 14:25 /var/run/nginx.pid
-rw-r--r-- 1 root root 5 Jun 22 13:14 /var/run/nginx.pid.oldbin

10.查看当前nginx进程

会有两组nginx进程,一组是前面旧版本的13:14分运行的,一组是刚刚14:24运行的,即新起来的进程

[root@localhost sbin]# ps aux |grep nginx
root        1435  0.0  0.1   9556  2528 ?        Ss   13:14   0:00 nginx: master process /usr/local/nginx/sbin/nginx
nginx       1436  0.0  0.2  13948  5088 ?        S    13:14   0:03 nginx: worker process
root        6202  0.0  0.3   9684  6528 ?        S    14:24   0:00 nginx: master process /usr/local/nginx/sbin/nginx
nginx       6203  0.0  0.2  13980  5100 ?        S    14:24   0:00 nginx: worker process
root        6207  0.0  0.1   3876  1920 pts/0    S+   14:26   0:00 grep --color=auto nginx

11.从容关闭旧的Nginx进程

此时旧版本的worker进程会关闭

[root@localhost sbin]# kill -WINCH `cat /var/run/nginx.pid.oldbin`

注意声明:

不重载配置启动旧的工作进程,该步骤是在如果新进程察觉到有问题,回滚启用旧nginx进程。如果没有问题,此步骤不操作。

kill -HUP `cat /var/run/nginx.pid.oldbin`

12.结束工作进程,完成此次升级

[root@localhost sbin]# kill -QUIT `cat /var/run/nginx.pid.oldbin`

 13.验证Nginx是否升级成功

[root@localhost sbin]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.26.1
built by gcc 11.4.1 20231218 (Red Hat 11.4.1-3) (GCC) 
built with OpenSSL 3.0.7 1 Nov 2022
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --group=nginx --user=nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/tmp/nginx/client_body --http-proxy-temp-path=/tmp/nginx/proxy --http-fastcgi-temp-path=/tmp/nginx/fastcgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-pcre --with-http_realip_module --with-stream --add-module=./ngx_req_status-master
[root@localhost sbin]# curl -Ik 192.168.226.20
HTTP/1.1 200 OK
Server: nginx/1.26.1
Date: Sat, 22 Jun 2024 06:38:12 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Sat, 22 Jun 2024 01:00:47 GMT
Connection: keep-alive
ETag: "6676223f-264"
Accept-Ranges: bytes

旧版本的nginx需要观察一段时间,确定不会影响业务需求再进行删除。 

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZDICT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值