Ceph 生产环境搭建 | 第 7 天:CRUSH Map 架构解析与故障域规划

第 7/20 天

引言:CRUSH Map 在 Ceph 生产环境中的核心地位

在前 6 天的连载中,我们已经完成了 Ceph Squid v19 集群在 Ubuntu 24.04 LTS 上的引导初始化、Monitor/Manager 节点部署以及基于 BlueStore 的 OSD 存储节点上线。集群已经可以写入数据,但你是否想过:当客户端写入一个对象时,Ceph 如何决定这个对象最终落在哪几个 OSD 上? 这个问题的答案就是 CRUSH——Ceph 分布式存储的灵魂算法。

CRUSH(Controlled Replication Under Scalable Hashing,可控可扩展哈希复制)是 Ceph 区别于传统集中式元数据存储系统的核心机制。传统分布式存储(如 HDFS、Ceph 的早期设计思路)依赖一个中心化的元数据服务器来记录”哪个数据放在哪个节点”,这个元数据服务器会成为单点瓶颈和性能天花板。CRUSH 则通过一个可计算的哈希算法,让任何客户端或 OSD 都能独立计算出任意对象的位置,无需查询中心节点。

而承载 CRUSH 算法运行所需的全部拓扑信息的数据结构,就是 CRUSH Map。它定义了集群的物理拓扑结构(机房、机架、主机、OSD)、故障域层级、以及数据分布规则(CRUSH Rule)。在生产环境中,CRUSH Map 的规划质量直接决定了集群的数据可靠性、故障隔离能力和扩容时的数据迁移效率。本篇将深入解析 CRUSH Map 的架构原理,并通过完整实战演示如何定制 CRUSH Map 与故障域规划。

K8s Logo

架构与原理:CRUSH 算法与 CRUSH Map 数据结构

CRUSH 算法核心思想

CRUSH 算法的核心流程可以用一句话概括:给定一个对象 ID(x)和集群拓扑(CRUSH Map),通过伪随机哈希函数 + 节点权重,确定性地计算出该对象的所有副本应放置在哪些 OSD 上。

整个计算过程不需要任何中心化元数据查询,关键特性如下:

特性 说明 生产价值
确定性 同样的输入永远得到同样的输出 所有节点计算结果一致,无需协调
去中心化 任何节点都能独立计算 消除元数据服务器单点瓶颈
稳定性 拓扑变化时仅迁移最小数据量 扩容/缩容时数据迁移可控
权重感知 按 OSD 容量比例分布数据 大容量盘承载更多数据
故障域感知 按 Bucket 层级隔离副本 避免单机架/机房故障丢数据

CRUSH Map 的四大组成部分

CRUSH Map 是一个文本/二进制结构,包含四个关键部分:

1. Devices(设备列表):列出集群中所有 OSD,每条形如 device 0 osd.0,编号从 0 开始。这是 CRUSH 计算的”叶子节点”。

2. Bucket Types(Bucket 类型):定义拓扑层级结构。Ceph 内置了从粗到细的层级类型:

💻 代码示例

root → 整个集群或集群逻辑分区的根

datacenter → 数据中心级别

room → 机房房间

row → 机房行

pod → 机房Pod(一组机架)

rack → 机架

host → 物理主机

chassis → 机箱(刀片服务器场景)

osd → 单个OSD设备(叶子,不可再分)

这些类型形成树形继承关系,每个子节点的故障不会影响父节点其他分支,这就是故障域隔离的基础。

3. Buckets(Bucket 实例):按照 Bucket Types 定义的层级,将实际的 OSD 组织成树。例如:

💻 代码示例

root default {

id -1

alg straw2

hash 0

item rack rack-1 weight 10.0

item rack rack-2 weight 10.0

}

rack rack-1 {

id -3

alg straw2

hash 0

item host node-1 weight 5.0

item host node-2 weight 5.0

}

host node-1 {

id -5

alg straw2

hash 0

item osd.0 weight 1.0

item osd.1 weight 1.0

}

这里的关键参数 alg straw2 是 Ceph 推荐的 Bucket 算法,相比早期的 uniform/list/tree 算法,straw2 在节点增删时数据迁移量最小,是生产环境的标准选择。

4. Rules(CRUSH 规则):定义数据如何从 CRUSH Map 树中选取副本位置。一条典型规则如下:

💻 代码示例

rule replicated_rule {

id 0

name "replicated_rule"

steps [

step take root default

step chooseleaf firstn 0 type host

step emit

]

}

规则的核心是 take → choose/chooseleaf → emit 三步:
– take:从某个 Bucket 开始选取
– chooseleaf firstn 0 type host:选取 N 个 host 类型的子树,并在每个 host 下递归选一个叶子 OSD(即 chooseleaf 的 leaf 含义)
– type host:这是故障域类型——意味着同一对象的多个副本不会落在同一台 host 上

K8s Logo

故障域规划的设计哲学

故障域是 CRUSH Map 规划中最重要的生产概念。故障域 = 你能容忍同时发生故障而不丢数据的最大物理边界。 如果 CRUSH Rule 的 type host 设为 host,那么同主机 OSD 全部损坏时,只要副本数足够,数据不丢。如果设为 rack,那么同机架断电也不丢数据。

生产环境的常见选择:

副本数 推荐 chooseleaf type 故障域含义 适用场景
3 副本 host 单机故障不丢数据 单机房、节点数≥3
3 副本 rack 单机架故障不丢数据 多机架、机架数≥3
3 副本 datacenter 单机房故障不丢数据 双机房/三机房灾备
纠删码 4+2 rack 1-2 个机架故障可恢复 大容量冷数据

部署实战:CRUSH Map 导出、定制与故障域配置

本节在已部署好的 Squid v19 集群上演示完整的 CRUSH Map 定制流程。所有操作在部署节点(拥有 cephadm 的 admin 节点)上执行。

1. 环境准备:查看当前 CRUSH Map 与集群拓扑

首先确认集群状态和当前默认 CRUSH Map:

💻 代码示例

# 确认集群健康状态

ceph -s

 

# 查看当前 CRUSH 树结构(人类可读层级视图)

ceph osd tree

 

# 查看当前所有 CRUSH Rule

ceph osd crush rule ls

# 输出示例:

# replicated_rule

# erasure-code

 

# 查看某条 rule 的详细步骤

ceph osd crush dump | python3 -m json.tool | head -80

典型 ceph osd tree 输出展示了 root → host → osd 的层级和权重:

💻 代码示例

ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT AFFINITY

-1 27.00000 root default

-7 9.00000 host ceph-node1

1 hdd 9.00000 osd.1 up 1.00000 1.00000

-5 9.00000 host ceph-node2

2 hdd 9.00000 osd.2 up 1.00000 1.00000

-3 9.00000 host ceph-node3

0 hdd 9.00000 osd.0 up 1.00000 1.00000

2. 导出 CRUSH Map 为可编辑文本

要定制 CRUSH Map,需要先导出反编译的文本格式:

💻 代码示例

# 获取当前编译后的 CRUSH Map 二进制

ceph osd getcrushmap -o /tmp/crushmap-compiled.bin

 

# 反编译为文本格式(需 ceph-crush-location 或源码 crushtool)

# Squid 版本:直接使用 crushtool

crushtool -d /tmp/crushmap-compiled.bin -o /tmp/crushmap-decompiled.txt

 

# 查看反编译文本

head -100 /tmp/crushmap-decompiled.txt

反编译文本的头部 tunable 参数控制 CRUSH 算法的行为,例如:

💻 代码示例

# begin crush map

tunable choose_local_tries 0

tunable choose_local_depth_to_fallback 0

tunable choose_try_fall_tree 1

tunable choose_leaf_stable 1

 

# devices

device 0 osd.0 class hdd

device 1 osd.1 class hdd

device 2 osd.2 class hdd

 

# types

type 0 osd

type 1 host

type 2 chassis

type 3 rack

type 4 row

type 5 pdu

type 6 pod

type 7 room

type 8 datacenter

type 9 zone

type 10 region

type 11 root

 

# buckets

host ceph-node1 { … }

host ceph-node2 { … }

host ceph-node3 { … }

root default { … }

3. 定制 CRUSH Map:引入机架故障域

假设我们的 3 台 OSD 主机分布在 3 个独立机架(rack-a / rack-b / rack-c),希望副本按机架隔离。下面直接通过 ceph CLI 添加 rack 层级,而不手动改文本(手动编译注入风险高,CLI 是生产推荐方式):

💻 代码示例

# 1) 创建 3 个 rack bucket

ceph osd crush add-bucket rack-a

ceph osd crush add-bucket rack-b

ceph osd crush add-bucket rack-c

 

# 2) 将每个 host 移动到对应的 rack 下(注意权重单位为TB)

ceph osd crush move ceph-node1 root=default rack=rack-a

ceph osd crush move ceph-node2 root=default rack=rack-b

ceph osd crush move ceph-node3 root=default rack=rack-c

 

# 3) 验证新的层级结构

ceph osd tree

# 现在应看到 default -> rack-x -> host -> osd 的 4 层结构

4. 创建基于机架故障域的 CRUSH Rule

💻 代码示例

# 创建新规则:replicated_rule_rack,副本按 rack 故障域分布

ceph osd crush rule create-replicated

replicated_rule_rack

default

rack

hdd

 

# 查看规则是否生效

ceph osd crush rule ls

# replicated_rule

# replicated_rule_rack

 

# 查看规则详细步骤

ceph osd crush rule dump replicated_rule_rack

5. 验证 CRUSH 分布正确性

最关键的一步:模拟数据分布,验证副本确实分布在不同的 rack 上:

💻 代码示例

# 生成测试 PG 分布报告(num réplicas = 3)

ceph osd pool create test-crush 8 8 replicated_rule_rack

 

# 写入测试对象并查看实际落点

rados -p test-crush put testobj /etc/hosts

ceph osd map test-crush testobj

# 输出形如:

# test-crush object testobj -> pg 3.xxx (3.0) -> up [2,1,0] acting [2,1,0]

# 这里的 [2,1,0] 应来自 3 个不同的 rack

 

# 用 crushtool 做大规模模拟(更直观的分布验证)

crushtool -i /tmp/crushmap-compiled.bin –test –num-rep 3

–min-x 0 –max-x 1024 –output /tmp/crush-test-out.json 2>/dev/null

# 输出 json 中每个 OSD 承载的对象数应接近均衡

 

# 清理测试池

ceph osd pool delete test-crush test-crush –yes-i-really-really-mean-it

配置参数详解:CRUSH 相关关键参数

CRUSH 行为受多个全局 tunable 和 per-pool 参数控制。下面列出生产环境最需要关注的几项:

参数 默认值 说明 生产建议
osd_pool_default_crush_replicated_ruleset 0 新建副本池默认使用的 rule id 指向自定义 rack/DC rule
osd_crush_choose_leaf_type 1 (host) chooseleaf 默认故障域类型 跨机架设为 3 (rack)
osd_crush_update_on_start true OSD 启动时自动更新 CRUSH 权重 保持 true,但注意其会重置自定义权重
mon_max_mdsmap_epochs 500 与 CRUSH 无直接关系,但影响重平衡速度 —
osd_pool_default_size 3 默认副本数 生产最低 3
osd_pool_default_min_size 2 降级写入下限 推荐 size-1
mon_cluster_def_pool_default_crush_rule 0 全集群默认 rule 跨机架部署建议改为 rack rule

设置示例:

💻 代码示例

# 设置全集群默认使用基于 rack 的副本规则(避免新池误用 host 故障域)

ceph config set global osd_pool_default_crush_replicated_ruleset 1

 

# 确认

ceph config get mon osd_pool_default_crush_replicated_ruleset

# 输出: 1

此外,CRUSH Bucket 的 alg(算法)和 hash(哈希函数)也需关注。straw2 是 Squid 的默认且推荐算法,相比 straw 修正了权重变化时的数据迁移问题,生产环境切勿改回旧算法。hash 0 表示使用 rjenkins 哈希,这是唯一支持的值。

生产环境注意事项与踩坑提示

1. 切勿在生产环境直接手动编译注入 CRUSH Map

手动 crushtool -c 编译 + ceph osd setcrushmap 注入会绕过所有安全检查,一旦层级写错会导致大规模数据迁移甚至 PG 不可用。生产推荐使用 ceph osd crush 系列 CLI 子命令逐步调整,每步都可回滚。

2. 权重单位是 TiB,不是 GB

ceph osd crush add 和 move 的 weight 参数单位是 TiB(1024 GiB)。常见错误是把 9TB 盘写成 9.0 实际应是 0.008(9TiB = 9.0 即可,但若误用 GB 会差 10%)。生产建议始终使用 ceph osd crush add ... 由 cephadm 自动计算,不手动指定。

3. 跨机架故障域必须保证机架数 ≥ 副本数

如果设置了 chooseleaf type rack 但只有 2 个机架,3 副本将无法分布,Ceph 会触发 pool has insufficient devices 告警并拒绝创建。扩容到足够机架前,先用 host 故障域。

4. CRUSH Map 变更会触发数据重平衡

任何层级调整(add-bucket / move)都会让 CRUSH 重新计算所有 PG 的目标位置,触发数据迁移。生产环境务必在业务低峰期执行,并配合 osd_max_backfills、osd_recovery_max_active 限速:

💻 代码示例

# 临时降低重平衡速度,保护业务 IOPS

ceph config set osd osd_max_backfills 1

ceph config set osd osd_recovery_max_active 1

# 完成后恢复(默认 3 / 3)

ceph config rm osd osd_max_backfills

ceph config rm osd osd_recovery_max_active

5. 设备类(device class)与 CRUSH 层级的关系

Squid 版本默认启用 device class(hdd/ssd/nvme 自动分类)。device class 会在 CRUSH Map 中自动创建 shadow root(如 ~hdd),与自定义 rack/host 层级是正交的。如同时使用两者,确保 pool 的 rule 选择正确,否则可能副本全落同一物理盘类型。

6. 安全考量

CRUSH Map 描述了集群物理拓扑,包含主机名、机架编号等基础设施信息。生产环境应限制 ceph osd crush dump 的访问权限,仅授予运维 admin 角色,避免通过普通应用客户端枚举集群拓扑后进行定向攻击。

K8s Logo

常见问题(FAQ)

Q1:修改 CRUSH Map 后,已有数据需要手动迁移吗?

A:不需要。CRUSH 是惰性计算的,PG 的 up/acting 集合在下次 PG peering 时按新 Map 重新计算。Ceph 会自动触发数据迁移(reweight/backfill)使实际分布与新规则一致。但要注意迁移过程占用网络和磁盘 IO,需限速。

Q2:跨机房灾备时,CRUSH Rule 应该如何配置?

A:在多机房场景下,先在 CRUSH Map 中建立 datacenter 层级,把每个机房的主机归到对应 DC 下,然后创建规则 chooseleaf type datacenter。3 副本跨 3 机房时,单个机房整体故障仍可读写(前提是 min_size 设为 2)。若要支持机房级读亲和性,可配合 CRUSH chooseleaf 的 locality tunable 或使用 stretch cluster 特性。

Q3:straw2 和 straw 有什么区别,能否混用?

A:straw2 修正了 straw 在节点权重变化时数据迁移量过大的缺陷,是 Squid 的默认且唯一推荐算法。同一 CRUSH Map 中所有 bucket 应统一使用 straw2,混用会导致分布不均和不可预期的迁移。旧版升级到 Squid 时会自动迁移,无需手动干预。

总结

本篇深入解析了 Ceph CRUSH Map 的架构原理与生产环境故障域规划实战。核心要点回顾:

  1. CRUSH 是去中心化的伪随机分布算法,让客户端无需元数据服务器即可定位对象位置
  2. CRUSH Map 四大组成:devices / bucket types / buckets / rules,构成完整拓扑与规则
  3. 故障域类型决定数据可靠性边界,生产环境应至少做到 host 级别,多机架/机房部署应做到 rack/datacenter 级别
  4. 生产环境操作 CRUSH Map 必须用 CLI 子命令,避免手动编译注入;任何变更都伴随数据迁移,需限速并选低峰期
  5. straw2 是推荐算法,全集群应统一使用,切勿回退旧算法

理解并正确规划 CRUSH Map,是构建高可靠 Ceph 生产集群的关键基础。下一篇我们将进入存储池(Pool)的创建与 PG 数量规划,把 CRUSH 故障域策略落实到具体的存储池配置上。

下期预告

Ceph 生产环境搭建 | 第 8 天:存储池(Pool)创建与 PG 数量规划 —— 将讲解 PG/PGP 的计算原理、pool 配置参数与生产环境的 PG 数量规划公式。

系列目录

微信公众号二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像 - 恒星
欢迎您留下宝贵的见解!
提交
头像 - 恒星

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容