Skip to content

开机自启与远程部署 / CI

定位: 把 PM2 从"我这台机器上跑着"升级为"服务器重启了它自己回来,发版不用人肉 ssh"。
分两部分:Part I systemd 开机自启;Part II pm2 deploy 远程发布与 CI 集成。

Part I · 开机自启(systemd)

问题与原理

默认情况下 PM2 daemon 是你手动跑起来的:机器一重启,daemon 没了,进程没人看护。所以要做两件事:

  1. 让系统开机时自动把 daemon 拉起来
  2. daemon 起来后按你保存过的快照(dump.pm2)把应用进程恢复。

Linux 上的标准做法 = systemd 单元 + pm2 startup

开机 → systemd 启动 pm2-<user>.service → 拉起 PM2 daemon → pm2 resurrect(按 dump.pm2 恢复进程)

标准三步

以运行应用的普通用户(如 deploy)执行:

bash
# ① 先把自己想要的进程都 start 好
pm2 start ecosystem.config.js --env production
# ② 冻结进程清单(dump.pm2)
pm2 save

# ③ 生成并注册 systemd 单元
pm2 startup
# 它会打印一条形如下面的命令(含当前用户的 PATH 与 home):
#   sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
# 把这行"打印出来的"命令复制执行(需要 root)

验证:

bash
systemctl status pm2-deploy       # 服务名 = pm2-<user>
systemctl cat pm2-deploy          # 查看生成的 unit 内容
journalctl -u pm2-deploy          # 看 daemon 启动日志
pm2 ls                           # 进程应当 online

最后重启机器实测一次sudo reboot 后回来 pm2 ls 应该看到进程自动回来了。

关键点与坑

说明
必须 pm2 savestartup 只负责"拉起 daemon + 恢复快照";快照不存在 = 恢复空气
每次增删/改参后重新 save快照是"当时的进程清单",不是实时同步的
用哪个用户跑 startup就用哪个用户跑应用;root 的应用 = root 的 daemon,普通用户看不见(反之亦然)
换用户pm2 startup systemd -u www --hp /home/www(配合对应用户的 save)
服务名pm2-<user>;可用 --service-name 自定义
Node 路径变化后重新生成:先 pm2 unstartup 执行其打印命令删除旧单元,再按新 PATH 重新 pm2 startup
开机要等网络官方建议在 unit 里加 Wants=network-online.target / After=network-online.target 依赖
不需要自启了pm2 unstartup → 复制执行打印的 sudo 命令,即移除

升级 Node 大版本后务必重做 startup:unit 里固化的是旧 PATH(如 nvm 版本路径),新版本 node 装好但自启仍指向旧路径,是最常见的"重启后服务起不来"原因之一。

容器里怎么办

容器内一般不需要 systemd:让 PM2 当前台进程(PID 1)跑,交给容器的 restart policy / 编排系统兜底:

bash
# 方式一:pm2-runtime(随 PM2 提供,专为容器设计,作为 node 的 drop-in)
pm2-runtime start app.js -i max

# 方式二:PM2 前台运行 + --no-daemon
pm2 start app.js --no-daemon

需要"一套独立、不互相干扰的 PM2"时可用 PM2_HOME=/path pm2 ... 隔离(见 核心概念)。

Part II · 远程部署:pm2 deploy

它解决什么

传统发版:ssh 上服务器 → git pull → 重启。pm2 deploy 把"拉代码 → 装依赖 → 重启服务"固化成一条命令,支持多机并行、指定版本、回滚。

前置条件

  • 远端服务器:已装 Node 与 PM2(官方明确要求远端先装好);
  • 远端有权限 clone/pull 你的仓库(通常配置 ssh key);
  • 本地到远端 ssh 免密;
  • 仓库里带着 ecosystem.config.js(post-deploy 在远端执行时需要它,且通常把它纳入版本库)。

配置:deploy 块

deploy 配置挂在 ecosystem 文件顶层(apps 之外):

js
// ecosystem.config.js
module.exports = {
  apps: [
    { name: 'api', script: './dist/server.js', exec_mode: 'cluster', instances: 'max' },
  ],
  deploy: {
    production: {
      user: 'deploy',                 // 远端用户名
      host: ['10.0.0.11', '10.0.0.12'], // 单机写字符串,多机写数组(并行部署)
      ref: 'origin/main',             // 部署哪个分支/引用
      repo: 'git@github.com:org/api.git', // 仓库地址
      path: '/var/www/api',           // 远端部署根目录
      'pre-deploy-local': '',         // 本地先执行(如构建/打包)
      'post-deploy': 'npm ci && pm2 startOrReload ecosystem.config.js --env production',
      ssh_options: 'StrictHostKeyChecking=no',
    },
    // staging: { ... }  // 可定义多套环境
  },
};

命令序列

命令作用
pm2 deploy production setup首次部署:在远端创建目录结构并 clone 仓库(只需跑一次)
pm2 deploy production部署到配置里 ref 指向的版本(拉取→post-deploy)
pm2 deploy production <commit/tag>部署到指定版本
pm2 deploy production revert 1回滚到上一个部署(数字=回退几步)
pm2 deploy production curr / prev查看当前 / 上一次部署的提交
pm2 deploy production list列出历史部署
pm2 deploy production exec "pm2 reload all"在所有目标服务器上执行一次性命令
pm2 deploy production --force忽略本地未提交改动,强制部署
pm2 deploy production update更新 pm2-deploy 运行时相关(进阶用)
bash
pm2 deploy production setup          # 第一次
pm2 deploy production                # 以后每次发版就这一条
pm2 deploy production revert 1       # 出问题回滚

配置文件若命名为 ecosystem.config.jspm2.config.js,命令里可省略文件名;否则写作 pm2 deploy ecosystem.config.js production

post-deploy 最佳实践

post-deploy 在远端刚拉下来的代码里执行,典型的组合:

bash
npm ci                     # 干净安装依赖(比 npm install 快且可复现)
npm run build              # 需要构建就构建(产物进仓库则省略)
pm2 startOrReload ecosystem.config.js --env production   # 幂等:在跑就 reload,没跑就 start
  • startOrReload/startOrRestart 而不是裸 pm2 start:多跑几次不会把进程加重复。
  • cluster 应用用 reload = 零停机发布(集群模式与零停机发布)。
  • 若某台机器首次部署,post-deploy 的 reload 会退化为 start(幂等设计)。

常见进阶选项

说明
keyssh 私钥路径,默认 $HOME/.ssh
ssh_options额外 ssh 参数(字符串或数组)
pre-setup / post-setupsetup 阶段前后在远端执行的脚本(一次性准备)
pre-deploy / post-deploy每次部署前后执行的脚本
pre-deploy-local每次部署前在本地执行的脚本
ssh 转发需要在服务器上用你的 key 再拉私有依赖时,官方建议在 ~/.ssh/configForwardAgent yesssh-add

接进 CI(GitHub Actions 示例)

思路:CI 里配置 ssh 私钥 → 直接调用 pm2 deploy(前提:CI 环境已装 pm2、能连目标机)。

yaml
# .github/workflows/deploy.yml
name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npm install -g pm2
      - name: 配置 ssh
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H ${{ vars.DEPLOY_HOST }} >> ~/.ssh/known_hosts
      - run: pm2 deploy production --force
        env:
          # 若 deploy 块里 user/host 想用变量,可用 sed 或让 host 用占位后替换
          NODE_ENV: production

若不引入 pm2 deploy,最简替代是"scp/rsync 产物 → ssh host 'cd /path && pm2 reload ecosystem.config.js --env production'"——效果类似,少一层抽象。 安全:使用专用 deploy 账号与独立 key;私钥只放 CI secrets,绝不提交仓库;远端禁止 root 部署。

三种发版姿势对比

方式特点适用
pm2 deploy一条命令、版本/回滚/多机中大型、git 流程规范的项目
CI + ssh(手动命令)灵活、和现有 CI 深度集成已有完善 CI 的团队
容器镜像 + 编排不依赖 pm2 远程管理已容器化的架构

参考链接