十五分钟上手
这一页从空目录开始:写一份配置,把服务跑起来,改一次配置,再停下与退役。
命令都在项目目录里执行。装好 bm2 之前先看安装与升级 bm2。
一、准备项目
新建一个目录,写两个文件。
my-api/
├── bm2.toml
└── index.tsbm2.toml 是这份配置的全部。
name = "my-api"
script = "index.ts"
instances = 2
port = 3000index.ts 是一个最小服务。
const port = Number(process.env.BM2_APP_PORT)
Bun.serve({
port,
reusePort: true,
fetch() {
return new Response(
`instance ${process.env.BM2_APP_INSTANCE} in ${process.env.NODE_ENV}`,
)
},
})四个细节值得先讲清。
- 端口不要写死,读
BM2_APP_PORT。fork 模式下每个实例拿到的值不同。 reusePort: true只在默认的cluster模式下需要。BM2_APP_INSTANCE是实例编号,从0开始,用它看请求落到了哪个实例。NODE_ENV由启动动词决定:serve给production,dev给development。
二、先校验配置
bm2 check它只读当前目录的 bm2.toml,不碰守护进程,也不登记任何东西。
字段写错、脚本不存在、端口越界、占位符变量没设置,都会在这里被指出来。它还会探测整段端口,告诉你端口是空闲还是已被占用。
bm2 serve 执行同一套校验,所以这一步可以跳过,但部署前跑一次很值。
三、启动
bm2 serve它做四件事:校验配置、探测运行时、登记项目、拉起实例。
看到 已启动 之后,看看现状。
bm2 list先是一行守护进程摘要,然后是实例表(英文环境下的形状)。
api: starting
api: online
started
┌────────┬────┬───────┬──────┬────────────┬─────────┬────────┬───────┬────────┬─────────┬─────────────┐
│ NAME │ ID │ PID │ PORT │ MODE │ EXEC │ STATUS │ TIMES │ MEMORY │ UPTIME │ CWD │
├────────┼────┼───────┼──────┼────────────┼─────────┼────────┼───────┼────────┼─────────┼─────────────┤
│ my-api │ 0 │ 48213 │ 3000 │ production │ cluster │ online │ 1 │ 61 MB │ 8s │ /srv/my-api │
│ my-api │ 1 │ 48214 │ 3000 │ production │ cluster │ online │ 1 │ 60 MB │ 8s │ /srv/my-api │
└────────┴────┴───────┴──────┴────────────┴─────────┴────────┴───────┴────────┴─────────┴─────────────┘表头与文案跟随项目语言。BM2_LANG=en 可以固定成上面这一份。
两个实例共用一个 3000,cluster 模式的正常表现。
验证服务确实在响应。
curl http://127.0.0.1:3000反复执行,返回的实例编号会变化,说明内核正在把新连接分给两个实例。同一个 keep-alive 连接上的后续请求仍然落在同一个实例,这是 cluster 模式的分发单位。
四、看日志
日志在状态目录里,按项目分开。
ls ~/.bm2/my-api/logs/my-api-0.out.log
my-api-0.error.log
my-api-0.crash.log三份按用途分开:标准输出、标准错误、崩溃诊断。跟随实时输出。
tail -f ~/.bm2/my-api/logs/my-api-0.out.log日志超过 [logs] max_size_mb(默认 10 MiB)会自动轮转,归档留在同目录,代数由 keep 决定。
五、改配置
改端口或实例数,再执行一次。
bm2 serve每次 serve 都是一次完整重启:旧实例全部停下,新实例按新配置起来。那一刻反代短暂 502 是正常的。
要零停机改配置,做法是在另一段端口上把新版本跑起来,切完流量再退役旧项目。这件事 bm2 不代劳。
六、停下与恢复
bm2 stop my-api实例全部退出,但项目与日志都留在册。bm2 list 仍然能看到它,状态是 stopped。
这个停止是持久的:守护进程重启、bm2 reload、机器重启都不会把它带回来。
bm2 serve再执行一次,它就从停止状态恢复。
七、退役
bm2 remove my-api项目注销,名字交还,日志保留。真正想翻日志的时刻,往往发生在项目消失之后。
连日志一起清掉。
bm2 remove my-api -f退役之后名字可以重新登记。手动删目录、或从别的机器拷过来的残留,用 clean 扫。
bm2 clean --dry-run # 只看不动
bm2 clean # 真删八、接反向代理
cluster 模式只有一个端口,网关写一条就够。
upstream my_api {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://my_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}换成 exec_mode = "fork" 时端口从 port 起连续分配:两个实例是 3000 与 3001,upstream 要写两条,而且以后改实例数还得同步改网关。
两种模式的区别与取舍:cluster 模式、fork 模式、怎么选。
九、接告警
在 bm2.toml 里加一段,改完重新 bm2 serve。
[notify]
enabled = true
lang = "zh"
events = ["crash", "errored", "memory_limit", "reuseport_missing"]
threshold_rss_percent = 80
[[notify.report]]
name = "故障告警"
events = ["crash", "errored"]
[[notify.report.target]]
platform = "feishu"
url = "__BM2_FEISHU_WEBHOOK__"真值放在项目目录的 .env.production 里,不写进配置。
BM2_FEISHU_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxxx先发一条测试消息,确认链路通。
bm2 notify事件、阈值、周期报告的完整写法在通知。
十、升级 bm2 与换班
bm2 upgrade它查最新版本、用 moon install 换入两个二进制,再让新守护进程接管。应用全程继续运行。
只换了二进制、想手动换守护进程时:
bm2 reload走到这里你已经会了
- 配置怎么写,校验怎么跑。
- 实例怎么起,状态怎么看。
- 日志在哪,轮转怎么配。
- 停止、恢复、退役、清理的分工。
- 网关怎么接,告警怎么发。
- 升级与换班。
日常用得最多的是 bm2 list 与 bm2 serve。
出问题时翻排障与常见问题。
上线前的九条
bm2 check通过,配置与端口都没有隐患。cluster模式的应用已经开了reusePort,并且监听所有地址而不是只绑回环。fork模式整段端口都空着,网关 upstream 逐个覆盖。max_memory_mb与机器内存匹配,max_restarts不要设成很大的数字。[logs]给了明确的大小与代数,磁盘有上界。[notify]的凭据走__NAME__占位符,真值在.env里,没有进仓库。bm2 notify能收到测试消息。- 网关指向正确的端口:
cluster一条,fork每个实例一条。 - 开机自启已经配好。 bm2 不管自启,守护进程启动时也不会把项目拉起来,所以必须有一个 systemd 单元(或等价机制)在开机后执行
bm2 serve。
第九条最容易被忘:手工试的时候一切正常,重启一次机器后所有项目都不在了。