如果你在服务器上部署 Node.js 应用的方式还是简单粗暴的 node app.js,那你可得小心了。这种方式在开发时很方便,但放到生产环境简直就是一场灾难。应用崩溃了怎么办?服务器重启了怎么办?答案是:你的服务就挂了,直到你手动重启它。
要解决这个问题,我们需要一个进程管理器。PM2 就是为 Node.js 量身打造的生产级进程管理器。它能让你的应用在后台稳定运行,崩溃后自动重启,服务器重启后也能自动拉起服务。这基本是所有 Node.js 生产环境的标配。
这篇文章就是一份 PM2 的实战指南,我会带你走一遍在 Ubuntu 服务器上安装、配置、管理 Node.js 应用的全过程。
快速上手:安装与运行
我们先从安装 PM2 和跑起一个简单的应用开始。
安装 PM2
PM2 是一个 npm 包,最好全局安装,这样在任何路径下都能使用。
一个常见的误区是直接用 sudo npm install。这可能会导致权限问题,更推荐的做法是使用 nvm (Node Version Manager)。nvm 会把 Node.js 和全局包安装在你的用户主目录下,完全不需要 sudo。
如果你已经装了 nvm,安装 PM2 就非常简单:
npm install pm2 -g
安装完成后,可以检查一下版本来确认是否成功:
pm2 --version
创建一个简单的 Express 应用
我们先搭建一个基础的 Express 服务来做演示。
初始化项目:
mkdir my-app
cd my-app
npm init -y
npm install express
创建应用文件:
新建一个 app.js 文件,写入以下代码:
// app.js
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello from PM2!');
});
// 处理 SIGINT 信号,让 PM2 能够优雅地重启应用
process.on('SIGINT', () => {
console.log('收到关闭信号,正在清理资源...');
// 这里可以关闭数据库连接、清理定时器等
process.exit(0);
});
app.listen(PORT, () => {
console.log(`服务运行在 http://localhost:${PORT}`);
});
这里加上一个 SIGINT 信号的监听器,这在后面“零停机重载”时会用到。
现在使用 PM2 来启动它:
pm2 start app.js
PM2 输出一个格式化的表格,显示应用正在后台运行。
[PM2] Spawning PM2 daemon with pm2_home=/home/user/.pm2
[PM2] PM2 successfully daemonized
[PM2] Starting /home/user/my-app/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name │ mode │ pid │ status │ restart │ uptime │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0 │ app │ fork. │ 12345│ online │ 0 │ 0s │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
用 curl 命令测试一下,确认它真的在运行:
curl http://localhost:3000
如果看到 Hello from PM2! 就说明成功了。
核心功能:让应用跑的更稳健
PM2 的强大之处还在于它为生产环境设计的一系列功能。
集群模式:榨干多核 CPU
Node.js 的一大特点是单线程。这意味着,就算你的服务器有 8 核、16 核 CPU,一个普通的 Node.js 进程也只能用到其中一个核心,其他的都在围观。这显然是巨大的浪费。
PM2 的集群模式 (Cluster Mode) 解决了这个问题。它会根据你服务器的 CPU 核心数启动相应数量的应用实例,并自动在它们之间做负载均衡。
启动集群模式非常简单,用 -i 参数就行了。你可以指定具体的实例数量,或者直接用 max 让 PM2 自动检测 CPU 核心数。
# 停止刚才的应用
pm2 delete app
# 用集群模式启动,充分利用所有 CPU 核心
pm2 start app.js -i max
现在运行 pm2 list,你会看到多个 app 实例在运行,每个都有独立的 pid。
零停机重载:优雅地更新代码
当你的代码更新后,怎么部属到服务器上?直接 pm2 restart 会导致服务短暂中断,对于生产环境来说,这可不太好。
更好的方式是 pm2 reload。这个命令能实现零停机重载。它的工作原理是:逐个重启集群中的工作进程,而不是一次性全干掉。它会先启动一个新代码的进程,等新进程准备好之后,再关闭一个旧进程。这样就保证了在整个更新过程中,始终有进程在处理请求。
pm2 reload app
这个功能依赖我们之前在代码里加的 SIGINT 信号处理。reload 会给旧进程发送这个信号,让它有机会完成当前请求、释放资源,然后体面地退出。
注意:零停机重载只在集群模式下有效。如果你的应用是单实例的 fork 模式,pm2 reload 的效果就和 pm2 restart 一样了,还是会中断服务。
开机自启:别让重启毁了你的服务
这是很多人都会忘掉的一步。PM2 运行得好好的,结果服务器一重启,所有应用都挂了。我们需要让 PM2 作为一个系统服务,随开机自动启动。
这需要两步:
-
生成启动脚本
运行
pm2 startup,它会检测你的操作系统(比如 Ubuntu 上的systemd),然后生成一条对应的命令。pm2 startup它会输出类似下面的一行命令,你需要复制并执行它:
[PM2] To setup the Startup Script, copy/paste the following command: sudo env PATH=$PATH:/home/user/.nvm/versions/node/v18.17.0/bin /home/user/my-app/node_modules/pm2/bin/pm2 startup systemd -u user --hp /home/user这条命令的作用是创建一个
systemd服务,让 PM2 守护进程在开机时启动。 -
保存当前进程列表
光让 PM2 启动还不够,它得知道要启动哪些应用。运行
pm2 save,它会把你当前正在运行的应用列表保存下来。pm2 save这样,下次服务器重启时,PM2 就会自动加载这个列表,把你的应用都拉起来。
专业的玩法:使用生态系统文件
当你的应用配置越来越复杂,比如需要设置环境变量、指定不同的启动模式时,总是在命令行里敲一长串参数会变得非常麻烦。
PM2 推荐使用生态系统文件 (ecosystem file) 来管理配置。一般是一个叫 ecosystem.config.js 的 js 文件。
你可以用 pm2 ecosystem 命令生成一个模板:
pm2 ecosystem
配置文件示例:
// ecosystem.config.js
module.exports = {
apps : [{
name : "my-app",
script : "./app.js",
instances: "max", // 根据 CPU 核心数自动启动实例
exec_mode: "cluster", // 集群模式
watch: false, // 生产环境不要开 watch,会影响性能
env_production: {
NODE_ENV: "production",
PORT: 8080
},
env_development: {
NODE_ENV: "development",
PORT: 3000
}
}]
}
定义文件后,启动也很简单:
# 生产环境
pm2 start ecosystem.config.js --env production
# 开发环境
pm2 start ecosystem.config.js --env development
配置文件可以直接提交到 Git,团队协作和部署都方便。
监控与日志
应用跑起来了,但我们还要看一下他的状态。
常用命令:
pm2 list # 看所有应用状态
pm2 monit # 实时监控 CPU 和内存
pm2 logs my-app # 查看日志,排查问题
日志管理
PM2 默认日志会一直写,不处理的话磁盘很快就满了。建议装个 pm2-logrotate 自动切割:
pm2 install pm2-logrotate
它会自动切割、压缩和删除旧的日志文件。
生产环境安全
生产环境不要裸跑,基本的安全措施要做好:
防火墙规则
只开放必要端口(80/443),Node.js 应用端口(3000/8080)不要直接暴露:
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
用普通用户跑应用
永远不要用 root 用户来运行你的应用。创建一个专用的、低权限的用户。如果你的应用被黑了,这能把损失降到最低。
sudo adduser nodeapp
sudo su - nodeapp
- 使用反向代理 (Nginx): 让 Nginx 这样的专业服务器来处理来自公网的请求,然后把请求转发给本地的 Node.js 应用。这样做的好处多多:可以轻松配置 HTTPS、做负载均衡、抵御一些常见的网络攻击。
- 管理好你的密钥: 数据库密码、API Key 这类敏感信息,千万不要硬编码在代码里。最好通过环境变量传入,或者使用专门的密钥管理服务。
PM2 vs. Docker:不是对手,是队友
很多人会问,既然有了 Docker,还需要 PM2 吗?
我的观点是:它们解决的问题不同,而且是完美的搭档。
- Docker 负责的是环境隔离和打包。它把你的应用、运行时、系统依赖全部打包成一个镜像,确保在任何地方运行都有一致的环境。
- PM2 负责的是应用进程管理。它在容器内部,管理 Node.js 进程,提供集群模式、零停机重载这些 Docker 默认不具备的功能。
最佳实践是:在 Docker 容器里使用 pm2-runtime 来启动你的应用。pm2-runtime 是 PM2 的一个特殊版本,专门为容器环境设计。
这样,你既能享受到 Docker 带来的环境一致性和部署便利,又能利用 PM2 强大的 Node.js 进程管理能力。
总结
PM2 是一个功能强大且简单易用的工具,它解决了直接用 node 命令在生产环境运行应用的几乎所有痛点。通过掌握它的核心功能,如集群模式、零停机重载和开机自启,再结合良好的安全实践,你就能搭建一个稳定、高效且易于维护的 Node.js 生产环境。
关于
关注我获取更多资讯