08 更新、回滚与故障排查

第一次上线成功后,最频繁的工作是发布新功能。更新流程必须能够回答:

构建失败怎么办?
启动失败怎么办?
数据库已经变更怎么办?
如何回到上一个可运行版本?

1. 最简单的手工更新流程

在小型学习项目中可以先使用:

备份当前可运行版本信息
  → 获取新代码
  → npm ci
  → 类型检查
  → 构建
  → migration(如有)
  → PM2 restart
  → 健康检查和日志检查

示意命令:

cd /var/www/nloop/current

git status
git pull --ff-only
npm ci
npm run typecheck
npm run build
npm prune --omit=dev

pm2 restart ecosystem.config.js \
  --only nloop-api \
  --update-env

pm2 logs nloop-api --lines 100 --nostream
curl -i http://127.0.0.1:8080/health

git pull --ff-only 会在无法快进时失败,避免服务器自动生成不受控的 merge commit。生产目录不应存在临时手改源码。

这种原地更新适合学习和低风险项目,但构建过程中目录处于变化状态。掌握后应过渡到独立 release 目录。

2. 为什么构建要在 restart 前完成

错误流程:

先停止旧服务
  → 再安装依赖
  → 再编译
  → 编译失败
  → 服务长时间不可用

更好的顺序:

旧服务保持运行
  → 新代码完成依赖安装和构建
  → 构建成功后才重启

即使是手工部署,也要尽量缩短真正的停机窗口。

3. restartreload

当前教程使用:

instances: 1,
exec_mode: "fork",

所以优先:

pm2 restart nloop-api

PM2 的平滑 reload 优势主要用于 cluster/多个实例。单进程 fork 模式不要因为看到“0 秒停机”宣传就假设已经获得真正的零停机发布。

4. 环境变量更新

修改 ecosystem 或 shell 环境变量后:

pm2 restart ecosystem.config.js \
  --only nloop-api \
  --update-env

修改 .env 后也要重启应用,让 dotenv 重新加载。

不要把同一变量同时定义在多个地方:

.env 中 PORT=8080
ecosystem 中 PORT=8081
systemd 中 PORT=8082

否则最终值由加载顺序决定,排查非常困难。

5. 数据库 migration 的发布顺序

代码可以回滚,数据库结构不一定能轻易回滚。推荐向后兼容的渐进变更:

第一次发布

新增 nullable 字段或新表
旧代码仍能运行

第二次发布

新代码开始写入和读取新字段

后续发布

数据回填完成
确认没有旧代码
再增加 NOT NULL 或删除旧字段

不要在同一次发布一开始就删除旧代码仍需要的列。

多人、多语言共同访问数据库时,migration 必须进入统一版本管理和发布流程,不能依赖 Sequelize Model 自动猜测线上结构。

6. 简单可靠的 release 目录

进阶一步可以采用:

/var/www/nloop/
├── releases/
│   ├── 20260821-100000/
│   └── 20260821-120000/
├── shared/
│   └── .env
└── current -> releases/20260821-120000

每次发布在新目录完成:

获取代码
  → npm ci
  → typecheck
  → build
  → 安装/裁剪生产依赖
  → 链接 shared/.env
  → 切换 current 软链接
  → PM2 restart

ecosystem 中保持:

cwd: "/var/www/nloop/current",

这样 PM2 不需要知道具体时间戳目录。

7. 回滚的边界

代码回滚可以把 current 指回上一版本,再重启:

current → 上一个已验证 release
pm2 restart nloop-api
健康检查

但必须先确认:

“能够切回旧 JavaScript”不等于整个系统一定能够回滚。

8. PM2 高频故障

pm2: command not found

nvm current
which node
which pm2

检查是否切换了 Node 版本,或非交互 shell 没有加载 NVM。

应用一直 restarting

pm2 show nloop-api
pm2 logs nloop-api --lines 200 --nostream

常见原因:

PM2 online,但 API 失败

online 只说明进程存在。继续测试:

curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/ready

/ready 可以检查数据库等关键依赖,但要设置短超时,不能让健康检查本身拖垮服务。

端口占用

ss -lntp | grep 8080

可能是之前手工运行的 node dist/server.js 没有停止,或存在另一个 PM2 用户的进程。

9. Nginx 高频故障

502 Bad Gateway

sudo tail -n 100 /var/log/nginx/nloop-error.log
pm2 list
curl -i http://127.0.0.1:8080/health

重点检查上游地址、端口和 Express 状态。

404,但 Express 中有路由

检查:

修改配置没有生效

sudo nginx -t
sudo systemctl reload nginx
sudo nginx -T

nginx -T 会输出完整生效配置,适合发现编辑了错误文件或配置没有被 include。

权限错误

Nginx worker 用户必须能读取静态文件和进入父目录,但不要通过全局 chmod 777 解决。检查每一级目录权限和实际 worker 用户。

10. 日志排查顺序

1. 浏览器/客户端错误和状态码
2. Nginx access log:请求是否到达
3. Nginx error log:代理是否失败
4. curl 127.0.0.1:8080:绕开 Nginx
5. PM2 list/show:进程是否稳定
6. PM2 应用日志:启动和业务错误
7. MySQL/Redis/下游服务状态

这种分层排查比同时修改 Nginx、PM2 和代码更有效。

11. 更新验收表

官方参考