08. 日常更新与回滚

1. 本章目标

本章建立代码修改后的发布流程,并准备失败时的回滚路径。第一部分适合当前学习项目;第二部分介绍版本化发布目录,减少“新构建失败却已经覆盖旧文件”的风险。

2. 更新前先判断影响范围

改动 需要重新构建前端 需要重新构建/重启后端 需要 reload Nginx
仅 Vue 源码 是 否 通常否
Express/Service/Model 否 是 否
共享依赖或根 lockfile 按实际影响 按实际影响 否
server/.env 否 需要重启 否
Nginx 配置 否 否 是
PM2 ecosystem 否 需要更新环境并重启 否

即使只修改一端,也建议先运行与改动相关的类型检查和测试。跨前后端或依赖变化执行完整 npm run check。

3. 更新前检查

在本地:

npm ci
npm run check
git status --short
git push

在服务器:

cd /var/www/airport-operations-explorer
git status --short --branch
git rev-parse HEAD
pm2 status
curl -fsS http://127.0.0.1:3000/health

记录 git rev-parse HEAD 输出,它是回滚目标。若服务器 git status 有未提交修改,先查清来源,不要用 git reset --hard 覆盖。

检查新版本是否增加:

4. 基础更新流程

适用于个人学习项目和允许短暂中断的场景。

4.1 拉取指定分支

cd /var/www/airport-operations-explorer
git fetch origin
git switch main
git pull --ff-only origin main
git rev-parse HEAD

--ff-only 在服务器存在分叉提交时停止,不自动创建难以追踪的 merge commit。

4.2 安装锁定依赖

npm ci

它会按 lockfile 重建 node_modules。不要只在“看起来增加了依赖”时执行,否则传递依赖变化可能被遗漏。

4.3 检查和构建

npm run check

这一步完成前不要重启线上 PM2。若失败,旧进程仍在使用旧的已加载代码,但注意直接构建前端可能已经改写 client/dist。更稳妥的方法是后面的版本化目录。

4.4 应用更新

后端变化:

pm2 restart airport-api

environment 配置变化:

pm2 restart ecosystem.config.cjs --only airport-api --update-env

只有 Nginx 配置变化才执行:

sudo nginx -t
sudo systemctl reload nginx

前端构建完成后 Nginx 会读取新的 client/dist,无需重启 Nginx。

4.5 发布后检查

pm2 status
curl -fsS http://127.0.0.1:3000/health
curl -fsS https://airport.cxhair.cn/health
curl -I https://airport.cxhair.cn/
pm2 logs airport-api --lines 100 --nostream

再用浏览器执行一条关键用户链路。健康检查通过不代表业务 SQL 和页面都正确。

5. 为什么单实例更新会有短暂中断

当前 PM2 使用一个 fork 实例。restart 会停止旧进程再启动新进程,中间可能出现短暂 502。学习阶段可以接受,并且更容易理解连接和生命周期。

若未来需要接近零停机:

不要只把 instances 改成 max 就认为获得了可靠零停机。

6. 基础回滚

假设发布前记录的 commit 为 <previous_commit>:

cd /var/www/airport-operations-explorer
git switch --detach <previous_commit>
npm ci
npm run check
pm2 restart airport-api

验证:

curl -fsS http://127.0.0.1:3000/health
curl -fsS https://airport.cxhair.cn/health

恢复到主分支继续下一次发布:

git switch main

如果问题只来自 Nginx 配置,应恢复配置备份、执行 nginx -t 再 reload。不要为代理配置问题回滚整个应用。

数据库变更需要独立回滚计划。索引通常可删除,但操作可能长时间占用资源;DDL 不能假设和代码切换一样瞬间完成。

7. 推荐:版本化发布目录

当基础流程理解清楚后,使用以下结构:

/var/www/airport-explorer/
  releases/
    20260920-120000/
    20260921-093000/
  shared/
    server.env
  current -> releases/20260921-093000/

Nginx root 指向:

/var/www/airport-explorer/current/client/dist

PM2 cwd 指向:

/var/www/airport-explorer/current/server

7.1 创建新 release

release_id=$(date +%Y%m%d-%H%M%S)
release_dir="/var/www/airport-explorer/releases/$release_id"
git clone --branch main --single-branch <仓库URL> "$release_dir"
cd "$release_dir"
npm ci
npm run check

不要把密码复制进 release。共享环境文件可以通过受控软链接提供:

ln -s /var/www/airport-explorer/shared/server.env "$release_dir/server/.env"

确认目标:

readlink -f "$release_dir/server/.env"

7.2 切换 current

构建和检查全部成功后:

ln -sfn "$release_dir" /var/www/airport-explorer/current
readlink -f /var/www/airport-explorer/current
pm2 restart /var/www/airport-explorer/current/ecosystem.config.cjs \
  --only airport-api --update-env

先确认 current 指向正确 release,再重启 PM2。

7.3 回滚 release

列出发布目录并确定上一个版本:

ls -la /var/www/airport-explorer/releases
readlink -f /var/www/airport-explorer/current

切回经过确认的绝对路径:

ln -sfn /var/www/airport-explorer/releases/<上一个release> \
  /var/www/airport-explorer/current
pm2 restart /var/www/airport-explorer/current/ecosystem.config.cjs \
  --only airport-api --update-env

然后执行健康检查与业务冒烟测试。

不要写一个未经检查的“删除所有旧版本”命令。保留最近若干稳定 release,清理时逐个确认绝对路径,并确保它不是 current 指向的目录。

8. 更新脚本应具备的行为

理解手工流程后再写脚本。脚本至少应该:

自动化不会修复错误流程,只会更快地重复它。先手工成功多次再自动化。

9. 本章验收

下一章:运维和故障排查