要实现pg按机房→机架→主机→osd层级分布,必须显式建模物理拓扑:先用ceph osd crush add-bucket创建rack、room等bucket,再用ceph osd crush move逐级挂载;然后编写含chooseleaf firstn 3 type rack的crush规则,确保副本落于不同机架且最终收敛到osd;规则生效需配合扩容、重权或重建pool触发重分布。

要让 PG 真正按机房 → 机架 → 主机 → OSD 的层级分布,不能只靠默认 default root 和 host bucket;必须显式建模物理拓扑,并绑定规则到对应 failure domain。否则 CRUSH 会“以为”所有 OSD 都在同一个机架里,副本就可能落在同一台机器甚至同一块盘上。
怎么建模机房和机架的 bucket 层级
CRUSH Map 是树形结构,叶子是 OSD,中间节点是虚拟 bucket(如 rack、room、root)。默认只有一层 host,你要手动补全上层。
- 先用
ceph osd tree确认当前 OSD 所属主机名(比如serverc0、serverd3),再按物理归属分组:哪些主机属于rack1,哪些属于rack2,哪些 rack 属于room-a - 用
ceph osd crush add-bucket创建新 bucket:ceph osd crush add-bucket rack1 rack、ceph osd crush add-bucket room-a room - 用
ceph osd crush move把 host 挪进 rack,再把 rack 挪进 room:ceph osd crush move serverc0 rack=rack1,ceph osd crush move rack1 room=room-a - 最后把 room 挂到 root:
ceph osd crush move room-a root=default(注意不要覆盖原有default,可新建root=dc-prod更安全)
怎么写 rule 强制副本跨机房+跨机架
关键在 chooseleaf 的 type 和 num_reps 参数。想实现「3副本,分别落在不同机房、且每个机房内再落在不同机架」,rule 必须分两步选:
- 第一步用
chooseleaf firstn 3 type room:从roombucket 中选 3 个不同机房(要求集群至少有 3 个机房) - 第二步对每个选中的
room,再向下选rack—— 但 CRUSH rule 不支持嵌套,所以实际得改成:chooseleaf firstn 1 type rack,并确保每个rack下至少有 1 个 OSD 可用 - 更稳妥的做法是设
type rack+firstn 3,再靠chooseleaf自动落到 rack 下的 OSD:这样能保证 3 副本在 3 个不同机架,且只要每个 rack 有 ≥1 OSD 就能成功 - 完整 rule 示例:
rule dc-safe { ruleset 1 type replicated min_size 1 max_size 10 step take dc-prod step chooseleaf firstn 3 type rack step emit }
为什么 choose firstn 不行,必须用 chooseleaf
choose 只选 bucket 节点(比如选中 3 个 rack),不往下钻;而 chooseleaf 会递归选到叶子 OSD。如果你写 choose firstn 3 type rack,CRUSH 返回的是 3 个 rack ID,客户端根本没法定位 OSD,写入直接失败,报错类似 error 22: Invalid argument 或卡在 pg is stuck inactive。
- 只有
chooseleaf才真正完成「从 rack → host → OSD」的路径收敛 - 如果某 rack 下所有 OSD
out或down,chooseleaf会自动 fallback 到其他 rack,而choose会直接报错 - 验证 rule 是否生效:用
ceph osd getcrushmap -o map.bin && crushtool -d map.bin -o map.txt查看 rule 步骤,确认含chooseleaf
调整后 PG 分布没立刻变化?别急着重刷
CRUSH rule 变更只影响新写入对象的 PG 映射,已有 PG 不会自动迁移。你看到 ceph -s 里 PG 状态还是 active+clean,不代表数据已跨机房。
- 真正触发重分布靠两个动作:扩容(加新 OSD)、或执行
ceph osd reweight改权重(让 CRUSH 认为某些 OSD “更值得放数据”) - 更可控的方式是重建 pool:删旧 pool → 用新 rule 创建同名 pool → 用
rbd export/import或ceph-migrate迁移数据 - 切记:改 rule 前先
ceph osd set noout,避免 reweight 过程中 OSD 临时 down 导致 PG remap 风险
最易被忽略的一点:CRUSH rule 里的 type 名称必须和 bucket 定义时完全一致(大小写敏感),rack 写成 Rack 或 rack1 都会导致 rule 加载失败且无明确提示,只会表现为 PG 永远无法进入 active 状态。











