1. 为什么需要关注 rgw 的 root 与 log 池
很多人部署完 Ceph 集群、跑通 rgw 网关之后,就默认对象存储这条链路已经"完事"了。直到某天发现网关响应变慢、bucket 列表加载卡顿、或者集群里莫名其妙多出一堆小对象把 osd 的 omap 撑爆,才开始回头翻 rgw 的元数据池。我踩过最典型的一次坑,是某个业务方疯狂创建临时 bucket,结果.rgw.root池里的对象数量在两天内翻了好几倍,直接拖慢了整个网关的元数据读取。从那以后,我养成了一个习惯:上线 rgw 之前,先把 root 池和 log 池的对象结构摸清楚。
这篇内容就是把这几年对 rgw 元数据池的观察整理出来。核心讲三件事:root 池里到底存了哪些对象、每个对象的 key 和 value 长什么样、log 池在什么场景下会被写入以及它的对象结构有什么特点。适合已经会用radosgw-admin基本命令、但对底层元数据布局还比较模糊的运维和存储开发同学。读完你应该能做到:不依赖任何图形界面,直接用rados ls和rados get把 rgw 的关键元数据翻个底朝天,并且在排查元数据类故障时知道该盯哪个池、哪个对象。
需要先明确一个前提:rgw 的元数据并不是全部塞在一个池里。默认部署下,跟 rgw 相关的池至少有这几个——.rgw.root(存 realm、zonegroup、zone 等全局信息)、<zone>.rgw.control(控制类信息)、<zone>.rgw.meta(user、bucket、bucket.instance 等)、<zone>.rgw.log(日志类)、<zone>.rgw.data.root(数据根,部分版本用于 bucket 索引的根)。其中 root 池和 log 池是最容易被忽视、但出问题时又最要命的两个。下面逐个拆。
2. root 池的核心对象结构拆解
2.1 root 池里到底放了什么
.rgw.root池是 rgw 多站点架构的"户口本"。它不存用户数据,也不存 bucket 索引,只存三类东西:realm、zonegroup、zone。这三者是层层包含的关系——一个 realm 下可以有多个 zonegroup,一个 zonegroup 下可以有多个 zone。每个实体在 root 池里对应一个 rados 对象,key 的命名规则非常规整。
你可以直接用下面这条命令把 root 池里的所有对象列出来:
rados -p .rgw.root ls在一个刚初始化完的单站点集群里,输出通常长这样:
default.rgw.root zonegroup.default zone.default如果是多站点,还会多出类似zonegroup.secondary、zone.secondary这样的对象。注意这里的default是 zonegroup 和 zone 的名字,不是固定值,取决于你radosgw-admin realm create和zonegroup create时传的参数。
2.2 realm、zonegroup、zone 对象的 key 与 value
这三个对象的 value 都是同一种编码格式——JSON 经过 bufferlist 封装。早期版本用的是纯 JSON 文本,后来为了兼容性和压缩,改成了带长度前缀的 bufferlist。你直接rados get出来看到的是二进制乱码,需要借助radosgw-admin或者手动解析才能读出内容。
先看 realm 对象。key 是default.rgw.root(格式是<realm_name>.rgw.root),value 里包含的核心字段有:
| 字段 | 含义 | 典型值 |
|---|---|---|
| id | realm 的唯一标识 | 一串 UUID |
| name | realm 名称 | default |
| current_period | 当前 period 的 id | 数字编号 |
| epoch | period 的纪元号 | 递增整数 |
| zonegroups | 该 realm 下的 zonegroup 列表 | 数组 |
zonegroup 对象的 key 是zonegroup.<name>,value 里最关键的是zones数组和master_zone字段。master_zone决定了这个 zonegroup 里哪个 zone 是主,写请求默认走主 zone。zone 对象的 key 是zone.<name>,value 里包含pool字段——这个字段直接告诉你该 zone 的元数据池、数据池、log 池分别叫什么名字。
这里有个实操技巧:如果你不确定某个集群的 log 池叫什么,不用去翻配置文件,直接读 zone 对象就行。用radosgw-admin zone get --rgw-zone=<zone_name>会输出可读的 JSON,里面log_pool、data_pool、metadata_pool一目了然。这比在ceph.conf里找rgw log pool配置项靠谱得多,因为配置项可能被覆盖,而 zone 对象里的值是实际生效的。
2.3 period 对象与元数据版本控制
除了 realm、zonegroup、zone 这三个"静态"对象,root 池里还会有一个 period 对象,key 格式是period.<period_id>。这个对象是 rgw 元数据版本控制的核心。每次你修改 zonegroup 或 zone 的配置(比如加一个 zone、改 master_zone),rgw 都会生成一个新的 period,并把旧 period 保留一段时间。
period 对象的 value 里包含realm_id、zonegroup_id、zone_ids等字段,还有一个period_map,记录了当前 period 下所有 zone 的映射关系。这个对象的存在意味着:root 池的写入频率虽然低,但每次写入都涉及元数据版本切换。如果你在多站点环境里频繁改配置,root 池的 omap 会持续增长,虽然量不大,但值得监控。
注意:不要手动删除 root 池里的任何对象。我见过有人为了"清理"把
period.*对象删了,结果整个网关的元数据版本对不上,所有 zone 的配置读取全部失败,最后只能从备份恢复。root 池的对象数量应该是个位数到几十个,如果发现上百个,说明 period 积累过多,需要用radosgw-admin period trim清理旧 period。
3. log 池的触发场景与对象布局
3.1 log 池什么时候会被写入
log 池(默认叫<zone>.rgw.log)不是一直有数据的。它的写入触发条件主要有三类:bucket 操作日志、用户操作日志、以及 sync 相关的日志。在单站点模式下,如果你没开 bucket logging,log 池基本是空的。但在多站点模式下,log 池是数据同步的"传送带",每个跨 zone 的元数据变更都会在这里留下记录。
具体来说,以下操作会往 log 池写对象:
- 创建、删除 bucket
- 修改 bucket 的 ACL 或 policy
- 用户信息的变更(创建用户、改 key、改 quota)
- 跨 zone 的元数据同步事件
每条日志在 log 池里对应一个 rados 对象,key 的命名规则是<op_type>.<timestamp>.<unique_id>。比如bucket.create.2024-01-15T10:30:00.123456Z.abc123。这个命名规则的好处是:按时间排序时,同一类型的操作会聚在一起,方便 sync 模块按顺序消费。
3.2 log 对象的 value 结构
log 对象的 value 同样是 bufferlist 封装的。解出来之后是一个结构化的记录,核心字段包括:
| 字段 | 含义 |
|---|---|
| op | 操作类型,如 create、delete、modify |
| bucket | 涉及的 bucket 名称 |
| owner | 操作发起者的用户 id |
| timestamp | 操作时间戳 |
| entries | 具体的变更条目列表 |
entries字段是重点。它不是一个简单的字符串,而是一个列表,每个元素描述了一个具体的元数据变更。比如创建一个 bucket 时,entries 里会包含 bucket 的 name、owner、creation_time、zonegroup 等信息。sync 模块读取这个对象后,会根据 entries 里的内容在目标 zone 重放同样的操作。
这里有个容易误解的点:log 池里的对象不是"日志文件",而是"同步事件"。它跟系统日志(syslog)完全是两回事。你不需要去"读日志排查问题",而是要在 sync 卡住时检查 log 池里是否有积压的对象。用rados -p <zone>.rgw.log ls | wc -l可以快速看积压量,正常运行时这个数字应该很小(接近 0),如果持续增长到几千,说明 sync 跟不上或者目标 zone 有问题。
3.3 log 池的清理机制
log 池的对象不会永久保留。rgw 的 sync 模块在成功消费一条日志后,会把它标记为已处理,然后由后台的 trim 线程定期清理。清理的触发条件是:所有 zone 都确认消费到了某个时间点之前的日志。这个"确认"机制依赖每个 zone 在 root 池里维护的 sync status 对象。
如果你发现 log 池的对象数量只增不减,通常有三个原因:一是某个 zone 的 sync 进程挂了,没有上报消费进度;二是网络分区导致 zone 之间无法通信;三是 trim 线程的配置参数(如rgw_sync_log_trim_interval)设得太大。排查顺序建议从 sync status 对象入手,先确认每个 zone 的消费进度是否在推进。
实操心得:在多站点环境里,我习惯给 log 池单独配一个较小的 pg_num,比如 32 或 64。因为 log 池的对象通常很小(几 KB),但数量可能很多,pg 太多反而增加 osd 的元数据开销。另外,log 池的
omap使用率要重点监控,sync status 对象是存在 omap 里的,如果 omap 满了,sync 会直接卡死。
4. 用 rados 命令直接解剖对象
4.1 列出并定位关键对象
前面讲了理论,现在上手操作。第一步永远是列对象:
# 列出 root 池所有对象 rados -p .rgw.root ls # 列出 log 池所有对象(如果 zone 名是 default) rados -p default.rgw.log ls如果 log 池对象太多,ls会刷屏,可以配合head或grep:
rados -p default.rgw.log ls | grep 'bucket.create' | head -20这样能快速看到最近的 bucket 创建事件。注意ls返回的顺序不保证按时间,因为 rados 的列举是按对象名哈希分布的。要按时间排序,得把结果拿出来自己排。
4.2 读取并解析对象内容
直接rados get出来的是二进制,但我们可以用radosgw-admin的--dump选项或者ceph-dencoder来解码。更通用的做法是用rados get拿到文件,然后用 Python 的json模块尝试解析(如果是纯 JSON 的话):
rados -p .rgw.root get zone.default /tmp/zone.default.bin拿到文件后,先看头部:
xxd /tmp/zone.default.bin | head -5如果开头是{或[,说明是纯 JSON,直接cat就能看。如果是二进制,说明是 bufferlist 封装,需要用ceph-dencoder:
ceph-dencoder import /tmp/zone.default.bin type RGWZone dump_jsonceph-dencoder是 Ceph 自带的编解码工具,支持大部分 rgw 元数据类型。不过它要求你指定正确的 type,常见的 type 有RGWZone、RGWZoneGroup、RGWRados等。如果 type 不对,会报解码错误。
4.3 一个完整的解析示例
假设我要看 zone 的配置,完整流程如下:
# 1. 从 root 池取出 zone 对象 rados -p .rgw.root get zone.default /tmp/zone.bin # 2. 用 dencoder 解码 ceph-dencoder import /tmp/zone.bin type RGWZone dump_json # 3. 输出会是一段 JSON,包含 pool 配置输出的 JSON 里,log_pool字段的值就是 log 池的实际名称。这个值可能跟你在配置文件里写的不一样,因为 rgw 在初始化时会根据 zone 名自动生成池名。比如 zone 叫default,log 池默认就是default.rgw.log。如果你手动改过池名,以这里读出来的为准。
注意:
ceph-dencoder的 type 名称在不同 Ceph 版本里可能有差异。Luminous 版本用RGWZone,Octopus 之后有些类型改成了rgw_zone之类的小写形式。如果报 "unknown type",先用ceph-dencoder list_types | grep -i zone查一下实际支持的 type 名。
5. 常见问题与排查技巧实录
5.1 root 池对象丢失导致网关启动失败
这是最严重的一类故障。症状是 rgw 进程启动后立刻退出,日志里报failed to load realm或cannot find zonegroup。原因通常是 root 池被误删、或者 pool 的 pg 状态异常导致对象读不出来。
排查步骤:
- 先确认 root 池是否存在:
ceph osd pool ls | grep rgw.root - 再确认池里对象是否完整:
rados -p .rgw.root ls - 如果对象缺失,检查是否有备份。rgw 的 root 池对象可以用
radosgw-admin realm get、zonegroup get、zone get导出成 JSON,建议定期备份。 - 如果池本身损坏,先恢复 pg,再尝试从其他 zone 的 root 池同步数据。
预防措施很简单:给 root 池配一个独立的 CRUSH rule,确保它的副本分布在不同的故障域。root 池的数据量极小,多副本的开销可以忽略,但可靠性提升明显。
5.2 log 池积压导致 sync 延迟
症状是跨 zone 的数据同步延迟越来越大,radosgw-admin sync status显示某个 zone 的behind时间持续增长。这时候去 log 池看对象数量,通常能看到几千甚至上万个未消费的对象。
排查思路:
- 先看 sync status:
radosgw-admin sync status - 再看 log 池积压:
rados -p <zone>.rgw.log ls | wc -l - 检查目标 zone 的 rgw 进程是否正常:
ceph orch ps | grep rgw - 检查网络连通性:从源 zone 的 rgw 节点 telnet 目标 zone 的 rgw 端口
如果确认是目标 zone 挂了,恢复后 sync 会自动追上。如果是网络问题,解决网络后 sync 也会恢复。但如果是 log 池的 omap 满了,就需要扩容 omap 或者清理旧日志。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方向 |
|---|---|---|---|
| 网关启动报 realm 加载失败 | root 池对象丢失 | rados -p .rgw.root ls | 从备份恢复或从其他 zone 同步 |
| sync 延迟持续增长 | log 池积压 | radosgw-admin sync status | 检查目标 zone 和网络 |
| bucket 列表加载慢 | meta 池 omap 过大 | radosgw-admin bucket list | 清理无用 bucket 或扩容 omap |
| period 数量过多 | 频繁改配置 | radosgw-admin period list | 执行 period trim |
| log 池对象只增不减 | trim 线程未运行 | ceph config get rgw rgw_sync_log_trim_interval | 调整 trim 间隔或重启 rgw |
5.4 几个容易踩的坑
第一个坑是把 log 池当成普通日志池来清理。有人看到 log 池对象多,直接rados rm删了一批,结果 sync 模块因为找不到预期的日志对象而报错,跨 zone 同步直接中断。log 池的对象只能由 rgw 自己 trim,不能手动删。
第二个坑是在单站点环境里给 log 池配了太多 pg。单站点其实用不到 log 池的同步功能,但 rgw 初始化时还是会创建这个池。如果 pg 数设得太大(比如 512),会白白浪费 osd 的 pg 配额。建议单站点 log 池 pg 数控制在 32 以内。
第三个坑是忽略 root 池的备份。root 池的数据量小,很多人觉得不重要,但一旦丢失,整个网关的元数据体系就崩了。我现在的做法是每天用radosgw-admin realm get和zonegroup get导出 JSON,存到独立的备份池里。恢复时用realm set和zonegroup set导回去,几分钟就能搞定。
6. 监控与日常维护建议
6.1 该盯哪些指标
root 池和 log 池的监控不需要太复杂,重点看三个指标:
- 对象数量:root 池对象数应该稳定在个位数到几十个,log 池对象数在 sync 正常时应该接近 0。如果 log 池对象数持续超过 1000,就要警惕。
- omap 使用率:用
ceph osd pool stats <pool_name>看 omap 的占用情况。root 池的 omap 很小,log 池的 omap 会随 sync status 增长。 - pg 状态:
ceph pg stat看是否有 pg 处于undersized或degraded状态。root 池的 pg 如果异常,会直接影响网关启动。
6.2 日常维护清单
我给自己定的维护节奏是:
- 每天:检查 log 池对象数量,确认 sync status 正常
- 每周:导出 root 池的 realm、zonegroup、zone 配置到备份
- 每月:检查 period 数量,超过 10 个就 trim 一次
- 每季度:review 一次 root 池和 log 池的 pg 数、副本数是否合理
这些操作加起来不超过 10 分钟,但能避免 90% 的元数据类故障。存储这行有个特点:平时不出事,出事就是大事。root 池和 log 池就是典型的"平时没人看,出事要人命"的组件。
6.3 一个真实的排查案例
去年有个集群,网关每隔几小时就卡顿一次,每次持续几十秒。查了 osd、网络、cpu 都没问题。最后用rados -p .rgw.root ls发现 root 池里有 200 多个 period 对象。原因是某个自动化脚本在频繁调用zone modify,每次调用都生成新 period。period 多了之后,rgw 加载元数据时要遍历所有 period,导致周期性卡顿。执行radosgw-admin period trim --yes-i-really-mean-it清理后,问题消失。
这个案例的教训是:root 池的对象数量是一个被严重低估的监控指标。它不像数据池那样有直观的容量曲线,但对象数量异常增长往往意味着配置管理出了问题。
7. 从对象结构反推 rgw 的设计思路
把 root 池和 log 池的对象结构看完之后,其实能反推出 rgw 的几个设计取舍。第一,元数据与数据分离。root 池只存配置,meta 池存用户和 bucket 索引,data 池存实际对象。这种分离让每个池可以独立调优,比如 root 池用高副本保可靠,data 池用纠删码省空间。第二,日志即事件。log 池的对象不是流水账,而是可重放的事件。这个设计让多站点同步变成了"事件回放",比传统的 binlog 同步更灵活。第三,版本化元数据。period 机制让配置变更可以回滚,虽然实际用回滚的场景不多,但它保证了多 zone 之间的配置一致性。
理解这些设计思路之后,再看radosgw-admin的各种命令,就不会觉得它们是一堆零散的工具,而是一套围绕元数据版本和事件同步构建的体系。这也是为什么我建议每个 rgw 运维都花点时间把 root 池和 log 池的对象翻一遍——不是为了背命令,而是为了在脑子里建一张元数据地图。有了这张地图,排查问题时就知道该往哪个方向走,而不是盲目地重启服务或者翻日志。
最后分享一个小技巧:如果你用的是较新的 Ceph 版本,可以用ceph dashboard或者radosgw-admin sync status的 JSON 输出来做自动化监控。把 log 池对象数量和 period 数量做成告警项,阈值分别设 1000 和 10,基本能覆盖大部分元数据异常场景。这个配置我用了两年,帮我提前发现了三次潜在的 sync 故障。