Node 后端服务整理:NestJS、Prisma、PM2 与可回滚部署
本文目录
PM2 进程列表只告诉你进程名称和状态,不一定能说明当前正在运行哪个仓库、构建版本或环境文件。整理服务先做只读盘点,再设计可重复发布流程;不要在未知进程上直接 restart、delete、pull 或覆盖目录。
只读盘点:先建立服务清单
把运行状态、配置来源和代码路径分开核验。PM2 名称不是源码目录;同一台机器还可能有旧发布目录、符号链接、systemd 单元和手工启动的 Node 进程。先对照服务清单,再考虑变更。
pm2 status --no-color
pm2 describe <service-name>
ss -ltnp
readlink -f /proc/<pid>/cwd
readlink -f /proc/<pid>/exe
pm2 jlist 的 JSON 可能包含环境变量与敏感配置。需要批量盘点时,在管道内只输出 allowlist 字段,不要把原始 JSON 保存到工单或终端记录:
pm2 jlist | node -e 'let s=""; process.stdin.on("data", d => s += d); process.stdin.on("end", () => { const rows = JSON.parse(s); console.log(JSON.stringify(rows.map(p => ({ name: p.name, pid: p.pid, status: p.pm2_env?.status, cwd: p.pm2_env?.pm_cwd, script: p.pm2_env?.pm_exec_path })), null, 2)); });'
把输出整理成一行一个服务的清单:PM2 名称、运行用户、cwd、实际入口、Node 主版本、监听地址/端口、Nginx 路由、日志位置、仓库与 commit、环境文件路径、发布方法、回滚版本和负责人。环境文件只记录路径、权限与必需变量名,不复制值。
从进程追到代码
为每个服务记录 PM2 名称/id、运行用户、Node 版本、解释器、script、cwd、环境变量来源、日志路径、监听端口、反向代理位置、仓库/commit 和负责人。检查时使用 pm2 describe <name>、pm2 jlist(先脱敏)、ps -fp <pid>、readlink -f /proc/<pid>/cwd、ss -ltnp 与 ecosystem 文件。环境变量输出可能含凭证;查看前先做 allowlist,不把完整 env、进程参数或 .env 复制进工单。
特别留意符号链接和多份同名目录:进程 cwd 指向的实际路径可能与预期仓库不同。通过进程 PID 反查真实工作目录、主脚本、Node 二进制和文件修改时间,再对照发布记录与 Git commit。确认 Nginx/反向代理路由后,才能判断一个端口属于哪个服务。
将 PM2 配置当作可审阅的清单
Ecosystem 文件集中声明 name、script、cwd、interpreter、instances、restart 策略、内存上限、日志路径和环境变量名。敏感值不要写入仓库;读取权限受控的部署环境文件或秘密管理器。更新环境后按 PM2 文档的环境刷新行为执行,并在健康检查中确认新值确实被服务加载,绝不通过打印环境值来确认。
保留一个服务一个明确入口,命名使用项目/服务职责,而不是脚本随机名。不同应用使用独立运行用户、目录权限、端口和日志文件。不要为“方便排障”把数据库或调试端口绑定到公网。
部署采用不可变候选目录
- 本地固定依赖锁文件,跑 lint/typecheck/test/build;记录 commit 和构建产物哈希。
- 服务器上传至时间戳候选目录,校验文件数/哈希,使用最小权限安装运行依赖。
- 校验环境变量存在性但不回显值,生成 Prisma Client;数据库迁移作为独立、已备份步骤。
- 在 loopback 的备用端口启动候选服务,执行 readiness、核心只读 API、数据库连通和日志检查。
- 原子切换流量或目录链接,只重载本批服务;确认健康后保留旧版本一段回滚窗口。
- 若探针失败,立即切回上一运行目录并确认 PM2、端口、首页/API 健康;不要在失败后继续删旧构建物。
Prisma schema 和数据库迁移不能跟代码目录切换混为一个可逆动作。向前兼容地先加列/索引,部署读写兼容版本,再做回填和清理;数据库降级前单独评估数据可逆性,普通代码 rollback 不会撤销已提交迁移。
PM2 和生命周期
pm2 status/pm2 describe用于观察;restart 是进程级重启,reload 仅在应用与运行模式满足条件时逐实例切换。先核对当前进程是否由 ecosystem 文件托管、运行多少实例、是否有内存 session 或启动脚本。pm2 save 会持久化当前进程清单,执行前必须确认清单没有临时/错误项目。
应用要处理 SIGTERM:停止接新任务,等待在途请求,关闭 Nest app、数据库和 Redis 连接,并在期限内退出。PM2 的 kill_timeout 与应用 shutdown 时长要匹配。不能只通过 pm2 restart all 来“刷新配置”,这可能重启其它项目。
NestJS 信号与优雅关闭
Nest 应用收到 SIGTERM 后应停止接收新请求,让在途请求和事务在限定时间内结束,再关闭数据库/Redis 等连接。Nest 的 shutdown hooks 需要显式启用;worker 还要停止领取新任务并安全释放租约。PM2 的 kill_timeout 必须大于应用的正常关闭时间,同时设置有限上限,避免卡死进程永久占用端口。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(port, host);
}
根据拓扑选择监听地址:同机 Nginx 反代通常只需 loopback;容器或独立代理场景要按容器网络配置。不要为了让健康检查可达而把管理端口或调试端口暴露到公网。服务启动失败要让进程以非零状态退出并留下脱敏错误,不能吞掉异常后继续假装健康。
Readiness、迁移与上线门禁
Liveness 只回答进程是否还活着;Readiness 回答实例能否接收流量。Readiness 可以检查关键依赖,但每次探测都应有短超时、并发上限和稳定响应,不能让探针在数据库故障时形成额外请求风暴。启动探针应给进程有限的初始化宽限期;不要在健康接口返回连接串、版本密钥或堆栈。
Prisma migration 由发布流水线作为独立的一次性步骤执行,而不是让每个 API 实例启动时同时迁移。生产先备份并检查迁移 SQL,再执行 npx prisma migrate deploy;新增字段/索引采用 expand-contract,先让新旧代码兼容,再回填和清理。代码可以回滚,已经提交的数据迁移不一定可逆,二者要分别准备恢复方案。
pm2 describe <service-name>
pm2 logs <service-name> --lines 100 --nostream
curl --fail --silent --show-error http://127.0.0.1:<port>/health/ready
发布门禁至少包括:锁文件安装成功、类型检查/测试/构建通过、候选实例 Ready、只读关键 API 可用、数据库查询成功、错误率与重启次数稳定。任何一项失败就停止切流;记录本次 commit、构建哈希、迁移版本、健康探针结果和回滚指针。
常见故障按层定位
- PM2
errored或反复重启:看安全裁剪后的退出码、最近一段日志、Node 版本、cwd、入口文件和缺失扩展;不要先执行pm2 restart all。 - 本机健康检查成功但公网 502:检查 Nginx upstream 地址/端口、监听接口、超时、候选切换和主机防火墙;不要直接开放数据库端口。
- 发布后数据库报错:对照生成 Client 的 schema、实际迁移状态和应用版本兼容范围;不要盲目重置生产数据库或重跑 seed。
- 进程在线但功能超时:观察依赖耗时、事件循环延迟、连接池等待、内存和磁盘;重启只能暂时恢复症状,必须记录触发原因。
变更结束后再次执行相同健康探针,检查 PM2 重启计数和日志;保留上个可启动版本直到回滚窗口结束。临时账号、候选端口、测试数据和访问权限需逐一清理,并留下操作记录。
Nest 应用健康检查参考 NestJS Terminus;数据库发布步骤参考 Prisma production migrations。
手动修改时的安全步骤
- 先找到 Git 仓库、分支/commit 和部署脚本;备份当前 dist、ecosystem、环境文件权限元数据与数据库(敏感文件保持 mode 600,不把内容放进普通压缩包)。
- 修改源文件后本地构建,在候选目录部署;禁止直接在服务器 dist 上用编辑器改产物,否则无法复现和审查。
- 用健康探针确认候选,记录变更服务和回滚目录;其余 PM2 服务不动。
- 发布后验证公网 API/页面、PM2 稳定时间、重启次数和错误日志。移除临时候选前确认回滚版本保留充分。
官方参考:PM2 process management、PM2 ecosystem file、PM2 startup hook、Node.js signals。具体路径和服务名需按自己的服务器清单填写;不要把博客服务器凭证写进这篇通用指南。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。