nginx平滑升级
Nginx 平滑升级(又称热升级)是运维必会的技能之一。本文通过源码编译安装 Nginx 1.26.3,模拟用户正在下载大文件的场景,逐步升级到 1.28.0,全程下载不中断。文章涵盖:为什么 dnf 安装不行、USR2/WINCH/QUIT 三个信号的作用、新旧进程如何共用端口、以及升级失败如何回滚。手把手带你完成一次真正的零停机升级。
大概思路:
关于平滑升级,其核心在于:
服务从旧到新平滑切换,但用户无感知
所以需要模拟用户无感知:
这里用下载文件来模拟,规定带宽,确保时间够长足以完成nginx平滑升级
关于平滑升级的步骤:
1.有旧版本运行
2.新版本和旧版本共存
3.新版本替换旧版本服务
在这个过程中要保证下载文件不中断
旧版本nginx
练习平滑升级,建议卸载 dnf 版,改用源码编译安装。
因为 dnf 安装的 nginx 没有提供平滑升级需要的信号机制控制。
简单说:
- dnf 安装 :文件分散在系统目录,升级时会直接覆盖二进制、重启服务 ,无法手动发
USR2、WINCH、QUIT信号来控制新旧进程共存。- 源码编译 :可以自定义安装路径(如
/opt/nginx),手动替换二进制、手动发信号,实现新旧共存、无感知切换。
bash
[root@bogon ~]# dnf remove -y nginx
#环境依赖
[root@bogon ~]# dnf install -y gcc-c++ pcre-devel openssl-devel zlib-devel
#下载nginx包
[root@bogon ~]# wget https://nginx.org/download/nginx-1.26.3.tar.gz
#解压
[root@bogon ~]# tar -zxf nginx-1.26.3.tar.gz -C /usr/local/src/
#创建一个专门运行 nginx 的系统用户,并禁止其登录。
[root@bogon ~]# useradd -M -r -s /sbin/nologin nginx
useradd: user 'nginx' already exists
[root@bogon ~]# id nginx
uid=995(nginx) gid=994(nginx) groups=994(nginx)
#应该是之前dnf安装自动创建了,可以直接用
#配置nginx服务
[root@bogon ~]# cd /usr/local/src/nginx-1.26.3/
[root@bogon nginx-1.26.3]# ./configure \
--prefix=/opt/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_sub_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module
#编译&安装
[root@bogon nginx-1.26.3]# make && make install
#启动服务
root@bogon nginx-1.26.3]# /opt/nginx/sbin/nginx
#查看进程
[root@bogon nginx-1.26.3]# ps -ef | grep nginx
root 8112 1 0 15:30 ? 00:00:00 nginx: master process /opt/nginx/sbin/nginx
nginx 8113 8112 0 15:30 ? 00:00:00 nginx: worker process
root 8115 1597 0 15:31 pts/0 00:00:00 grep --color=auto nginx
[root@bogon nginx-1.26.3]# ps auxf | grep nginx
root 8119 0.0 0.1 6416 2280 pts/0 S+ 15:31 0:00 \_ grep --color=auto nginx
root 8112 0.0 0.1 11872 2400 ? Ss 15:30 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 8113 0.0 0.3 16108 5356 ? S 15:30 0:00 \_ nginx: worker process
#可以看到master的pid为8112,而其子进程worker的pid为8113
#测试服务
[root@bogon nginx-1.26.3]# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/usr/local/src就是"放源码的公共文件夹",方便统一管理手动编译的软件源码。
为什么要创建nginx用户和组?
源码安装不会自动创建nginx用户和组
为了安全,让 Nginx 的工作进程(Worker Process)不以 root 身份运行。
关于启动服务
如果是rpm/dnf安装一般会有.service文件,可直接用systemctl命令来管理;
但源码安装则没有这个文件,源码安装的 nginx 在
/opt/nginx/目录下,systemd 不认识它,只能直接运行二进制文件启动。不清楚是哪个文件可以找一下/opt/nginx/下面的目录,一般都是sbin/下面
关于进程查看
ps -ef | grep nginx:横向列出完整信息(标准格式)ps auxf | grep nginx:纵向带树形结构,能看清父子关系(f选项显示进程树)
准备下载文件
用 dd 造一个 10MB 的假视频文件,专门用来测试平滑升级时连接会不会断。
1.生成文件
bash
[root@bogon ~]# cd /opt/nginx/html/
[root@bogon html]# ls
50x.html index.html
[root@bogon html]# dd if=/dev/zero of=./download.avi bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.003529 s, 3.0 GB/s
[root@bogon html]# ll
total 10248
-rw-r--r--. 1 root root 497 Apr 8 15:20 50x.html
-rw-r--r--. 1 root root 10485760 Apr 8 15:41 download.avi
-rw-r--r--. 1 root root 615 Apr 8 15:20 index.html
2.下载文件
bash
[root@bogon html]# wget --limit-rate=1K http://localhost/download.avi
--2026-04-08 15:47:24-- http://localhost/download.avi
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:80... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M) [video/x-msvideo]
Saving to: 'download.avi.1'
download.avi.1 0%[ ] 2.81K 1024 B/s
新版本nginx
复制当前会话
bash
#下载新版本压缩包
[root@bogon ~]# wget https://nginx.org/download/nginx-1.28.0.tar.gz
#解压
[root@bogon ~]# tar -zxf nginx-1.28.0.tar.gz -C /usr/local/src
#配置
[root@bogon ~]# cd /usr/local/src/nginx-1.28.0/
[root@bogon nginx-1.28.0]# ls
auto CHANGES.ru conf contrib html man SECURITY.md
CHANGES CODE_OF_CONDUCT.md configure CONTRIBUTING.md LICENSE README.md src
[root@bogon nginx-1.28.0]# ./configure \
--prefix=/opt/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_sub_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module
#编译
[root@bogon nginx-1.28.0]# make
#查看编译后的目录文件
[root@bogon nginx-1.28.0]# ls
auto CHANGES.ru conf contrib html Makefile objs SECURITY.md
CHANGES CODE_OF_CONDUCT.md configure CONTRIBUTING.md LICENSE man README.md src
#查看objs
[root@bogon nginx-1.28.0]# ll objs
total 5800
-rw-r--r--. 1 root root 18360 Apr 8 15:50 autoconf.err
-rw-r--r--. 1 root root 54316 Apr 8 15:50 Makefile
-rwxr-xr-x. 1 root root 5776648 Apr 8 15:51 nginx
-rw-r--r--. 1 root root 5513 Apr 8 15:51 nginx.8
-rw-r--r--. 1 root root 8380 Apr 8 15:50 ngx_auto_config.h
-rw-r--r--. 1 root root 657 Apr 8 15:50 ngx_auto_headers.h
-rw-r--r--. 1 root root 9198 Apr 8 15:50 ngx_modules.c
-rw-r--r--. 1 root root 41040 Apr 8 15:51 ngx_modules.o
drwxr-xr-x. 9 root root 91 Apr 8 15:50 src
仅编译,一定不要安装:
make install会覆盖配置文件、静态文件等,可能导致配置丢失。平滑升级只需要替换二进制文件。
关于objs目录:
objs目录是 Nginx 源码编译过程中生成的中间文件和目标文件的存放目录。包含:
- 编译产生的
.o对象文件- 最终的
nginx可执行二进制文件- 依赖关系文件、模块链接文件等编译产物
平滑升级
1.查看当前运行的nginx版本
bash
[root@bogon nginx-1.28.0]# /opt/nginx/sbin/nginx -v
nginx version: nginx/1.26.3
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11317 0.0 0.1 6416 2280 pts/1 S+ 16:01 0:00 \_ grep --color=auto nginx
root 8112 0.0 0.1 11872 2400 ? Ss 15:30 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 8113 0.0 0.3 16108 5736 ? S 15:30 0:00 \_ nginx: worker process
[root@bogon nginx-1.28.0]#
还是旧版本
2.备份旧版本的nginx可执行文件
直接移动就可以。
nginx 服务已经在内存中运行了,可执行文件只是磁盘上的一个文件。不会影响当前nginx服务
bash
[root@bogon nginx-1.28.0]# mkdir -p /bak/nginx
[root@bogon nginx-1.28.0]# mv /opt/nginx/sbin/nginx /bak/nginx
3.用新版本替代旧版本
就是将新版本的可执行文件复制到 /opt/nginx/sbin/ 目录下
bash
[root@bogon nginx-1.28.0]# cp -f objs/nginx /opt/nginx/sbin/
#查看当前nginx进程pid
[root@bogon nginx-1.28.0]# cat /opt/nginx/logs/
access.log error.log nginx.pid
[root@bogon nginx-1.28.0]# cat /opt/nginx/logs/nginx.pid
8112
#查看当前进程
root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11351 0.0 0.1 6416 2284 pts/1 S+ 16:06 0:00 \_ grep --color=auto nginx
root 8112 0.0 0.1 11872 2400 ? Ss 15:30 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 8113 0.0 0.3 16108 5736 ? S 15:30 0:00 \_ nginx: worker process
[root@bogon nginx-1.28.0]#
nginx进程pid仍然是旧版本
4.新旧共存
发送USR2信号给当前运行nginx进程,
让旧版 nginx 主进程启动新版 nginx 进程,实现新旧共存。
bash
[root@bogon nginx-1.28.0]# kill -USR2 `cat /opt/nginx/logs/nginx.pid`
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11392 0.0 0.1 6416 2276 pts/1 S+ 16:11 0:00 \_ grep --color=auto nginx
root 8112 0.0 0.1 11872 2812 ? Ss 15:30 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 8113 0.0 0.3 16108 5736 ? S 15:30 0:00 \_ nginx: worker process
root 11389 0.0 0.4 11908 7332 ? S 16:11 0:00 \_ nginx: master process /opt/nginx/sbin/nginx
nginx 11390 0.0 0.3 16160 5476 ? S 16:11 0:00 \_ nginx: worker process
[root@bogon nginx-1.28.0]# ls /opt/nginx/logs/
access.log error.log nginx.pid nginx.pid.oldbin
[root@bogon nginx-1.28.0]#
5.关闭旧nginx的worker进程
关闭旧版本的 worker 进程,保留旧 master。
旧 master 还在,但它的 worker 全没了,不再处理新请求。新请求全由新版本 worker 处理。
为什么要这样:
- 旧 worker 退出 → 新版本完全接管服务
- 旧 master 保留 → 万一新版本有问题,可以随时唤醒旧 worker 回滚
bash
[root@bogon nginx-1.28.0]# kill -WINCH `cat /opt/nginx/logs/nginx.pid.oldbin`
[root@bogon nginx-1.28.0]#
[root@bogon nginx-1.28.0]# ls /opt/nginx/logs/
access.log error.log nginx.pid nginx.pid.oldbin
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11398 0.0 0.1 6416 2284 pts/1 S+ 16:13 0:00 \_ grep --color=auto nginx
root 8112 0.0 0.1 11872 2816 ? Ss 15:30 0:00 nginx: master process /opt/nginx/sbin/nginx
root 11389 0.0 0.4 11908 7332 ? S 16:11 0:00 \_ nginx: master process /opt/nginx/sbin/nginx
nginx 11390 0.0 0.3 16160 5476 ? S 16:11 0:00 \_ nginx: worker process
6.退出旧nginx的master
彻底退出旧版本的 master 进程,完成升级。
只剩下新版本 master 和它的 worker; nginx.pid.oldbin文件被删除(或不再存在)
bash
[root@bogon nginx-1.28.0]# kill -QUIT `cat /opt/nginx/logs/nginx.pid.oldbin`
[root@bogon nginx-1.28.0]#
[root@bogon nginx-1.28.0]# ls /opt/nginx/logs/
access.log error.log nginx.pid
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11402 0.0 0.1 6416 2288 pts/1 S+ 16:13 0:00 \_ grep --color=auto nginx
root 11389 0.0 0.4 11908 7332 ? S 16:11 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 11390 0.0 0.3 16160 5476 ? S 16:11 0:00 \_ nginx: worker process
[root@bogon nginx-1.28.0]#
7.查看当前nginx服务版本
此时新nginx已经完全接手,查看当前nginx服务的版本号肯定是新版本。
bash
[root@bogon nginx-1.28.0]# /opt/nginx/sbin/nginx -v
nginx version: nginx/1.28.0
检查下载窗口,没有中断
bash
download.avi.1 16%[+++++++++++ ] 1.65M 1024 B/s eta 2h 22m
至此, Nginx 服务器的平滑升级完成。
8.还原之前的版本
利用旧版本可执行文件的备份即可
TERM 信号会优雅退出(处理完当前请求再退) 如果想强制退出可以用 kill -9,但一般不推荐
bash
#停止新版本nginx服务
[root@bogon nginx-1.28.0]# kill -TERM `cat /opt/nginx/logs/nginx.pid`
#查看进程-->无
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11407 0.0 0.1 6416 2284 pts/1 S+ 16:19 0:00 \_ grep --color=auto nginx
#将备份放在/opt/nginx/sbin/nginx(nginx 可执行文件的位置)
[root@bogon nginx-1.28.0]# cp -f /bak/nginx/nginx /opt/nginx/sbin/nginx
cp: overwrite '/opt/nginx/sbin/nginx'? y
#启动
[root@bogon nginx-1.28.0]# /opt/nginx/sbin/nginx
#查看版本和进程
[root@bogon nginx-1.28.0]# /opt/nginx/sbin/nginx -v
nginx version: nginx/1.26.3
[root@bogon nginx-1.28.0]# ps auxf | grep nginx
root 11453 0.0 0.1 6416 2280 pts/1 S+ 16:25 0:00 \_ grep --color=auto nginx
root 11436 0.0 0.1 11872 2408 ? Ss 16:21 0:00 nginx: master process /opt/nginx/sbin/nginx
nginx 11437 0.0 0.3 16108 5336 ? S 16:21 0:00 \_ nginx: worker process
[root@bogon nginx-1.28.0]#
#发现pid和之前新版本不一样
这里下载文件也没有中断,还原完成
一些相关问题:
Q1:平滑升级和热部署的区别?
平滑升级 :换版本(二进制文件),新旧共存切换
热部署 :不换版本,只重载配置(kill -HUP)
Q2:为什么 USR2 后两个 master 能共用同一个端口?
新版 nginx 启动时会从旧 master 继承监听 socket 的文件描述符 ,不需要重新 bind 端口。这是 nginx 源码中通过环境变量
NGINX_VAR传递的。旧 master 把监听端口的文件描述符传给新 master,新进程继承使用,避免重复绑定。
Q3:如果升级后发现有问题,怎么回滚?
1.如果还没发
QUIT(旧 master 还在):
bashkill -HUP \`cat nginx.pid.oldbin\` # 唤醒旧 worker kill -TERM \`cat nginx.pid\` # 停掉新版本2.如果已经发了
QUIT,就只能用备份二进制重启旧版本。
Q4:为什么用 kill -WINCH 而不是直接 -TERM 旧 master?
WINCH只杀 worker,保留 master。这样回滚时只需要-HUP就能唤醒 worker,不需要重新启动整个进程。如果直接-TERMmaster,旧 master 就彻底没了。
Q5:源码编译和包管理器安装,生产环境选哪个?
- 包管理器:方便、易管理、自动处理依赖,适合大多数场景
- 源码编译:可定制模块、可指定路径、支持平滑升级,适合对版本和路径有特殊要求的场景