折腾 rustfs:一次从 brew 升级到桶策略崩溃的完整排障记录 >
折腾 rustfs:一次从 brew 升级到桶策略崩溃的完整排障记录
适用读者:在 macOS / Linux 上用 Homebrew 管理 rustfs(S3 兼容对象存储)的用户。 本文记录了一次从「升级第三方 tap」到「桶策略字段非法导致整个桶不可访问、甚至误改后端文件把服务打挂」的完整链路,以及每个坑的正确解法。 说明:文中命令的密钥已脱敏为
<ACCESS_KEY>/<SECRET_KEY>,请替换为你的实际值。
一、背景
- 设备:Mac mini(Apple M4 / macOS arm64)
- rustfs:S3 兼容的对象存储,通过 Homebrew 第三方 tap
rustfs/tap安装
- 部署形态:本地对象存储,
mc别名rustfs,端口9000
- 数据根:
/Volumes/backup/rustfsdata(外置盘)
- 起始状态:服务跑在
1.0.0-alpha.70,数据根已有两个桶earl-backup(21.4 GiB 实数据)与qcow2(测试桶)
整条链路由一次「例行 brew upgrade」触发,最终演化成一场对象存储元数据救援。
二、排障链路(按时间线)
阶段 1:brew 第三方 tap 未信任,升级被拦
现象
根因 rustfs 来自外部 tap(项目方自维护的 Homebrew 源),不在 Homebrew 官方 core。出于安全,Homebrew 默认拒绝加载未信任 tap 的公式。
附带坑
brew update rustfs不是合法升级命令,Homebrew 会自动按upgrade行为处理(会提示你改用brew upgrade)。
- 升级是
alpha.70 → rc.1的大跨跃(预发布 → 候选版),需下载约 185MB 构建产物,从 GitHub release-assets 拉取,走代理可能不稳定。
正确做法
阶段 2:旧版本目录「删不掉」的假象
现象:升级完成后,Cellar 里 1.0.0-alpha.70 目录一度删不掉。
真相(两个原因叠加)
- 升级还在后台下载
rc.1(此时rc.1keg 是空壳,brew 刻意保留旧 keg 作回退);
- 在役的 rustfs 服务正占用 alpha.70 的二进制(进程持有文件句柄)。
正确做法
- 别
rm -rf旧目录——会切断在役对象存储服务,且 macOS 上进程仍持句柄会导致服务异常。
- 别 kill 升级进程——中途杀会留下半拉空壳 keg。
- 等升级自然跑完,brew 会在链接成功后自动
cleanup旧 keg(本次最终自动删除了 alpha.70,释放 185.8MB)。
阶段 3:删除非空桶 qcow2(删除标记幽灵条目)
现象
根因 mc ls 的正常视图隐藏了删除标记(delete marker)。用 --versions 才看得到:
桶虽未开版本控制,但只要存在删除标记,S3 就判定「非空」,mc rb 因此拒绝。mc rm --recursive --force 不加 --versions 只删「当前版本对象」,而那些对象早被删光,于是标记原样留下。
正确做法
经验:以后遇到「明明空了却删不掉」的桶,一律先
mc ls --versions看删除标记,再mc rb --force。
阶段 4:earl-backup 桶策略解析崩溃(核心坑)
现象
根因
- S3 / IAM 策略 JSON 字段大小写敏感,AWS 规范字段是
Id(大写 I、小写 d),不是ID。
- rustfs 的 Web 控制台在设置「私有」策略时,写出了非法的
"ID"(全大写);而 rustfs 的解析器严格按规范只认"Id",自己写错、自己解析不了。这条坏策略早就被存进了桶元数据xl.meta,升级前就存在。
- 影响面:rustfs 每次加载桶元数据都会先解析策略,解析失败就整体拒绝——所以任何列桶 / 读策略 / 改策略的操作都先崩,连
mc anonymous get都取不回原文。
已排除的解法(均失败)
|
路径 |
结果 |
|
|
被「加载旧策略先崩」挡死 |
|
|
同上,应用前先解析旧策略 |
|
rustfs 原生 |
无此子命令(只有 |
|
启动参数 / 环境变量关 bitrot |
无此开关 |
|
直接改后端文件 |
见阶段 5,反而把服务打挂 |
阶段 5:误改后端文件导致服务 FATAL(最严重,教训最深)
这是本文最值得记的坑:绝不要手动改对象存储的后端元数据文件。
误操作 为消除 ID 错误,直接用脚本把磁盘上的 xl.meta 里 "ID" → "Id"(都是 2 字节,长度不变,误以为安全)。
后果 rustfs 对元数据做了 bitrot 校验(HighwayHash 系列)。改了文件内容却没更新它存的校验和,重启时校验失败:
服务直接起不来——比你之前「只是列桶报错」更糟。
正确救援
- 改文件前务必先备份(
cp xl.meta xl.meta.bak_$(date +%s))。本次靠备份回滚:cp xl.meta.bak_* xl.meta,服务恢复。
- 对象存储的后端文件(
.metadata.bin/xl.meta/ 纠删码分片)是带校验和的黑盒,只走 API(mc / aws / s3cmd)操作,不要手改。手改必然失配校验和。
阶段 6:升级到 rc.4 修复解析器
调查 tap 已前进到 1.0.0-rc.4(比 rc.1 新约 3 周),近期 changelog 含:
社区 issue 也印证这是 rustfs 的已知严格性 bug(同策略在 MinIO 正常,在 rustfs 崩)。
做法
结果 rc.4 的解析器已能容错 / 归一化 "ID" → mc ls rustfs/earl-backup/ 直接恢复正常,21.4 GiB 数据完好。
澄清因果:不是升级造成的。根因是 rustfs 自己写出非法
"ID"策略;升级反而是修复手段(rc.4 修了策略解析)。
阶段 7:规范化策略 + 清理备份
rc.4 下 API 恢复正常,用 mc 写入一条规范策略(标准 "Id" 字段,去掉原策略里那条危险的 Deny *——显式 Deny 优先于 Allow,会连桶主一起锁死):
三、坑点总表(按严重度)
|
# |
坑 |
现象 |
根因 |
正确做法 |
|
1 |
第三方 tap 未信任 |
|
Homebrew 安全默认 |
|
|
2 |
旧 keg 删不掉 |
Cellar 旧目录在 |
升级未完 + 服务占用二进制 |
等升级完,别 |
|
3 |
删标记幽灵 |
桶空却 |
删除标记让桶判非空 |
|
|
4 |
策略字段 |
|
Web UI 写出全大写非法字段 |
升级修复版 / |
|
5 |
手改后端文件 |
bitrot 失配 FATAL 起不来 |
破坏元数据校验和 |
绝不手改,只走 API,改前备份 |
|
6 |
裸进程无托管 |
重启不恢复、崩溃不拉起 |
|
用 launchd 托管(见下) |
四、经验与正确做法(速记)
- 对象存储后端文件是黑盒:
.metadata.bin/xl.meta/ 纠删码分片带 bitrot / 纠删码校验和,任何手改都会失配 → 服务起不来。只走mc/aws/s3cmdAPI。
- 桶策略字段严格 AWS 规范:
Id/Version/Statement(注意大小写),Sid在 Statement 内部。全大写ID是非法字段。
- 删除非空桶:先
mc ls --versions看删除标记,再mc rb --force。
- 第三方 tap 先
brew trust,再brew upgrade;升级前看 changelog 是否修了已知 bug。
- 改任何后端文件前先
cp备份,且确认有回滚手段再动手。
- 服务用 launchd 托管(用户级
~/Library/LaunchAgents/或系统级/Library/LaunchDaemons/),设RunAtLoad=true+KeepAlive=true,实现开机自启 + 崩溃自动拉起;二进制用/opt/homebrew/bin/rustfs软链,升级后自动跟新版本。注意数据根在外置盘时的挂载依赖。
五、关键命令速查
附录:规范私有桶策略模板
(仅桶主全权、无匿名权限;不要加 Deny *,否则会把桶主也锁死。)

浙公网安备 33010602011771号