用 PM2 在 Ubuntu 上搭建生产级 Node.js 环境

还在用 `node app.js` 部署你的 Node.js 应用吗?这篇文章将带你掌握生产环境的必备工具 PM2,从安装、集群模式、零停机更新到安全配置和故障排查,一步步教你搭建一个稳如泰山的 Node.js 运行环境。

阅读时长: 6 分钟
共 2992字
作者: eimoon.com

如果你在服务器上部署 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 作为一个系统服务,随开机自动启动。

这需要两步:

  1. 生成启动脚本

    运行 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 守护进程在开机时启动。

  2. 保存当前进程列表

    光让 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 生产环境。

关于

关注我获取更多资讯

公众号
📢 公众号
个人号
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计