在 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.js。pm2-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 应用,强烈推荐这样用。

Comments | NOTHING