让 PM2 在服务器重启后自己活过来(两半缺一不可)

服务器重启了,网站没回来。一个小时后有人跑了一次部署,网站才恢复。这看起来像是部署的问题,其实不是:那次部署只不过是又手动执行了一遍 pm2 start

要让应用自己开机启动,需要两件互相独立的事情,缺了任何一件,另一件都没用。

这两半是什么

pm2 save 把当前的进程列表写进 ~/.pm2/dump.pm2。PM2 只会恢复这个文件里有的东西,别的一概不管。一个只做 pm2 deletepm2 start、从不保存的部署脚本,留下的是一份过期的 dump;如果从来没保存过,那就是空的。

pm2 startup 安装一个 systemd 单元,开机时执行 pm2 resurrect,读的正是那份 dump。

只有其中一半,你得到的要么是一次什么都不启动的开机,要么是一份没人读的列表。

那个坑

用非特权用户执行 pm2 startup什么都不会被安装。它检测到 systemd,算出那条能安装单元的命令,把它打印出来,然后退出。输出很长、是绿色的,末尾还有一行提示你复制这条命令。看上去像是成功了。其实什么都没发生。

所以:用 root 执行,然后用 systemd 来确认,而不是看 PM2 打印了什么。

正确的顺序

保存之前应用必须是在运行的,否则你保存下来的是一份空列表。

# 以 <user> 身份 —— 确认应用在跑
pm2 status
# 以 root 身份 —— 为该用户安装单元
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>

前面的 env PATH=$PATH:/usr/bin 不是装饰。root 的 PATH 里通常没有 Node,而单元会把生成时的路径原样记下来。少了它,单元是存在的,但开机时找不到 node

# 再切回 <user> —— 写入 dump
pm2 save

两半分开验证

它们是分别失效的,所以要分别检查。

systemctl is-enabled pm2-<user>

必须输出 enabled。其他任何结果,无论是 disabled 还是 Failed to get unit file state,都说明单元根本没装上,不管 PM2 当时说了什么。

grep -o '"name":"[^"]*"' /home/<user>/.pm2/dump.pm2

这里必须出现 <app>。如果文件不存在,或者 grep 什么都没打印,说明 pm2 save 从来没有在应用运行的状态下跑过。

不重启也能测

要知道这套是否管用,不必去重启一台生产机器。

# 以 <user> 身份
pm2 kill
# 以 root 身份
systemctl start pm2-<user>
# 以 <user> 身份
pm2 status
curl -sf http://127.0.0.1:3000/health

如果 <app> 重新在线,两半都是好的。开机时发生的正是这一串。

绝对不要在 pm2 kill 之后执行 pm2 save 守护进程被杀掉时进程列表是空的,一保存就把这份空白写进了 dump,恰好毁掉你正在测试的东西。如果顺手做了,把应用重新启动起来,再跑一次 pm2 save

pm2 save 写进部署脚本

只要某次部署动了进程,dump 就会开始对不上。如果你的脚本是先删再起,那它必须在最后保存:

pm2 delete <app> || true
pm2 start ecosystem.config.cjs --only <app>
pm2 save

|| true 在第一次运行时才见效:那时没有东西可删,pm2 delete 会以非零码退出,在 set -e 下会直接把整个脚本终止掉。

少了最后那一行,上面的一切照样能用,一直用到第一次改了应用名、改了实例数或换了入口文件的部署为止。从那以后,dump 描述的是一个已经不存在的进程,而下一次重启会把它复活。

关于启动顺序

生成出来的单元带的是 After=network.target,它并没有排在你的数据库之后。冷启动时,应用可能在 MariaDB 或 PostgreSQL 开始接受连接之前就起来了,第一次查询就失败,然后退出。

多数情况下这会自己解决:PM2 重启进程,第二次或第三次尝试时数据库已经就绪,网站就上来了。识别的线索是一个本身健康的进程带着大于零的重启计数,在 pm2 status 那一列。

如果它确实恢复不了,再显式地排一下顺序:

sudo systemctl edit pm2-<user>
[Unit]
After=network.target mariadb.service

只有在你亲眼见过它失败时才加。把单元排在一个很慢的服务后面,或者排在一个跑在别的机器上的服务后面,你换来的是一次卡住不动的开机,而不是原来那次会重试的开机。

检查你的网站 · 更多文章 · 关于 AgentReady