CRUSH 算法通过计算数据存储位置来确定如何存储和检索数据。使用 CRUSH,Ceph 客户端无需通过中心服务器或中介程序,即可直接与 OSD 通讯。借助算法确定的数据存储和检索方法,Ceph 可避免单一故障点、性能瓶颈和可伸缩性物理限制。
CRUSH 需要获取集群的地图,它使用 CRUSH 地图以伪随机的方式在 OSD 中存储和检索数据,并以一致的方式在整个集群中分布数据。
CRUSH 地图包含一个 OSD 列表、一个用于将设备聚合到物理位置的“桶”列表,以及一个告知 CRUSH 应如何在 Ceph 集群存储池中复制数据的规则列表。通过反映安装的底层物理组织,CRUSH 可对相关设备故障的潜在根源建模,从而解决故障的根源。典型的根源包括物理接近、共用电源和共用网络。通过将这些信息编码到集群地图中,CRUSH 归置策略可将对象副本分隔在不同的故障域中,同时维持所需的分布方式。例如,为了消除可能的并发故障,可能需要确保数据副本位于使用不同机架、机柜、电源、控制器和/或物理位置的设备上。
部署 Ceph 集群后,将会生成默认的 CRUSH 地图。这种模式适合 Ceph 沙箱环境。但是,在部署大规模的数据集群时,强烈建议您考虑创建自定义 CRUSH 地图,因为这样做有助于管理 Ceph 集群、提高性能并确保数据安全。
例如,如果某个 OSD 停机,而您需要使用现场支持或更换硬件,则 CRUSH 地图可帮助您定位到发生 OSD 故障的主机所在的物理数据中心、机房、设备排和机柜。
同样,CRUSH 可以帮助您更快地确定故障。例如,如果特定机柜中的所有 OSD 同时停机,故障可能是由某个网络交换机或者机柜或网络交换机的电源所致,而不是发生在 OSD 自身上。
当与故障主机关联的归置组处于降级状态时,自定义 CRUSH 地图还可帮助您确定 Ceph 存储数据冗余副本的物理位置。
CRUSH 地图包括三个主要部分。
为了将归置组映射到 OSD,CRUSH 地图需要 OSD 设备(OSD 守护进程的名称)的列表。设备列表显示在 CRUSH 地图的最前面。
#devices device num osd.name
例如:
#devices device 0 osd.0 device 1 osd.1 device 2 osd.2 device 3 osd.3
一般而言,一个 OSD 守护进程映射到一个磁盘。
CRUSH 地图包含 OSD 的列表,可将这些 OSD 组织成“桶”,以便将设备聚合到物理位置。
0 |
OSD |
OSD 守护进程(osd.1、osd.2 等)。 |
1 |
主机 |
包含一个或多个 OSD 的主机名。 |
2 |
机箱 |
组成机柜的机箱。 |
3 |
机柜 |
计算机机柜。默认值为 |
4 |
设备排 |
由一系列机柜组成的设备排。 |
5 |
Pdu |
电源分配单元。 |
6 |
Pod | |
7 |
机房 |
包含主机机柜和主机排的机房。 |
8 |
数据中心 |
包含机房的物理数据中心。 |
9 |
地区 | |
10 |
根 |
可以删除这些类型,并创建自己的桶类型。
Ceph 的部署工具可生成 CRUSH 地图,其中包含每个主机的桶,以及名为“default”的存储池(可用于默认的 rbd
池)。剩余的桶类型提供了一种存储有关节点/桶的物理位置信息的方法,当 OSD、主机或网络硬件发生故障,并且管理员需要访问物理硬件时,这种方法可大大简化集群管理工作。
桶具有类型、唯一的名称(字符串)、以负整数表示的唯一 ID、相对于其项目的总容量/功能的权重、桶算法(默认为 straw
)和哈希(默认为 0
,反映 CRUSH 哈希 rjenkins1
)。一个桶可以包含一个或多个项目。项目可由其他桶或 OSD 组成。项目可能会有一个权重来反映该项目的相对权重。
[bucket-type] [bucket-name] { id [a unique negative numeric ID] weight [the relative capacity/capability of the item(s)] alg [the bucket type: uniform | list | tree | straw ] hash [the hash type: 0 by default] item [item-name] weight [weight] }
下面的示例说明如何使用桶来聚合存储池,以及诸如数据中心、机房、机柜和设备排的物理位置。
host ceph-osd-server-1 { id -17 alg straw hash 0 item osd.0 weight 1.00 item osd.1 weight 1.00 } row rack-1-row-1 { id -16 alg straw hash 0 item ceph-osd-server-1 weight 2.00 } rack rack-3 { id -15 alg straw hash 0 item rack-3-row-1 weight 2.00 item rack-3-row-2 weight 2.00 item rack-3-row-3 weight 2.00 item rack-3-row-4 weight 2.00 item rack-3-row-5 weight 2.00 } rack rack-2 { id -14 alg straw hash 0 item rack-2-row-1 weight 2.00 item rack-2-row-2 weight 2.00 item rack-2-row-3 weight 2.00 item rack-2-row-4 weight 2.00 item rack-2-row-5 weight 2.00 } rack rack-1 { id -13 alg straw hash 0 item rack-1-row-1 weight 2.00 item rack-1-row-2 weight 2.00 item rack-1-row-3 weight 2.00 item rack-1-row-4 weight 2.00 item rack-1-row-5 weight 2.00 } room server-room-1 { id -12 alg straw hash 0 item rack-1 weight 10.00 item rack-2 weight 10.00 item rack-3 weight 10.00 } datacenter dc-1 { id -11 alg straw hash 0 item server-room-1 weight 30.00 item server-room-2 weight 30.00 } pool data { id -10 alg straw hash 0 item dc-1 weight 60.00 item dc-2 weight 60.00 }
CRUSH 地图支持“CRUSH 规则”概念,这些规则确定存储池的数据归置。对于大型集群,您可能会创建许多存储池,其中每个存储池各自可能具有自己的 CRUSH 规则组和规则。默认的 CRUSH 地图对每个存储池使用一个规则,并为每个默认存储池指定一个规则组。
大多数情况下,无需修改默认规则。创建新存储池时,该存储池的默认规则组为 0。
规则采用以下格式:
rule rulename { ruleset ruleset type type min_size min-size max_size max-size step step }
一个整数。将规则分类,使其属于一个规则组。通过在存储池中设置规则组来激活。必须指定此选项。默认值是 0
。
需要从默认值 0 开始连续递增规则组编号,否则相关的监视器可能会崩溃。
一个字符串。描述硬盘 (replicated) 或 RAID 的规则。必须指定此选项。默认值为 replicated
。
一个整数。如果归置组创建的副本数小于此数字,CRUSH 将不选择此规则。必须指定此选项。默认值是 2
。
一个整数。如果归置组创建的副本数大于此数字,CRUSH 将不选择此规则。必须指定此选项。默认值是 10
。
采用某个桶名称,并开始在树中向下迭代。必须指定此选项。有关在树中迭代的说明,请参见第 6.3.1 节 “在节点树中迭代”。
target 可以是 choose
或 chooseleaf
。如果设置为 choose
,则会选择许多桶。chooseleaf
直接从桶集的每个桶的子树中选择 OSD(叶节点)。
mode 可以是 firstn
或 indep
。请参见第 6.3.2 节 “firstn 和 indep”。
选择给定类型的桶的数量。其中,N 是可用选项的数量,如果 num > 0 且 < N,则选择该数量的桶;如果 num < 0,则表示 N - num;如果 num == 0,则选择 N 个桶(全部可用)。跟在 step take
或 step choose
后使用。
输出当前值并清空堆栈。通常在规则的末尾使用,但也可在同一规则中用来构成不同的树。跟在 step choose
后使用。
要将一个或多个具有共同规则组编号的规则构成某个存储池,请将规则组编号设置为该存储池。
可采用节点树的形式来查看使用桶定义的结构。在此树中,桶是节点,OSD 是叶。
CRUSH 地图中的规则定义如何从此树中选择 OSD。规则从某个节点开始,然后在树中向下迭代,以返回一组 OSD。无法定义需要选择哪个分支。CRUSH 算法可确保 OSD 集能够满足复制要求并均衡分布数据。
使用 step take
bucket 时,节点树中的迭代从给定的桶(而不是桶类型)开始。如果要返回树中所有分支上的 OSD,该桶必须是根桶。否则,以下步骤只会在子树中迭代。
完成 step take
后,接下来会执行规则定义中的一个或多个 step choose
项。每个 step choose
项从前面选定的上层节点中选择定义数量的节点(或分支)。
最后,使用 step emit
返回选定的 OSD。
step chooseleaf
是一个便捷函数,可直接从给定桶的分支中选择 OSD。
图 6.1 “示例树”中提供了说明如何使用 step
在树中迭代的示例。在下面的规则定义中,橙色箭头和数字与 example1a
和 example1b
对应,蓝色箭头和数字与 example2
对应。
# orange arrows rule example1a { ruleset 0 type replicated min_size 2 max_size 10 # orange (1) step take rack1 # orange (2) step choose firstn 0 host # orange (3) step choose firstn 1 osd step emit } rule example1b { ruleset 0 type replicated min_size 2 max_size 10 # orange (1) step take rack1 # orange (2) + (3) step chooseleaf firstn 0 host step emit } # blue arrows rule example2 { ruleset 0 type replicated min_size 2 max_size 10 # blue (1) step take room1 # blue (2) step chooseleaf firstn 0 rack step emit }
CRUSH 规则定义有故障节点或 OSD 的替换项(请参见第 6.3 节 “规则组”)。关键字 step
要求使用 firstn
或 indep
参数。图 6.2 “节点替换方法”提供了示例。
firstn
将替换节点添加到活动节点列表的末尾。如果某个节点发生故障,其后的正常节点会移位到左侧,以填充有故障节点留下的空缺。这是副本池的默认方法,也是需要采取的方法,因为次要节点已包含所有数据,因此可立即接管主要节点的职责。
indep
为每个活动节点选择固定的替换节点。替换有故障节点不会更改剩余节点的顺序。这对于纠删码池而言是所需的行为。在纠删码池中,节点上存储的数据取决于在选择节点时它所在的位置。如果节点的顺序发生变化,受影响节点上的所有数据都需要重新放置。
确保针对每个纠删码池设置使用 indep
的规则。
本节介绍基本的 CRUSH 地图操作方法,例如编辑 CRUSH 地图、更改 CRUSH 地图参数,以及添加/移动/删除 OSD。
要编辑现有的 CRUSH 地图,请执行以下操作:
获取 CRUSH 地图。要获取集群的 CRUSH 地图,请执行以下命令:
root #
ceph osd getcrushmap -o compiled-crushmap-filename
Ceph 会将编译的 CRUSH 地图输出 (-o
) 到您指定名称的文件。由于该 CRUSH 地图采用编译格式,您必须先将其反编译,然后才能对其进行编辑。
反编译 CRUSH 地图。要反编译 CRUSH 地图,请执行以下命令:
cephadm >
crushtool -d compiled-crushmap-filename \
-o decompiled-crushmap-filename
Ceph 将对已编译的 CRUSH 地图进行反编译 (-d
),并将其输出 (-o
) 到您指定名称的文件。
至少编辑“设备”、“桶”和“规则”中的其中一个参数。
编译 CRUSH 地图。要编译 CRUSH 地图,请执行以下命令:
cephadm >
crushtool -c decompiled-crush-map-filename \
-o compiled-crush-map-filename
Ceph 会将编译的 CRUSH 地图存储到您指定名称的文件。
设置 CRUSH 地图。要设置集群的 CRUSH 地图,请执行以下命令:
root #
ceph osd setcrushmap -i compiled-crushmap-filename
Ceph 将输入您所指定文件名的已编译 CRUSH 地图,作为集群的 CRUSH 地图。
要在运行中集群的 CRUSH 地图中添加或移动 OSD,请执行以下命令:
root #
ceph osd crush set id_or_name weight root=pool-name
bucket-type=bucket-name ...
一个整数。OSD 的数字 ID。必须指定此选项。
一个字符串。OSD 的全名。必须指定此选项。
一个双精度值。OSD 的 CRUSH 权重。必须指定此选项。
一个键/值对。默认情况下,CRUSH 层次结构包含 default 存储池作为根。必须指定此选项。
键/值对。可在 CRUSH 层次结构中指定 OSD 的位置。
下面的示例将 osd.0
添加到层次结构,或移动之前某个位置的 OSD。
root #
ceph osd crush set osd.0 1.0 root=data datacenter=dc1 room=room1 \
row=foo rack=bar host=foo-bar-1
要在运行中集群的 CRUSH 地图中调整 OSD 的 CRUSH 权重,请执行以下命令:
root #
ceph osd crush reweight name weight
一个字符串。OSD 的全名。必须指定此选项。
一个双精度值。OSD 的 CRUSH 权重。必须指定此选项。
要从运行中集群的 CRUSH 地图中删除 OSD,请执行以下命令:
root #
ceph osd crush remove name
一个字符串。OSD 的全名。必须指定此选项。
要将某个桶移到 CRUSH 地图层次结构中的不同位置,请执行以下命令:
root #
ceph osd crush move bucket-name bucket-type=bucket-name, ...
一个字符串。要移动/重新定位的桶的名称。必须指定此选项。
键/值对。可在 CRUSH 层次结构中指定桶的位置。
除了复制对象的多个副本外,Ceph 还需通过整理 (scrub) 归置组来确保数据完整性。Ceph 的整理 (scrub) 类似于在对象存储层运行 fsck
。对于每个归置组,Ceph 都会生成一个包含所有对象的编目,并比较每个主对象及其副本,以确保不会有缺失或不匹配的对象。每天的浅层整理 (light scrub) 会检查对象大小和属性,而每周的深层整理 (deep scrub) 则会读取数据并使用校验和来确保数据完整性。
整理 (scrub) 对于维护数据完整性非常重要,但该操作可能会降低性能。您可以通过调整以下设置来增加或减少整理 (scrub) 操作:
osd max scrubs
针对 Ceph OSD 同时执行的最大整理 (scrub) 操作数量。默认值是 1。
osd scrub begin hour
、osd scrub end hour
按小时定义一天内可以执行整理 (scrub) 的时间段(0 到 24)。默认开始时间为 0,结束时间为 24。
如果归置组的整理 (scrub) 间隔超出 osd scrub max interval
设置的值,则无论您定义的整理 (scrub) 时间段为何,都将执行整理 (scrub)。
osd scrub during recovery
允许恢复期间执行整理 (scrub)。如果将此选项设置为“false”,则当存在活动的恢复进程时,将禁止安排新的整理 (scrub)。已在运行的整理 (scrub) 将继续执行。此选项有助于降低忙碌集群上的负载。默认值为“true”。
osd scrub thread timeout
整理 (scrub) 线程超时前的最长时间(以秒为单位)。默认值是 60。
osd scrub finalize thread timeout
整理 (scrub) 完成线程超时前的最长时间(以秒为单位)。默认值为 60*10。
osd scrub load threshold
规范化的最大负载。当系统负载(由 getloadavg()
与 online cpus
数量之比定义)高于此数字时,Ceph 将不会执行整理 (scrub)。默认值是 0.5。
osd scrub min interval
当 Ceph 集群负载较低时整理 (scrub) Ceph OSD 的最短间隔(以秒为单位)。默认值为 60*60*24(一天一次)。
osd scrub max interval
无论集群负载如何都整理 (scrub) Ceph OSD 的最长间隔(以秒为单位)。7*60*60*24(一周一次)。
osd scrub chunk min
单个操作期间要整理 (scrub) 的对象存储块的最小数量。整理 (scrub) 期间,Ceph 会阻止向单个块写入数据。默认值是 5。
osd scrub chunk max
单个操作期间要整理 (scrub) 的对象存储块的最大数量。默认值是 25。
osd scrub sleep
整理 (scrub) 下一组存储块之前休眠的时间。增大此值会降低整个整理 (scrub) 操作的速度,但对客户端操作的影响较小。默认值是 0。
osd deep scrub interval
深层整理 (deep scrub)(完整读取所有数据)的间隔。osd scrub load threshold
选项不会影响此设置。默认值为 60*60*24*7(一周一次)。
osd scrub interval randomize ratio
在安排归置组的下一次整理 (scrub) 作业时,为 osd scrub min interval
值增加一个随机延迟。该延迟为一个随机的值,小于 osd scrub min interval
* osd scrub interval randomized ratio
所得结果。因此,该默认设置实际上是将整理 (scrub) 随机地安排在允许的时间段 [1, 1.5] * osd scrub min interval
内执行。默认值为 0.5
osd deep scrub stride
执行深层整理 (deep scrub) 时读取的大小。默认值为 524288 (512 kB)。
有时,用户可能需要配置这样一个 Ceph 集群:在每个节点上混用 SSD 和 HDD,并将一个存储池放在速度较快的 SSD 上,将另一个存储池放在速度较慢的 HDD 上。要实现此目的,需要编辑 CRUSH 地图。
默认的 CRUSH 地图采用简单的层次结构,其中,默认根包含主机,而主机包含 OSD,例如:
root #
ceph osd tree
ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF
-1 83.17899 root default
-4 23.86200 host cpach
2 hdd 1.81898 osd.2 up 1.00000 1.00000
3 hdd 1.81898 osd.3 up 1.00000 1.00000
4 hdd 1.81898 osd.4 up 1.00000 1.00000
5 hdd 1.81898 osd.5 up 1.00000 1.00000
6 hdd 1.81898 osd.6 up 1.00000 1.00000
7 hdd 1.81898 osd.7 up 1.00000 1.00000
8 hdd 1.81898 osd.8 up 1.00000 1.00000
15 hdd 1.81898 osd.15 up 1.00000 1.00000
10 nvme 0.93100 osd.10 up 1.00000 1.00000
0 ssd 0.93100 osd.0 up 1.00000 1.00000
9 ssd 0.93100 osd.9 up 1.00000 1.00000
这样,就无法区分磁盘类型。要将 OSD 分为 SSD 和 HDD,需在 CRUSH 地图中创建另一个层次结构:
root #
ceph osd crush add-bucket ssd root
为 SSD 创建新根后,需在此根中添加主机。这意味着需要创建新的主机项。但是,由于同一个主机名不能在 CRUSH 地图中出现多次,此处使用了虚设的主机名。这些虚设的主机名无需由 DNS 解析。CRUSH 不关心主机名是什么,只需创建适当的层次结构即可。要支持虚设的主机名,真正需要进行的一项更改就是必须设置
osd crush update on start = false
(在 /srv/salt/ceph/configuration/files/ceph.conf.d/global.conf
文件中),然后运行 DeepSea 阶段 3 以分发该项更改(有关详细信息,请参见第 1.11 节 “自定义 ceph.conf
文件”):
root@master #
salt-run state.orch ceph.stage.3
否则,您移动的 OSD 稍后将重设置到其在默认根中的原始位置,并且集群不会按预期方式工作。
更改该设置后,请将新的虚设主机添加到 SSD 的根中:
root #
ceph osd crush add-bucket node1-ssd hostroot #
ceph osd crush move node1-ssd root=ssdroot #
ceph osd crush add-bucket node2-ssd hostroot #
ceph osd crush move node2-ssd root=ssdroot #
ceph osd crush add-bucket node3-ssd hostroot #
ceph osd crush move node3-ssd root=ssd
最后,针对每个 SSD OSD,将 OSD 移到 SSD 的根中。在本示例中,我们假设 osd.0、osd.1 和 osd.2 实际托管在 SSD 上:
root #
ceph osd crush add osd.0 1 root=ssdroot #
ceph osd crush set osd.0 1 root=ssd host=node1-ssdroot #
ceph osd crush add osd.1 1 root=ssdroot #
ceph osd crush set osd.1 1 root=ssd host=node2-ssdroot #
ceph osd crush add osd.2 1 root=ssdroot #
ceph osd crush set osd.2 1 root=ssd host=node3-ssd
CRUSH 层次结构现在应类似下方所示:
root #
ceph osd tree
ID WEIGHT TYPE NAME UP/DOWN REWEIGHT PRIMARY-AFFINITY
-5 3.00000 root ssd
-6 1.00000 host node1-ssd
0 1.00000 osd.0 up 1.00000 1.00000
-7 1.00000 host node2-ssd
1 1.00000 osd.1 up 1.00000 1.00000
-8 1.00000 host node3-ssd
2 1.00000 osd.2 up 1.00000 1.00000
-1 0.11096 root default
-2 0.03699 host node1
3 0.01849 osd.3 up 1.00000 1.00000
6 0.01849 osd.6 up 1.00000 1.00000
-3 0.03699 host node2
4 0.01849 osd.4 up 1.00000 1.00000
7 0.01849 osd.7 up 1.00000 1.00000
-4 0.03699 host node3
5 0.01849 osd.5 up 1.00000 1.00000
8 0.01849 osd.8 up 1.00000 1.00000
现在,创建一个针对 SSD 根的 CRUSH 规则:
root #
ceph osd crush rule create-simple ssd_replicated_ruleset ssd host
原始默认规则组 replicated_ruleset
(ID 为 0)针对的是 HDD。新规则组 ssd_replicated_ruleset
(ID 为 1)针对的是 SSD。
所有现有存储池仍会使用 HDD,因为它们位于 CRUSH 地图的默认层次结构中。可以创建一个仅使用 SSD 的新存储池:
root #
ceph osd pool create ssd-pool 64 64root #
ceph osd pool set ssd-pool crush_rule ssd_replicated_ruleset