在 Docker 里用 pm2:让 Node 应用更稳健


在 Docker 里用 pm2:让 Node 应用更稳健

前面几篇我们把 Docker 的基础、Dockerfile 的玩法、甚至底层原理都捋了一遍,感觉对容器的掌控力越来越强了。但有一个问题不知道你想过没有:容器里的 Node 应用如果崩溃了怎么办?或者你想利用多核 CPU 跑多个进程提升性能?又或者你想实时查看日志、监控应用状态?这时候就该 pm2 登场了。

pm2 是一个 Node.js 的进程管理工具,它能帮你把应用跑起来,挂了自动重启,还能做负载均衡、日志管理、性能监控。在服务器上部署 Node 应用,pm2 几乎是标配。那问题来了:我们已经在 Docker 里跑 Node 了,还需要 pm2 吗?如果需要,怎么把它俩结合起来?今天就来聊聊这个话题。

为什么要在容器里用 pm2?

你可能会想:Docker 本身不是有重启策略吗?比如 --restart=always,容器挂了会自动重启,那还要 pm2 干嘛?问得好。

Docker 的重启策略是针对整个容器的,如果容器内的进程崩溃了,容器也就退出了,Docker 会帮你重启容器。但有时候,进程崩溃可能只是瞬间的事,比如 Node 抛了个未捕获的异常,导致进程退出。如果让 Docker 重启整个容器,那重建容器的开销比单纯重启进程要大,而且容器内的其他进程(如果有)也会被重启。

pm2 是在容器内部工作的,它只负责重启 Node 进程,容器本身还活着。这样重启速度更快,而且可以保留其他状态(比如已有的数据库连接池)。另外 pm2 还提供了很多容器没有的功能:比如日志轮转、集群模式(利用多核)、性能监控、API 管理等。所以,把 pm2 放进容器,相当于给 Node 应用加了一层更精细化的保障。

pm2 的基本功能速览

先简单过一下 pm2 常用的几个功能,方便你理解后面在 Docker 里的用法:

  • 进程管理pm2 start app.js 启动应用,pm2 stop/restart/delete 管理进程。如果进程意外退出,pm2 会自动重启它。
  • 日志管理pm2 logs 可以实时查看所有进程的日志,日志会自动写文件,还支持日志轮转(防止磁盘爆满)。
  • 负载均衡pm2 start app.js -i max 可以启动多个进程(利用多核 CPU),pm2 会自动做负载均衡。
  • 性能监控pm2 monit 可以看 CPU、内存使用情况,pm2 list 查看进程状态。
  • 配置文件:通过 ecosystem.config.js 可以定义多个应用、环境变量、启动参数等,然后 pm2 start ecosystem.config.js 批量启动。

在 Dockerfile 里集成 pm2

既然要在容器里用 pm2,首先得把 pm2 装进镜像。通常有两种方式:全局安装 pm2,然后通过 pm2 启动 Node 应用。但这里有个细节:官方建议使用 pm2-runtime,它是专门为容器环境优化的版本,默认将日志输出到 stdout(这样 Docker 就能捕获日志),并且能正确处理 SIGTERM 信号(让容器优雅关闭)。

来看一个 Dockerfile 的例子:

FROM node:18-alpine

# 安装 pm2
RUN npm install -g pm2

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

# 如果用了 ecosystem 配置文件,也可以复制过去
COPY ecosystem.config.js .

EXPOSE 3000

# 使用 pm2-runtime 启动应用
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

这里我们用 pm2-runtime 替代了直接 node app.jspm2-runtime 会把 pm2 的日志转发到容器的 stdout,这样你用 docker logs 就能看到应用的输出。而且当容器收到停止信号时,pm2-runtime 会让所有子进程优雅退出。

编写 ecosystem.config.js

如果你有多个应用或者需要配置环境变量、进程数量,用 ecosystem 文件更方便。例如:

module.exports = {
  apps: [{
    name: 'my-app',
    script: './server.js',
    instances: 'max',          // 根据 CPU 核心数启动多个进程
    exec_mode: 'cluster',       // 集群模式,实现负载均衡
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    error_file: '/var/log/pm2/err.log',  // 如果想把日志写文件,可以指定路径,但记得挂载卷
    out_file: '/var/log/pm2/out.log',
    combine_logs: true
  }]
};

然后在 Dockerfile 里把配置文件复制进去,CMD 里指定这个文件。

关于日志和数据持久化

如果你在 ecosystem 里配置了日志写文件(比如上面的 error_file),那这些日志是写在容器内的。一旦容器删除,日志就没了。所以如果你需要长期保留日志,可以把日志目录挂载成数据卷。同样,如果你的应用需要上传文件或者有数据要持久化,也要挂载对应的数据卷。

另外,pm2 在容器里运行时,你还可以通过 pm2 plus 或者 pm2.io 实现云端监控,不过那是另一个话题了。

用 pm2 的好处再总结

  • 稳定性:进程崩溃自动重启,不用等 Docker 重启整个容器。
  • 性能:利用多核 CPU,集群模式提升吞吐量。
  • 可观测性:通过 pm2 内置的监控命令,可以实时了解容器内应用的资源占用。
  • 日志管理:自动轮转、合并日志,避免日志文件过大。

需要注意的点

  • pm2 本身也是一个进程,它会占用少量资源,但相比带来的好处可以忽略。
  • 如果你在容器里只跑一个进程,也可以不用 pm2,直接用 Node 原生启动。但一旦有多个进程或需要自动重启,pm2 就很有用了。
  • 确保容器停止时能优雅退出:pm2-runtime 会处理,但你的应用代码最好也能监听 SIGTERM 信号,做清理工作。

小结

把 pm2 和 Docker 结合起来,相当于给 Node 应用上了双重保险:Docker 保证容器级别的重启和隔离,pm2 保证进程级别的稳定和管理。而且配置起来也不复杂,只要在 Dockerfile 里加一行安装,CMD 改成 pm2-runtime 就行了。生产环境的 Node 应用,强烈推荐这样用。

声明:麋鹿与鲸鱼|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 在 Docker 里用 pm2:让 Node 应用更稳健


Carpe Diem and Do what I like