- 运维
- CLI
- 可观测性
【免费下载链接】pm2
Node.js/Typescript/Bun Production Process Manager with a built-in Load Balancer.
本篇技术指南以仓库 examples/docker-pm2 示例为骨架,完整讲解如何在 Docker 容器中使用 pm2 官方镜像(keymetrics/pm2:latest-alpine)与pm2-runtime命令管理 Node.js 应用进程,包括镜像构建、应用配置文件编写、日志流转发与 Keymetrics 监控接入。读完本文,你将掌握一套可直接复制的"容器内进程托管"方案,并理解pm2-runtime作为"容器专用 PM2 运行时"的底层实现原理。
示例目录结构总览
examples/docker-pm2目录展示了一个最小可运行的容器化 PM2 示例,其完整结构如下:
examples/docker-pm2/ ├── README.md # 使用说明(本文即基于它展开) ├── Dockerfile # 镜像构建定义 └── app/ ├── app.js # Express 示例应用 ├── package.json # 应用依赖清单 └── process.config.js # PM2 进程配置文件(ecosystem 文件)示例的技术要点一句话概括:把整个 PM2(含守护进程与监控 Agent)封进镜像,容器启动时直接执行pm2-runtime,让容器的主进程就是 PM2 本身,从而天然获得进程守护、自动重启、日志聚合等能力,同时避免"容器即进程"模型下 PID 1 信号转发问题的复杂性。
镜像构建:Dockerfile 逐行拆解
示例仓库根目录下的 Dockerfile 内容如下:
FROM keymetrics/pm2:latest-alpine # Bundle APP files COPY ./app /app WORKDIR /app # Install app dependencies ENV NPM_CONFIG_LOGLEVEL warn RUN npm install --production ENV KEYMETRICS_SECRET xxxx ENV KEYMETRICS_PUBLIC yyyy CMD [ "pm2-runtime", "process.config.js" ]各指令的职责与注意事项:
| 指令 | 作用 | 实操建议 |
|---|---|---|
FROM keymetrics/pm2:latest-alpine | 使用 PM2 官方 Alpine 基础镜像,镜像内已内置pm2、pm2-runtime二进制及 Node.js 运行时 | 生产环境建议固定到具体版本 tag(如latest-alpine的某个发行版),避免latest漂移 |
COPY ./app /app | 将本地app/目录(含应用代码、package.json、process.config.js)复制进镜像/app | 配合.dockerignore排除node_modules等无关文件,缩小镜像上下文 |
WORKDIR /app | 设置工作目录,后续npm install与pm2-runtime均在此目录执行 | 保证process.config.js中相对路径./app.js能正确解析 |
ENV NPM_CONFIG_LOGLEVEL warn | 降低 npm 安装日志噪音 | 可移除;如需离线构建可改用npm ci --production |
RUN npm install --production | 仅安装生产依赖(本示例为express),不装devDependencies,显著缩小镜像体积 | 若应用使用 ES Modules 或需编译原生模块,需在此安装对应构建工具链 |
ENV KEYMETRICS_SECRET xxxx/ENV KEYMETRICS_PUBLIC yyyy | 注入 Keymetrics(PM2 Plus)监控密钥 | 生产环境应通过--build-arg或容器编排系统的 secret 机制注入,不要写死明文 |
CMD [ "pm2-runtime", "process.config.js" ] | 容器启动后由pm2-runtime加载并托管process.config.js中声明的应用 | 也可直接写CMD [ "pm2-runtime", "app.js" ]托管单个脚本 |
注意CMD使用exec 形式(JSON 数组)而非 shell 形式,这样容器 PID 1 进程就是pm2-runtime,可正确接收 Docker/K8s 下发的SIGTERM等信号并完成优雅退出。
应用代码与 PM2 进程配置
示例应用是一个标准的 Express HTTP 服务(examples/docker-pm2/app/app.js):
const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => console.log('Example app listening on port 3000!'))依赖清单声明在 examples/docker-pm2/app/package.json:
{ "name": "app", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "license": "ISC", "dependencies": { "express": "^4.16.2" } }真正决定 PM2 如何托管应用的是进程配置文件 examples/docker-pm2/app/process.config.js:
module.exports = { apps : [{ name : "express-app", script : "./app.js" }] }这是一个标准的 PM2 ecosystem 配置文件(CommonJS 导出),apps数组内每个对象代表一个被托管的进程。name用于在pm2 list/pm2 logs中标识应用,script为相对WORKDIR的入口脚本。在此基础上可继续扩展大量生产级配置项,例如:
module.exports = { apps : [{ name : "express-app", script : "./app.js", instances : 2, // 多实例,配合内置负载均衡(cluster 模式) exec_mode : "cluster", // fork | cluster max_memory_restart : "100M", // 内存超限自动重启 watch : false, env : { NODE_ENV: "production" }, error_file : "/dev/null", // 错误日志重定向 out_file : "/dev/null", // 输出日志重定向 time : true // 日志行附加时间戳 }] }这些字段会由 PM2 的启动逻辑解析并作用于进程生命周期管理,具体行为可对照 lib/binaries/Runtime4Docker.js 中透传给pm2.start(cmd, commander, ...)的选项处理。
构建与运行:三条核心命令
原文档给出了完整的三步操作流程,直接可执行:
# 1. 构建镜像 $ docker build -t docker-pm2-test . # 2. 查看本机镜像列表,确认构建成功 $ docker images # 3. 运行容器 $ docker run docker-pm2-test运行后观察容器日志,应依次出现 PM2 启动信息与应用自身的输出:
Example app listening on port 3000!容器内pm2-runtime会把 PM2 的日志流(stdout/stderr)直接打到容器标准输出,因此使用docker logs docker-pm2-test即可实时查看被托管应用的日志,这正是pm2-runtime相对常规pm2命令在容器场景下的关键差异(详见下文原理小节)。
如需验证应用可用性,可追加端口映射后访问:
$ docker run -p 3000:3000 docker-pm2-test $ curl http://localhost:3000/ # Hello World!深入原理:pm2-runtime 为什么适合容器
pm2-runtime是 PM2 为容器场景(Docker、Kubernetes 等)提供的专用二进制,其完整 CLI 实现位于 lib/binaries/Runtime4Docker.js,代码注释将其明确定义为:
Specialized PM2 CLI for Containers(容器专用 PM2 命令行)
与常规 pm2 命令的本质区别
常规pm2采用守护进程(daemon)架构:CLI 进程与长期驻留的 God 守护进程分离,命令执行后 CLI 即退出,守护进程在后台持续托管应用。这在宿主机场景没问题,但在容器中会产生"容器主进程退出、后台守护进程孤儿化"等管理难题。
pm2-runtime则运行在no-daemon 模拟模式下:Runtime4Docker.js内部通过new PM2.custom({ daemon_mode: false, ... })创建内嵌 PM2 实例,容器主进程即 PM2 本身,被托管应用的生命周期与容器完全绑定——容器停止,进程随之回收;进程崩溃,PM2 按配置自动拉起。
关键实现细节(源码证据)
从 lib/binaries/Runtime4Docker.js 可以观察到以下几点:
- 信号处理:实例启动后立即注册
SIGINT/SIGTERM处理器,收到信号即调用Runtime.exit(),先执行pm2.kill()清理全部托管进程再process.exit,实现优雅退出(Runtime.exit定义于Runtime.js对应逻辑的对称实现中,见 lib/binaries/Runtime.js 的exitPM2); - 日志流直通容器 stdout:
startLogStreaming()依据 CLI 选项在Log.jsonStream/Log.formatStream/Log.stream之间选择,默认把 PM2 全部应用日志透传到标准输出,天然适配docker logs; - 内嵌 PM2 实例:
pm2_home、secret_key、public_key、machine_name均通过环境变量或 CLI 选项注入,其中密钥优先级为constants.js中的环境变量优先、命令行参数兜底。
pm2-runtime 常用选项
Runtime4Docker.js中通过commander注册的选项即为容器场景下的完整能力面,常用项整理如下:
| 选项 | 含义 |
|---|---|
-i, --instances <number> | 启动 N 个实例并自动负载均衡(cluster 模式) |
--no-autorestart | 启动应用但禁止自动重启 |
--stop-exit-codes <codes...> | 指定一组退出码,命中时跳过自动重启 |
--max-memory-restart <memory> | 超过内存阈值(如100M)自动重启 |
-c, --cron <pattern> | 按 cron 表达式定时重启应用 |
--interpreter <interpreter> | 指定解释器(bash、python 等) |
--delay <seconds> | 延迟 N 秒后再加载配置文件启动 |
--web [port] | 在指定端口(默认 9615)启动 Web API |
--env <name> | 注入配置文件中的env_<name>环境变量块 |
--watch | 监听文件变化并自动重启 |
--json/--format | 以 JSON 或key=val格式输出日志 |
--no-auto-exit | 全部进程出错/停止时也不自动退出 |
其中--no-autorestart、--stop-exit-codes、--delay、--only <app-name>等选项在Runtime4Docker.js的commander.option(...)注册列表中均有对应声明,说明容器运行时对"重启策略"的控制粒度比常规命令行更细,适配 K8s 下由编排层决定重启而非 PM2 自行无限重试的场景。
无应用存活时的自动退出
容器场景一个值得注意的细节:Runtime4Docker.js内置了autoExitWorker(配合--auto-exit),默认失败容忍次数DEFAULT_FAIL_COUNT = 3,每 2 秒轮询一次pm2.list,若检测到0 个应用处于online/launching状态,则连续重试 3 次后调用Runtime.exit(2)使容器退出——避免容器"空转"占资源。这一点在 test/e2e/binaries/pm2-runtime.sh 中有对应测试(pm2_runtime exited_app.js启动注定崩溃的应用后,断言 PM2 进程最终被回收)。
测试验证:pm2-runtime 的自动化用例
仓库 test/e2e/binaries/pm2-runtime.sh 给出了pm2-runtime的端到端验证方式,可作为理解其行为契约的补充:
# 启动 4 个实例,断言有 4 个进程处于 online 状态 $pm2_runtime app.js -i 4 should 'should have started 4 apps' 'online' 4 # 通过 JSON 配置文件启动,并验证 watch / ignore_watch 透传生效 $pm2_runtime app.json $pm2 prettylist | grep "watch: \[ 'server', 'client' \]" $pm2 prettylist | grep "ignore_watch: \[ 'node_modules', 'client/img' \]"可见pm2-runtime与常规pm2共享同一套应用配置解析与进程管理内核,instances、watch、ignore_watch等配置均完整生效,容器内外行为保持一致。
集成 Keymetrics(PM2 Plus)监控
原文档明确提及示例内含 Keymetrics 集成能力,实现方式即 Dockerfile 中的两个环境变量:
ENV KEYMETRICS_SECRET xxxx ENV KEYMETRICS_PUBLIC yyyy这两个变量在 constants.js 中被正式消费:
MACHINE_NAME : process.env.INSTANCE_NAME || process.env.MACHINE_NAME || process.env.PM2_MACHINE_NAME, SECRET_KEY : process.env.KEYMETRICS_SECRET || process.env.PM2_SECRET_KEY || process.env.SECRET_KEY, PUBLIC_KEY : process.env.KEYMETRICS_PUBLIC || process.env.PM2_PUBLIC_KEY || process.env.PUBLIC_KEY,也就是说,只要在镜像或容器运行时注入合法的KEYMETRICS_SECRET/KEYMETRICS_PUBLIC(分别对应密钥对中的 secret 与 public),pm2-runtime在 Runtime4Docker.js 中创建内嵌 PM2 实例时就会自动带上这对密钥,从而把应用指标(事件循环延迟、内存、HTTP 请求等)推送至 Keymetrics 平台;INSTANCE_NAME或MACHINE_NAME则用于标识该容器在监控面板中的机器名。与之等效的运行时方式是在docker run时用-e覆盖:
$ docker run -e KEYMETRICS_SECRET=<你的secret> \ -e KEYMETRICS_PUBLIC=<你的public> \ -e INSTANCE_NAME=my-container \ docker-pm2-test若暂不使用监控,把KEYMETRICS_SECRET/KEYMETRICS_PUBLIC置空或删除即可,不影响进程托管主流程。此外constants.js还支持PM2_SECRET_KEY/PM2_PUBLIC_KEY与PM2_MACHINE_NAME等别名变量,便于在既有 CI/CD 环境中统一注入。
生产化改造建议
基于本示例可以继续演进,以下是贴合容器/编排场景的实用方向:
- 固定镜像 tag:将
latest-alpine替换为具体版本号(如keymetrics/pm2:12-alpine或对应 Node 版本的 tag),保证可复现构建; - 多实例负载均衡:在
process.config.js中设置instances与exec_mode: "cluster",由 PM2 内置负载均衡器分发请求(对应Runtime4Docker.js中-i选项的官方说明); - 健康检查:为应用增加
/healthz端点,并在 Dockerfile 中配置HEALTHCHECK,或依赖 K8s liveness/readiness probe,与pm2-runtime的退出码语义配合; - 密钥安全:
KEYMETRICS_SECRET/KEYMETRICS_PUBLIC应通过docker --build-arg、Docker secret 或 K8s ConfigMap/Secret 注入,绝不硬编码进镜像; - 日志标准化:按需使用
--json或--format输出结构化日志,便于采集到 ELK/Loki 等日志平台; - 冷启动优化:对于频繁启停的短生命周期容器,可评估
--fast-boot(见 lib/binaries/Runtime.js 的--fast-boot选项)以复用后台 PM2 实例、缩短二次启动耗时。
小结
本示例演示的"官方镜像 +pm2-runtime+ ecosystem 配置文件"三件套,是 PM2 官方推荐的容器内进程管理范式:pm2-runtime以 no-daemon 模式将 PM2 变为容器主进程,统一了进程守护、自动重启、日志聚合与 Keymetrics 监控,同时通过 exec 形式CMD与信号处理器保证容器优雅退出。无论是单一服务容器,还是需要多实例负载均衡与监控接入的生产集群,都可以从 examples/docker-pm2 这套最小示例出发快速落地。
- 运维
- CLI
- 可观测性
【免费下载链接】pm2
Node.js/Typescript/Bun Production Process Manager with a built-in Load Balancer.
相关推荐
FastAPI 容器化部署实战:基于官方 Python 镜像从零构建 Docker 镜像
FastAPI 容器化部署实战:基于官方 Python 镜像从零构建 Docker 镜像 导读 本指南以 docs/hi/docs/deployment/doc
后端Web框架API设计Fiora 安装部署完全指南:Node.js + MongoDB + Redis 环境搭建、PM2 守护与 Docker 容器化运行
Fiora 安装部署完全指南:Node.js + MongoDB + Redis 环境搭建、PM2 守护与 Docker 容器化运行 本文面向希望从零搭建并运行
即时通讯后端前端移动开发Sanic 应用 Docker 化部署实战:镜像构建、容器运行与 docker-compose 编排
Sanic 应用 Docker 化部署实战:镜像构建、容器运行与 docker compose 编排 导读 本文基于 Sanic 官方部署文档,完整讲解如何将一
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考