ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx编译安装平滑升级全攻略:从进程原理到回滚方案

Nginx编译安装平滑升级全攻略:从进程原理到回滚方案 接手过不少线上 nginx 升级的活儿说实话每次听到“平滑升级”这四个字第一反应不是激动而是先出一身汗。尤其是用了编译安装方式部署的 nginx意味着这台机器大概率承载着核心业务容不得半点闪失。但平滑升级又是一个不得不掌握的技能——安全补丁要打、新模块要加、老版本被扫描出漏洞要修总不能每次升级都挑凌晨三四点停机维护吧。这篇文章就聚焦在 Linux 环境下用编译安装方式部署的 nginx 怎么做平滑升级。我会从进程原理讲到实际操作再到失败回滚把我这些年踩过的坑和养成的习惯全部交代一遍。项目正文里的描述很简洁但热搜词里那些nginx配置文件详解nginx反向代理nginx 负载均衡配置等高频词说明大家其实更关心的是怎么在不动线上配置、不中断服务的前提下把二进制版本换了顺带保证原有模块一个不少、配置全部生效。这才是我下面要解决的核心问题。1. 为什么编译安装的 nginx 必须单独设计升级方案1.1 包管理器升级与编译安装的本质差异很多人一开始接触 nginx 升级第一反应是yum update nginx或者apt upgrade nginx。如果当初是通过 yum/apt 安装的 nginx这样做确实最省事因为包管理器会帮你处理二进制替换、依赖更新和服务重启的整套流程。但如果你当初选择的是编译安装——也就是从官网下载源码包自己 configure、make、make install 的那种——那整条升级路径就要换思路了。编译安装最大的特点在于nginx 二进制文件、配置文件、日志目录、模块扩展都散落在你自己指定的路径里比如常见的/usr/local/nginx包管理器根本感知不到这个目录的存在。你执行yum update nginx系统会告诉你没装这个包。即使硬把 rpm 包装上它管理的也是/etc/nginx下的配置和你/usr/local/nginx/conf/nginx.conf完全是两套体系操作不当甚至会搞乱服务器环境。更关键的是编译安装的 nginx 通常带着非常个性化的 configure 参数——比如启用了--with-http_v2_module、--with-stream、--with-http_realip_module或者静态编译了某个第三方模块。而 yum/apt 仓库里的 nginx 二进制是发行版维护者按他们自己的参数集编译的两者功能集合不一致你升级完发现某个旧配置指令报unknown directive那基本就是编译参数漂移了。所以用编译安装方式部署的 nginx升级就必须走源码重新编译 信号触发切换这条路这也是本文标题里括号特别标注编译安装方式的原因。1.2 什么时候必须走编译安装升级这条路我用实际工作场景来说以下三种情况基本没有别的选择只能编译安装升级第一种是安全漏洞修复。nginx 官方发布新版本修复了 HTTP/2 或 HTTP/3 相关的 CVE 漏洞而线上使用的旧版本恰好有风险。等包管理器同步新版本固然稳妥但大厂的镜像源同步往往有滞后而且 Red Hat / Ubuntu 的老版本 LTS 可能只会推送补丁版本不会给你推送最新主线版。第二种是启用第三方模块或 OpenResty 生态扩展。比如你要接入 lua-nginx-module、nginx-rtmp-module 或 brotli 压缩模块这些模块基本只提供源码必须和 nginx 源码一起编译集成包管理器根本没有对应的二进制包。第三种是定制化编译选项。比如为了配合国密算法或特定的 OpenSSL 库我在某金融项目里就遇到过必须指定--with-openssl/usr/local/openssl-gm的需求这种场景下 yum 安装的 nginx 根本无法满足。1.3 升级前必须确认的三件事说句大实话平滑升级这件事真正的风险不在升级动作本身而在升级前的准备工作。拿这些年带新人的经验来看我会强制让他们在操作前确认三件事一个都不能少先确认当前版本和编译参数执行/usr/local/nginx/sbin/nginx -V把输出完整保存下来后面新版本 configure 时要尽量保持参数一致。确认线上配置文件语法无误执行/usr/local/nginx/sbin/nginx -t保证nginx.conf和所有 include 的配置文件都通过检查。确认磁盘空间充足至少保证/usr/local和源码解压目录所在分区有两倍 nginx 源码包大小以上的剩余空间否则 make 过程中途报错会让你很被动。这三件事你花十分钟做完后面升级就是按部就班的事情。2. nginx 的进程模型与信号调度平滑升级的底层逻辑2.1 master/worker 进程模型与平滑升级的关系要理解平滑升级为什么能平滑必须先看 nginx 的进程模型。nginx 启动后会有两类进程master 进程和 worker 进程。master 进程是管理者负责读取配置、绑定端口、fork 出 worker 进程、接收外部信号worker 进程是实际干活的负责处理客户端请求、转发代理、处理静态文件。这里的核心关键在于master 进程和 worker 进程是分开的worker 进程之间通过 socket 共享监听端口。也就是说当 master 进程收到信号要切换二进制时真正在服务旧请求的是 worker 进程而 worker 进程不会立刻退出它会耐心地把当前正在处理的请求处理完然后才优雅退出。这就给平滑升级留出了操作窗口——新老 worker 进程可以在一段时间内并存各自承担一部分请求。所谓平滑升级本质上就是让 master 进程重新加载一个新的二进制文件然后用旧 worker 逐步退出、新 worker 逐步接管的方式完成版本切换。整个过程从客户端角度看TCP 连接不会断开请求不会中断最多只是新连接被新版本接管、旧连接被老 worker 处理完为止。2.2 信号指令对照表USR2、WINCH、QUIT 分别做了什么nignx 对信号的处理是平滑升级的指令集那组信号就是我们要手动敲给 master 进程的命令。我在实际操作中只用这几个整理成表格方便对照信号作用使用场景HUP重载配置文件平滑重启worker进程配置变更但不换版本USR2启动新的master进程和新的worker进程平滑升级的核心信号新老master并存WINCH逐步关闭旧的worker进程升级切换后让老worker优雅退出QUIT优雅退出处理完当前请求后关闭进程关闭老master进程或某个进程TERM/INT快速退出不等待请求处理完非平滑场景或应急关闭USR1重新打开日志文件日志切割场景平滑升级的主要流程就是围绕USR2、WINCH、QUIT这三个信号展开的。USR2让旧的 master 进程以一个新文件名启动新的 master 进程此时新旧两个 master 同时存在旧的 worker 还在处理请求新的 worker 也已经开始接管新连接。WINCH信号发给旧 master让它优雅地关闭它的 worker 进程但保留旧 master 进程本体这样一旦发现问题还能回滚。QUIT发给旧 master才算彻底把旧 master 生命周期的收尾工作做完。2.3 为什么不推荐用 HUP 信号走完整个升级流程你可能听说过kill -HUP nginx_pid可以重载配置并且重新启动 worker 进程。确实这个信号在日常配置变更时很好用但在二进制版本升级的场景下我完全不推荐只用 HUP。原因在于HUP 只会让 master 进程重新读取配置文件并重新 fork worker 进程但 master 进程自己还在运行旧版本的二进制文件。也就是说master 进程还持有旧代码而 worker 进程已经变成新代码了这种新老混血的状态会带来不可预测的行为尤其是在 master 进程需要派生新 worker 或者处理某些全局逻辑时。更重要的是HUP 升级后如果发现问题回滚的路径很别扭。因为你没法单纯通过 HUP 把 worker 进程的版本降回旧版本必须把旧 master 迁回来操作链路不干净。而利用 USR2 WINCH QUIT 这套标准组合拳回滚路径是天然设计好的——旧 master 只是处于休眠状态随时可以唤醒。所以千万别图省事用 HUP 替代真正的平滑升级流程。3. 编译新版本前的准备参数对齐与源码校验3.1 从旧版本导出完整 configure 参数新版本源码下下来之后第一件事不是急着./configure而是先拿旧版本的编译参数。我一般是这么操作的# 查看当前 nginx 版本号和编译参数 /usr/local/nginx/sbin/nginx -V # 输出示例 # nginx version: nginx/1.20.2 # built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) # configure arguments: --prefix/usr/local/nginx --with-http_ssl_module --with-http_v2_module --with-stream --with-http_realip_module --with-http_stub_status_module把configure arguments这一行完整复制出来这是你配置新版 nginx 时的基准参数。很多人容易忽略的问题在于新版 nginx 源码包可能有新的编译选项比如旧版没有--with-compat新版为了支持动态模块建议加上但如果你不确定这些新增选项是否会影响现有配置就保守一点先保持和旧版本完全一致的参数集升级成功后再考虑增量开启新功能。3.2 编译参数常见差异与模块完整性问题我自己踩过这样一个坑早期编译某版本 nginx 时用的是系统自带的 PCRE 库后来换了一台机器去升级系统库版本比较新新版 nginx 的 configure 居然自动探测到了不同的 PCRE 路径结果编译出来的 nginx 在解析原有正则 location 时行为出现了细微差异。后来我学乖了在 configure 时尽量显式指定依赖库路径避免系统自动探测带来的不确定性。另一个高频问题是第三方模块。如果你的旧 nginx 用了某个第三方模块比如nginx-rtmp-module或者lua-nginx-module你必须确认该第三方模块与新版本 nginx 的兼容性。最稳妥的办法是先看模块的官方文档有没有声明支持到哪个 nginx 主线版本然后在新版本源码解压目录下先做一次完整的 configure make确认没有编译错误再继续。如果第三方模块代码较老可能会因为 nginx 内部 API 变动而编译失败这时候你需要提前找替代方案或补丁而不是等到升级现场才临时抱佛脚。3.3 依赖库校验pcre、openssl、zlib编译 nginx 依赖的库主要是 PCRE、OpenSSL 和 zlib。PCRE 负责正则表达式解析OpenSSL 提供 TLS/SSL 支持zlib 负责 gzip 压缩。这三者的版本直接影响你后续使用的功能范围和安全性。我建议在新机器或新环境上编译时先用命令确认这些库的版本# 检查系统已安装的动态库 pcre-config --version 2/dev/null || rpm -qa | grep pcre openssl version zlib-config --version 2/dev/null || rpm -qa | grep zlib如果你需要特定版本的 OpenSSL 来支持 TLS 1.3 或国密算法那就要下载 OpenSSL 源码包然后在 configure 时通过--with-openssl/path/to/openssl-src指定。这样 nginx 会编译时静态链接该 OpenSSL 源码不会受系统库版本干扰。不过这么做也有代价静态链接后系统升级 OpenSSL 库也不会影响 nginx但你需要自己关注 OpenSSL 的安全公告及时重编译 nginx。3.4 编译与安装路径问题编译安装的细节直接决定升级动作成败这里有个关键认知要提前建立nginx 的 configure 参数里--prefix指定的是安装根目录默认是/usr/local/nginx。而make install会向这个目录写入二进制、配置文件和 html 静态文件。麻烦点在于如果你直接在新版本源码目录里make install它会覆盖掉原目录下的nginx.conf和 html 页面这是绝对不可接受的。我个人的做法是永远只把make install当作安装到临时目录的辅助手段真正生产环境升级时根本不用make install而是手动将编译生成的objs/nginx二进制文件复制到安装目录。这样能最大程度保护配置文件和静态资源。后面第 4 部分我会详细拆解这个过程。源码解压的路径也有讲究我习惯放在/tmp/nginx-build/下并且不同版本用不同子目录隔离比如/tmp/nginx-build/nginx-1.22.1/。这样即使编译失败或要清理也不会影响目录结构。4. 平滑升级完整操作流程从备份到信号切换4.1 备份旧版本二进制与配置这是整套流程里我最不厌其烦强调的一步。很多教程会说平滑升级很安全不用备份但我见过太多因为没备份而翻车的案例。备份其实只需要两三条命令却能让你在出问题时少流三小时的汗。# 1. 备份当前 nginx 二进制 cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak.$(date %Y%m%d) # 2. 备份整个配置目录 cp -r /usr/local/nginx/conf /usr/local/nginx/conf.bak.$(date %Y%m%d) # 3. 确认备份成功 ls -l /usr/local/nginx/sbin/ ls -l /usr/local/nginx/conf.bak.* | head这里有个细节值得注意备份二进制时我建议把nginx和nginx.bak.*放在同一个目录下这样后续通过信号切换时旧 master 进程依赖的路径语义保持一致。如果放到其他目录某些老版本 nginx 在启动新 master 时会因为找不到同目录下的某些辅助文件而出问题。4.2 编译新版 nginx 但不覆盖现有目录备份完成后回到源码目录进行配置和编译。我的习惯是先make不make install这样只生成二进制不动任何目录文件。# 进入新版源码目录 cd /tmp/nginx-build/nginx-1.22.1/ # 使用旧版本的 configure 参数加上必要的路径定义 ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-stream \ --with-http_realip_module \ --with-http_stub_status_module \ --with-pcre/opt/pcre-8.45 \ --with-zlib/opt/zlib-1.3 \ --with-openssl/opt/openssl-3.0.13 # 编译生成 objs/nginx make -j4编译完成后先别急着覆盖线上二进制。在临时环境验证一下编译产物的版本和配置加载情况是必要的。可以用-t参数测试新版二进制对配置文件的兼容性。但由于--prefix/usr/local/nginx新版 nginx 默认会使用该目录下的conf/nginx.conf而我们已经备份过配置可以直接测试# 测试新二进制加载现有配置是否正常 /usr/local/nginx/sbin/nginx -t我建议至少多跑几遍nginx -t确保输出是syntax is ok和test is successful两行提示。如果配置里用到第三方模块的指令而你在 configure 时没带上对应模块这个测试阶段就会报错提前暴露问题。4.3 通过信号完成新旧进程切换干净利落的验证通过后正式进入切换环节。我习惯把切换拆成四步每步之间留出观察时间不要一口气执行完否则出故障时很难定位是哪一步引起的。第一步备份旧版二进制文件为当前生效名。严格来说我们前面已经备份了但有些团队会把nginx.bak.20250101和nginx.bak.20250201混淆所以切换前我习惯只保留一个明确的当前二进制副本# 若前面已备份则跳过否则立即备份 cp -f /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak.latest第二步用新编译的二进制覆盖当前的 nginx 文件# 覆盖二进制文件 cp -f /tmp/nginx-build/nginx-1.22.1/objs/nginx /usr/local/nginx/sbin/nginx # 确认文件类型和版本 file /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx -V注意这一步执行完磁盘上的 nginx 已经变成新版本了但当前运行的进程还是旧版本它们互不影响。这也是平滑升级能成立的关键前提——你替换的是磁盘文件而不是正在运行的进程镜像。第三步向当前运行的旧 master 进程发送 USR2 信号# 获取旧 master 进程 PID export OLD_MASTER_PID$(cat /usr/local/nginx/logs/nginx.pid) echo old master pid: ${OLD_MASTER_PID} # 发送 USR2 信号给旧 master kill -USR2 ${OLD_MASTER_PID}发送 USR2 后旧 master 会以当前磁盘上的新二进制为模板启动一个全新的 master 进程。此时系统中应该是两个 master 进程并存新 master 会读取同一个nginx.conf重新绑定监听端口并 fork 一批新的 worker 进程。因为旧 worker 还占着连接新 worker 会和旧 worker 共享同样的监听 socket内核的负载均衡会把新的请求分配到新 worker 上。这个时候旧 master 的 PID 会记录在logs/nginx.pid.oldbin文件中有些版本是自动生成的新 master 的 PID 会覆盖写入logs/nginx.pid。你可以用ps -ef | grep nginx看到一排进程其中新旧 worker 混在一起那个场面我第一次见的时候还挺紧张担心是不是配置冲突了其实这是正常的中间状态。第四步观察无异常后再优雅关闭旧 worker 进程# 确认新旧 master 都在运行然后发送 WINCH 给旧 master kill -WINCH ${OLD_MASTER_PID}发送 WINCH 后旧 master 会通知它的所有 worker 进程处理完手头请求后退出。这个过程可能持续几秒到几十秒取决于线上是否有长连接或慢请求。你可以通过ps -ef | grep nginx观察旧 worker 数量逐步减少最终只剩下一个新 master 和新 worker。旧 master 进程本身不会退出这是故意保留的后门。4.4 收尾操作与旧 worker 进程退出确认看到旧 worker 全部退出后不要急着高兴先确认一下新版本的运行状态# 确认当前运行的 nginx 版本 /usr/local/nginx/sbin/nginx -v # 实际运行进程中的 master 版本确认 ps -ef | grep master process # 当前进程状态理想情况下应该是 # nginx: master process /usr/local/nginx/sbin/nginx # nginx: worker process再检查一下端口监听和访问日志确认新连接能正常建立# 查看监听端口状态 ss -lntp | grep 80 curl -I http://127.0.0.1/ tail -f /usr/local/nginx/logs/access.log确认线上 Web 服务正常后才算真正完成升级。最后一步是发送 QUIT 给旧 master彻底退出旧进程# 如果确认新版本一切正常退出旧 master kill -QUIT ${OLD_MASTER_PID} # 清理旧 master 残留的 pid 文件如存在 ls -l /usr/local/nginx/logs/*.pid这里要特别强调如果新版本有问题绝对不要发送 QUIT而是要把回滚流程执行完第 5 部分我会细讲。5. 升级后的验证与一键回滚方案5.1 升级后必须检查的指标升级完成不等于万事大吉。我以前就经历过升级很顺、隔天发现监控报警的尴尬后来学乖了每次升级后强制自己按清单检查几项硬指标。第一项是请求成功率。升级后至少观察 5 到 10 分钟看看 nginx 自身的access.log里有没有异常的大面积 4xx/5xx 状态码。如果服务有上游后端的话同时看后端服务的日志确认 nginx 与后端的连接没有异常断开。第二项是错误日志。盯着error.log重点看有没有[emerg]、[alert]、[crit]级别的输出。平滑升级后偶尔会出现[warn]级别提示比如配置项在新版本已被标记为废弃这类提醒不致命但要想清楚是否处理。第三项是 worker 进程数量和连接数。ss -s或者 nginx 的 stub_status 模块输出对比升级前的 worker 进程数与连接数正常情况下不会出现数量级差异。如果新 worker 进程数异常多或异常少说明 worker_processes 配置或 cpu 亲和性设置在新版本里的行为有变化要及时排查。第四项是定时任务的异常。如果这台机器上还有日志切割脚本、监控采集脚本或者其他依赖 nginx 二进制文件路径的程序升级后跑一遍这些脚本确保它们调用的是/usr/local/nginx/sbin/nginx -s reload或者nginx -t这类命令时不会因为新版本的输出格式变化而出错。5.2 回滚的完整命令序列哪怕做得再仔细回滚方案也必须有。回滚的原理其实很简单让旧 master 进程重新接管流量退出新 master。具体命令序列如下首先在发送 WINCH 之后、发送 QUIT 之前如果你发现问题还没有把旧 master 杀掉那么回滚很安全。执行kill -HUP ${OLD_MASTER_PID}唤醒旧 master 进程让它重新 fork 一批旧版本 worker 进程。此时旧 worker 和新 worker 并存流量会回到旧版本的处理逻辑中。接着用kill -QUIT关闭新 master# 恢复旧 master 的 worker 进程 export OLD_MASTER_PID$(cat /usr/local/nginx/logs/nginx.pid.oldbin) kill -HUP ${OLD_MASTER_PID} # 关闭新版 master 和它的 worker kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid)但如果已经发送了 QUIT 给旧 master那旧 master 彻底退出了回滚方式只能靠启动备份的旧二进制# 用备份的旧二进制替换当前二进制 cp -f /usr/local/nginx/sbin/nginx.bak.latest /usr/local/nginx/sbin/nginx # 用旧二进制启动一个全新的 nginx内部会 fork 新 master 和新 worker /usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx # 关闭当前运行的新版 master kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid 2/dev/null)注意这种回滚会导致短暂的停机窗口所以最稳妥的回滚时间点永远在发送 QUIT 之前。这也是我为什么反复强调网上很多教程把最后一步 QUIT 写得非常随意这就埋下了隐患。5.3 快速回滚的脚本化思路考虑到生产环境的紧张程度我把回滚流程固定成了一个脚本放在/usr/local/nginx/rollback.sh平时不执行出问题时一跑就行。脚本的逻辑就是把上面的命令串起来加上日志输出和状态判断。这个脚本的重点是明确当前是已发 WINCH 未发 QUIT还是已发 QUIT因为操作路径完全不同不能让运维在半夜三更去翻教程。#!/bin/bash # 回滚 nginx 升级 set -e NGINX_DIR/usr/local/nginx LOG_FILE/var/log/nginx_rollback.log echo $(date %F %T) start nginx rollback ${LOG_FILE} # 判断 oldbin 文件是否存在 if [ -f ${NGINX_DIR}/logs/nginx.pid.oldbin ]; then OLD_MASTER_PID$(cat ${NGINX_DIR}/logs/nginx.pid.oldbin) echo $(date %F %T) old master pid: ${OLD_MASTER_PID} ${LOG_FILE} kill -HUP ${OLD_MASTER_PID} sleep 2 kill -QUIT $(cat ${NGINX_DIR}/logs/nginx.pid) echo $(date %F %T) rollback to old version done ${LOG_FILE} else echo $(date %F %T) oldbin pid not found, restoring binary ${LOG_FILE} cp -f ${NGINX_DIR}/sbin/nginx.bak.latest ${NGINX_DIR}/sbin/nginx ${NGINX_DIR}/sbin/nginx -t kill -QUIT $(cat ${NGINX_DIR}/logs/nginx.pid) sleep 2 ${NGINX_DIR}/sbin/nginx echo $(date %F %T) restore binary and start service done ${LOG_FILE} fi这个脚本我实际用过好几次最典型的场景是升级到某个新版本后客户端突然出现大量 TLS 握手超时一查是因为新版本默认的 SSL 配置对加密套件的要求更严格线上还有些老客户端不兼容。我直接跑脚本回到旧版本然后从容调整配置后再次升级整个过程也就一分钟的事。6. 实战中容易踩的坑与排查思路6.1 编译参数不一致导致的模块静默丢失这个坑我见得最多坑得也最狠。某次升级 nginx 后所有页面突然开始 404翻看error.log发现是rewrite相关规则全部失灵。排查半天才意识到旧的编译参数里带了--with-pcre-jit而新版本 configure 时我没带这个参数JIT 编译支持丢失后某些正则重写规则在新版本里走了不同的处理路径最终出现了地址无法匹配的问题。所以升级后的验证阶段一定要做一次全站链接扫描或核心接口回归。你可以在升级前后各跑一次相同的自动化测试用例把核心的 rewrite、proxy_pass、负载均衡策略、SSL 证书加载这些逻辑全部覆盖到。不要因为nginx -t通过就放松警惕nginx -t只检查配置语法不检查行为一致性。6.2 升级后配置直接报错的场景有时候新版本的指令集会发生变更旧配置里用了某个在新版本中已被移除或改名的指令。常见的有ssl指令在新版本中被要求放到server块或者http2的开启方式从listen 443 ssl http2改成了listen 443 ssl; http2 on;这样的变化。这类问题基本在nginx -t测试时就会暴露出来所以每次编译新版本后用新二进制-t检查线上配置这个动作绝不能省。更隐蔽的情况是配置语法没问题但某些模块在新版本中默认行为变了。比如旧版本proxy_set_header的默认行为可能在新版本里有所不同。这时候最有效的排查方式不是翻 changelog而是对比新旧两个二进制在相同配置下的行为差异。我在升级时习惯保留一份旧版本的完整配置副本出现问题后直接跑nginx.bak.latest -t来对照检查就能快速定位是配置兼容性还是模块缺失的问题。6.3 磁盘空间不足与编译中途失败不要小看磁盘空间这个问题。nginx 源码包本身不大但解压后的源码目录加编译中间产物还有你指定的 OpenSSL、PCRE 源码目录加起来很容易超过 2GB。如果你的/tmp分区很小或者/usr/local所在根分区空间不足make会在链接阶段报No space left on device。这种报错会清空你当前编译的中间产物重新 make 往往又能过因为有些临时文件被清理了但如果你没注意磁盘已满反复尝试只会反复失败。我在新机器上编译前习惯看一遍分区空间df -h /tmp /usr/local /opt如果空间确实紧张不要犹豫直接把源码目录放到一个空间充足的大分区下比如/data/build/nginx-1.22.1/。同时把 configure 时--with-openssl指向的源码目录也放到同一分区避免跨分区链接导致性能下降或路径权限问题。6.4 升级后的依赖库动态链接问题有时候你只是升级了 nginx但 nginx 依赖的动态库发生变化会导致新二进制启动时报error while loading shared libraries。最常见的场景是libssl.so、libpcre.so这些库的版本号不匹配。比如系统里只有libssl.so.1.1而新编译的 nginx 链接时依赖libssl.so.3运行时就会加载失败。排查命令很简单ldd /usr/local/nginx/sbin/nginx | grep not found如果发现某个.so报 not found说明当前系统的动态库版本和编译时指定的版本不一致。解决办法要么是让系统安装对应版本的动态库要么是在编译时全部静态链接。个人建议 nginx 编译时尽量把 OpenSSL、PCRE、zlib 都用源码静态链接进去这样运行时就不会受系统库升级的影响复制到同架构的其他机器上也能直接跑。6.5 平滑升级对上游连接的影响平滑升级虽然对客户端友好但对上游后端的连接还是会有微小影响。新 worker 进程启动后它与后端的 keepalive 连接池是空的需要重新建立连接。如果后端服务连接数限制严格新建连接的一瞬间可能会触发后端的连接数峰值。在节假日或业务高峰时期我会避免在这个窗口期升级或者提前通知后端团队关注连接数变化。这个问题在大型代理场景下尤其明显。我之前处理过一个日请求量上亿的 nginx 集群每次升级切换时后端服务会短暂观察到新建连接数翻倍影响不大但如果同时多个节点一起升级后端就有压力了。所以现在我在生产环境做这种升级时会制定分批策略每台升级完观察一段时间确认无误后再升级下一台避免所有节点同时切换造成后端连接风暴。6.6 动态模块的加载路径问题如果你的 nginx 在编译时启用了动态模块也就是 configure 时带了类似--add-dynamic-module/path/to/module的参数那么升级时还要额外注意.so文件的版本一致性。动态模块是和 nginx 主程序一起编译的模块文件内部记录了它依赖的 nginx 版本和 ABI 接口信息。直接用旧版本的 .so 挂到新版本 nginx 上启动时大概率会报module is not binary compatible。这种情况下的处理方式是把新版本动态模块的 .so 文件一并编译并复制到对应目录同时确认配置中load_module指令指向的是新 .so 文件路径而不是旧路径。如果新旧版本跨度较大建议先看官方 changelog 确认动态模块接口是否有变更避免升级后某个特性静默失效。写在最后的个人习惯每次升级完成后我都会在/usr/local/nginx下新建一个UPGRADE_LOG文件把本次升级的时间、源版本、目标版本、configure 参数、操作人、回滚结果全部记录进去。这个习惯一开始觉得多余但后来排查问题或者交接给其他同事时这份日志省了大功夫。另一个小技巧是升级后把当前生效的二进制做一个 md5 记录方便后续做安全基线比对。平滑升级这件事说到底是把换文件和切进程拆成两个可独立操作的步骤再靠 nginx 的信号机制把切换过程做得足够平滑。这篇内容看着命令不多但每一步背后都有考究真正动手时建议先把本文第 3、4、5 部分通读两遍在测试环境完整演练一次再去生产操作。毕竟 nginx 这种基础设施稳一小时不如稳一年。
返回列表