05. 使用 PM2 管理 Node.js 后端

1. 本章目标

本章把 Express 构建为生产 JavaScript,并用 PM2 保持进程运行、查看日志和配置开机启动。

2. 为什么不能直接使用开发命令

开发命令 tsx watch src/server.ts 会监听文件变化,适合本地热更新。生产环境应先用 TypeScript 编译,再运行 dist/server.js:

TypeScript 源码
  → npm run build -w server
  → server/dist/server.js
  → Node.js 22
  → PM2 管理进程

PM2 不替你执行类型检查,也不会让错误代码变正确。发布前必须先完成质量检查。

3. 准备后端构建产物

cd /var/www/airport-operations-explorer
npm ci
npm run typecheck
npm test
npm run build -w server
test -f server/dist/server.js && echo "server build exists"

npm ci 严格使用 package-lock.json,并要求 lockfile 与 package.json 一致,适合可重复部署。

先前台测试:

cd /var/www/airport-operations-explorer/server
NODE_ENV=production node dist/server.js

另一个终端执行:

curl -i http://127.0.0.1:3000/health

成功后按 Ctrl+C 停止前台进程。

4. 创建 ecosystem 配置

在仓库根目录创建 ecosystem.config.cjs。项目使用 ESM,但 PM2 配置用 .cjs 可以避免配置加载器差异:

module.exports = {
  apps: [
    {
      name: "airport-api",
      cwd: "/var/www/airport-operations-explorer/server",
      script: "dist/server.js",
      interpreter: "node",
      instances: 1,
      exec_mode: "fork",
      env: {
        NODE_ENV: "production",
        HOST: "127.0.0.1",
        PORT: "3000"
      },
      max_memory_restart: "750M",
      kill_timeout: 10000,
      listen_timeout: 10000,
      time: true,
      autorestart: true
    }
  ]
};

字段解释:

字段 含义
name PM2 命令和日志中使用的稳定名称
cwd 应用工作目录,决定 dotenv 从哪里读取 .env
script 编译后的 Node.js 入口
interpreter 使用 Node.js 执行脚本
instances 进程数量,初学阶段先使用 1
exec_mode fork 是单进程模式,行为最容易理解
env 非敏感的生产运行参数
max_memory_restart 超过阈值时重启,但不能代替内存泄漏调查
kill_timeout 允许 Express 完成优雅关闭的最长等待时间
time PM2 日志带时间戳

数据库密码保存在权限为 600 的 server/.env,不要写进 ecosystem 文件并提交 Git。

项目的 server.ts 会监听 SIGINT 和 SIGTERM,停止接收请求并关闭数据库连接。kill_timeout 给这个过程留出时间。

5. 启动和检查 PM2

cd /var/www/airport-operations-explorer
pm2 start ecosystem.config.cjs
pm2 status
pm2 describe airport-api
pm2 logs airport-api --lines 100

测试健康检查:

curl -i http://127.0.0.1:3000/health

常用命令:

pm2 restart airport-api
pm2 stop airport-api
pm2 start airport-api
pm2 delete airport-api
pm2 logs airport-api
pm2 monit

6. 配置服务器重启后自动启动

先执行:

pm2 startup

PM2 会输出一条带 sudo、当前用户名、home 路径和 PATH 的命令。复制执行它,不要自行简化,因为 nvm 的 Node 路径需要写入 systemd 环境。

保存当前进程列表:

pm2 save

验证 systemd 服务名:

systemctl list-units --type=service | grep pm2

最终可以安排一次维护窗口重启服务器:

sudo reboot

重新登录后:

pm2 status
curl -i http://127.0.0.1:3000/health

只执行 pm2 startup 而不执行 pm2 save,systemd 启动时没有要恢复的应用清单。

7. 环境变量变化后的处理

PM2 重启时默认可能沿用已有进程环境。修改 ecosystem 的 env 后:

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

本项目的敏感配置由 dotenv 从 server/.env 读取。修改 .env 后也需要重启进程:

pm2 restart airport-api

然后重新执行健康检查。

8. 日志和轮转

查看路径:

pm2 describe airport-api
pm2 logs airport-api --lines 200

安装日志轮转模块:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 20M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true

检查磁盘:

du -sh ~/.pm2/logs
df -h

日志必须保留 request ID、路径、状态码和耗时,但不能记录数据库密码、Cookie 或 Authorization。

9. 常见问题

PM2 显示 errored

pm2 logs airport-api --err --lines 200

常见原因是 cwd 错误、dist/server.js 不存在、端口已占用或环境变量校验失败。

PM2 使用了错误 Node 版本

which node
node --version
pm2 report

重新执行正确用户环境下的 pm2 startup,并检查 systemd 服务中的 PATH。

地址已被占用

sudo ss -lntp | grep ':3000'

确定占用进程是谁。不要直接杀掉不认识的进程。

数据库 down

PM2 在线只表示 Node.js 进程存在。/health 返回 503 时应检查 MySQL 和应用账号,不要反复重启 PM2。

10. 本章验收

下一章:使用 Nginx 托管前端和代理 API

11. 参考资料