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 覆盖。
检查新版本是否增加:
- 环境变量;
- npm 依赖;
- Nginx 或 PM2 配置;
- 数据库索引或其他维护 SQL;
- 不兼容 API 契约。
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。学习阶段可以接受,并且更容易理解连接和生命周期。
若未来需要接近零停机:
- 应用必须无状态或把状态放到外部存储;
- 使用 PM2 cluster 多实例;
- 使用
pm2 reload; - 确认数据库连接总数能承受多个进程;
- 验证优雅关闭与长查询;
- Nginx 健康检查和发布策略也需要配合。
不要只把 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. 更新脚本应具备的行为
理解手工流程后再写脚本。脚本至少应该:
set -Eeuo pipefail,任一步失败立即退出;- 接受明确的 commit 或 tag,而不是隐式使用未知状态;
- 在新目录安装和构建;
- 检查
.env链接存在但不打印内容; - 通过检查后才切换
current; - 重启 PM2 后检查
/health; - 健康检查失败时切回旧 release;
- 记录发布时间、commit 和结果。
自动化不会修复错误流程,只会更快地重复它。先手工成功多次再自动化。
9. 本章验收
- 能判断不同改动需要重启什么;
- 更新前记录旧 commit;
- 使用
npm ci和质量检查; - 发布后验证内部与公网健康检查;
- 能回滚到旧 commit;
- 理解版本化目录如何避免覆盖可运行产物。
下一章:运维和故障排查