我们开发完一个 Node.js 或 Bun 项目,要部署到线上,pm2 往往是唯一选择。
它是由 Node.js 实现的进程管理工具,可以让 Node.js 项目持续运行、崩溃重启,是提供线上稳定服务的不二之选。
我从使用 Node.js 开始,就一直用 pm2 部署线上项目。
但久而久之有点厌倦,就萌生了自己写一个 Node.js 进程管理器的想法。
现在,这个想法已经实现了。
它采用国产编程语言 MoonBit 实现,叫做 bm2。
有什么特点呢?其实没啥特点。
什么意思?你没看错。
bm2 最大的特点,就是没啥特点,且听我一一道来。
首先,bm2 只提供了如下几个命令:
start:启动项目kill:关闭项目list:查看项目refresh:刷新bm2版本upgrade:升级项目version:打印版本
一共 6 个命令,比 pm2 少了一大截。
所以,我称之为 没啥特点。
但有个概念,叫做 少即是多。
虽然命令更少,功能更少,但它令人更踏实。
因为 pm2 里 90% 的命令,咱们平时都用不上,甚至从来没用过。
一个线上部署工具,最重要的就是 稳、狠、准。
bm2 就是这样一个工具,致力于用最少的实现、最少的功能,去解决最重要的线上部署问题:长期稳定,可靠运行。
接下来,我分享如何使用 bm2。

如图,这是在 Linux 服务器上的实战步骤,下面一一道来。
安装 MoonBit
curl -fsSL https://cli.moonbitlang.cn/install/unix.sh | bash
bm2 是用 MoonBit 实现的,第一步就是先在 Linux 服务器上安装 MoonBit。
这里注意一下,bm2 只支持 Linux 系统。
当然,macOS 应该也是支持的,只不过我没有机器测试,欢迎反馈。
因为线上部署,非 Linux 莫属,没必要让 bm2 在 Windows 系统上跑。
重载 bash 环境
source ~/.bashrc
moon help
MoonBit 安装好之后,用 source ~/.bashrc 重新加载一下环境,让 moon 命令生效。
如果你要问什么是 moon?
你就这么理解,它就是 Node.js 里面的 npm。
更新索引
moon update
# Registry index cloned successfully
# Symbols updated successfully
接着,运行 moon update 更新索引。
安装 bm2
moon install chensuiyi/bm2/... --bin ~/.local/bin
# Downloading chensuiyi/bm2@0.2.1
# Downloading moonbitlang/x@0.4.41
# Downloading moonbitlang/quickcheck@0.14.0
# Downloading bobzhang/toml@0.4.2
# Downloading bobzhang/lexer@0.2.0
# Finished. moon: ran 17 tasks, now up to date
# Success: Installed `bm2` to `/root/.local/bin/bm2`
# Finished. moon: ran 3 tasks, now up to date
# Success: Installed `bm2d` to `/root/.local/bin/bm2d`
接着,用如上命令安装 bm2,通过 --bin 参数,安装后直接放到 ~/.local/bin 目录下。
这是我非常喜欢 MoonBit 的一个点,从语言层面提供了命令的 下载 到 安装 闭环!
看到 Downloading、Finished、Success,就表示安装成功了。
把 bm2 加入到环境变量
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
问题来了,光下载还不顶用,bm2 命令还是找不到呀。
因为 ~/.local/bin 目录不在我们的环境变量里。
所以,我们需要运行如上 2 个命令,把 ~/.local/bin 加入到环境变量中,并重载 .bashrc 配置。
当然,如果一开始就指定把 bm2 放到 /usr/bin、/usr/local/bin 之类的目录,就能直接用起来啦。
[ root@tsy7822 ~ ]# bm2 list
no instances
接着,运行 bm2 list,会提示 no instances 无实例。
因为我们还没运行项目呢。
运行项目
[ root@tsy7822 /www/wwwroot/xxx ]# bm2 start
started
NAME ID PID PORT STATUS RESTARTS MEMORY UPTIME CWD
api 0 2599431 3002 online 0 53 MB 0s /www/wwwroot/xxx
api 1 2599432 3003 online 0 53 MB 0s /www/wwwroot/xxx
api 2 2599434 3004 online 0 53 MB 0s /www/wwwroot/xxx
api 3 2599436 3005 online 0 53 MB 0s /www/wwwroot/xxx
我们进入到 Node.js 或 Bun 项目目录,运行 bm2 start,就能看到项目运行起来啦。
bm2 配置
保险起见,可不是任何项目都能直接运行的。
必须要为项目提供一个 bm2.toml 配置文件。
name = "api" # 项目代号,唯一,字母,数字,下划线
cwd = "/srv/api" # 项目路径
script = "src/index.js" # 项目入口
instances = 2 # 实例数量
port = 3000 # 监听端口
[env]
API_HOST = "https://abc.com/"
MYSQL_HOST = "127.0.0.1"
MYSQL_PORT = "5566"
这是一个基本的项目配置。
| 字段 | 含义 |
|---|---|
name | 项目/应用名。字母开头,后接字母、数字和下划线,在所有已注册项目中唯一。 |
script | cwd 内的相对脚本路径,禁止 ..。 |
instances | 实例数量,1..1024。 |
port | 第一个实例的端口,后续实例使用连续端口。 |
可选字段(含默认值):
| 字段 | 默认值 | 含义 |
|---|---|---|
cwd | 配置目录 | 应用工作目录(绝对路径)。 |
runtime | bun | 运行时可执行文件,bun 或 node。 |
max_memory_mb | 512 | 最大 VmRSS(MiB),至少为 1。 |
max_restarts | 10 | 允许的连续异常重启次数,0 表示禁用重试。 |
restart_delay_ms | 1000 | 自动重启前的延迟。 |
min_uptime_ms | 10000 | 早于该时长的干净退出会计入重启预算。 |
stop_timeout_ms | 10000 | SIGTERM 到 SIGKILL 之间的宽限期,最大 60000。 |
完整的配置如上。
环境变量
bm2 只把自己的 PATH、HOME、TMPDIR 传给被管理进程,外加这些保留变量:
BM2_APP_NAMEBM2_INSTANCE_IDBM2_APP_INSTANCE(实例编号,第一个实例为"0",与 pm2 的NODE_APP_INSTANCE约定一致,便于集群感知的应用选择主实例)BM2_APP_PORT(分配给该实例的端口)NODE_ENV(恒为"production",bm2是生产运行工具,被管理应用可以可靠地检测到自己在bm2之下)
Bun 会自动从项目的 cwd 加载 .env 文件。
bm2 保留变量优先于 [env] 和 .env 中的值。
环境变量名必须使用字母、数字和下划线,不能以数字开头。
上述保留名加 PATH、HOME、TMPDIR 会被拒绝。
nginx 和 caddy 负载均衡
设置了 bm2.toml 配置,并成功运行项目后,bm2 会基于 port 端口和 instances 实例数量,fork 出多个进程。
[ root@tsy7822 /www/wwwroot/xxx ]# bm2 start
started
NAME ID PID PORT STATUS RESTARTS MEMORY UPTIME CWD
api 0 2599431 3002 online 0 53 MB 0s /www/wwwroot/xxx
api 1 2599432 3003 online 0 53 MB 0s /www/wwwroot/xxx
api 2 2599434 3004 online 0 53 MB 0s /www/wwwroot/xxx
api 3 2599436 3005 online 0 53 MB 0s /www/wwwroot/xxx
所以你就会看到这样的多个实例,互相独立,互不影响。
要让不同的实例承担共同的访问流量,就要对 Nginx 或 Caddy 这样的 Web 服务托管工具进行 负载均衡 配置,把请求平均分配到不同的实例上面。
upstream api_backend {
server 127.0.0.1:3002;
server 127.0.0.1:3003;
server 127.0.0.1:3004;
server 127.0.0.1:3005;
}
Nginx 配置如上。
xxx.com {
# 多个上游 = 自动轮询负载均衡
reverse_proxy 127.0.0.1:3002 127.0.0.1:3003 127.0.0.1:3004 127.0.0.1:3005
}
Caddy 配置如上。
当然,最后都要重启 Nginx 或 Caddy。
到这一步,你的线上服务就平稳运行啦。
尾言
bm2 把 pm2 最核心的能力重新做了一遍:稳定运行,崩溃重启,多实例部署。
它是一个正在开发中的项目,欢迎大家积极反馈,不断完善。
如果你在部署中遇到了问题,欢迎在评论区留言交流。
开源地址:https://github.com/chenbimo/bm2
