已停止的网站

折腾 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 目录一度删不掉。

真相(两个原因叠加)

  1. 升级还在后台下载 rc.1(此时 rc.1 keg 是空壳,brew 刻意保留旧 keg 作回退);
  1. 在役的 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 都取不回原文。

已排除的解法(均失败)

路径

结果

mc anonymous set private/download

被「加载旧策略先崩」挡死

mc anonymous set-json <合法策略>

同上,应用前先解析旧策略

rustfs 原生 heal / repair

无此子命令(只有 server/info/tls/diagnose)

启动参数 / 环境变量关 bitrot

无此开关

直接改后端文件 xl.meta

见阶段 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 未信任

Refusing to load formula

Homebrew 安全默认

brew trust 后再升级

2

旧 keg 删不掉

Cellar 旧目录在

升级未完 + 服务占用二进制

等升级完,别 rm

3

删标记幽灵

桶空却 rb 报非空

删除标记让桶判非空

mc ls --versions 查,mc rb --force

4

策略字段 ID

unknown field ID

Web UI 写出全大写非法字段

升级修复版 / mc 写规范策略

5

手改后端文件

bitrot 失配 FATAL 起不来

破坏元数据校验和

绝不手改,只走 API,改前备份

6

裸进程无托管

重启不恢复、崩溃不拉起

nohup 裸起

用 launchd 托管(见下)


四、经验与正确做法(速记)

  1. 对象存储后端文件是黑盒:.metadata.bin / xl.meta / 纠删码分片带 bitrot / 纠删码校验和,任何手改都会失配 → 服务起不来。只走 mc / aws / s3cmd API。
  1. 桶策略字段严格 AWS 规范:Id / Version / Statement(注意大小写),Sid 在 Statement 内部。全大写 ID 是非法字段。
  1. 删除非空桶:先 mc ls --versions 看删除标记,再 mc rb --force。
  1. 第三方 tap 先 brew trust,再 brew upgrade;升级前看 changelog 是否修了已知 bug。
  1. 改任何后端文件前先 cp 备份,且确认有回滚手段再动手。
  1. 服务用 launchd 托管(用户级 ~/Library/LaunchAgents/ 或系统级 /Library/LaunchDaemons/),设 RunAtLoad=true + KeepAlive=true,实现开机自启 + 崩溃自动拉起;二进制用 /opt/homebrew/bin/rustfs 软链,升级后自动跟新版本。注意数据根在外置盘时的挂载依赖。

五、关键命令速查


附录:规范私有桶策略模板

(仅桶主全权、无匿名权限;不要加 Deny *,否则会把桶主也锁死。)

posted @ 2026-08-31 16:59  kesz  阅读(23)  评论(0)    收藏  举报