首页/MoonBit/bm2,不到1M大小实现pm2的nodejs&bun线上部署

我们开发完一个 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 的一个点,从语言层面提供了命令的 下载安装 闭环!

看到 DownloadingFinishedSuccess,就表示安装成功了。

把 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项目/应用名。字母开头,后接字母、数字和下划线,在所有已注册项目中唯一。
scriptcwd 内的相对脚本路径,禁止 ..
instances实例数量,1..1024
port第一个实例的端口,后续实例使用连续端口。

可选字段(含默认值):

字段默认值含义
cwd配置目录应用工作目录(绝对路径)。
runtimebun运行时可执行文件,bunnode
max_memory_mb512最大 VmRSS(MiB),至少为 1
max_restarts10允许的连续异常重启次数,0 表示禁用重试。
restart_delay_ms1000自动重启前的延迟。
min_uptime_ms10000早于该时长的干净退出会计入重启预算。
stop_timeout_ms10000SIGTERM 到 SIGKILL 之间的宽限期,最大 60000

完整的配置如上。

环境变量

bm2 只把自己的 PATHHOMETMPDIR 传给被管理进程,外加这些保留变量:

  • BM2_APP_NAME
  • BM2_INSTANCE_ID
  • BM2_APP_INSTANCE(实例编号,第一个实例为 "0",与 pm2 的 NODE_APP_INSTANCE 约定一致,便于集群感知的应用选择主实例)
  • BM2_APP_PORT(分配给该实例的端口)
  • NODE_ENV(恒为 "production"bm2 是生产运行工具,被管理应用可以可靠地检测到自己在 bm2 之下)

Bun 会自动从项目的 cwd 加载 .env 文件。

bm2 保留变量优先于 [env].env 中的值。

环境变量名必须使用字母、数字和下划线,不能以数字开头。

上述保留名加 PATHHOMETMPDIR 会被拒绝。

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