背景
在当今云原生时代,OpenStack 与 Ceph 的组合已成为私有云和混合云场景下的 "黄金搭档"。Ceph 作为统一的分布式存储平台,为 OpenStack 提供了块存储(Cinder)、镜像存储(Glance)解决方案。在 OpenStack + Ceph 架构中,如何在保证高可用的前提下进一步提升存储利用率?答案就是:RBD + 纠删码(Erasure Coding, EC)。本文结合实际改造经验,完整梳理 OpenStack 适配 Ceph 纠删码的设计思路、实现方式与关键改造点。
Ceph rbd介绍
随着云计算的发展,Ceph已经成为目前最为流行的分布式存储系统,俨然存储界的Linux操作系统。Ceph集块存储、文件存储和对象存储于一身,适用场景广泛,用户众多。RBD是 Ceph 分布式存储系统中提供的块存储服务,Ceph的块存储通过一个客户端模块实现,这个客户端可以直接从数据守护进程读写数据(不需要经过一个网关)。根据客户端整合生态系统的差异,使用Ceph的块设备有两种实现方式:librbd (用户态)和krbd (内核态)。

使用Ceph的块设备有两种路径(内核态与用户态):(rbd map就是内核使用ceph块设备,调用librbd/librados API访问ceph块设备是用户态)
-
通过Kernel Module(内核态RBD):即创建了RBD设备后,把它映射到内核中(使用rbd map命令映射到操作系统上),成为一个虚拟的块设备,这时这个块设备同其他通用块设备一样,设备文件一般为/dev/rbd0,后续直接使用这个块设备文件就可以了,可以把/dev/rbd0格式化后挂载到某目录,也可以直接作为裸设备进行使用。krbd是一个内核模块。其在内核中以一个块设备的方式加以实现。这整个Ceph客户端都是以内核模块的方式实现(没有与之相关的用户态进程或者守护进程)。krbd在内核的源码目录源文件:drivers/block/rbd.c、drivers/block/rbd_types.h、net/ceph/、include/linux/ceph
-
通过librbd(用户态RBD):即创建了RBD设备后,使用librbd/librados库访问和管理块设备。这种方式直接调用librbd提供的接口,实现对RBD设备的访问和管理,不会在客户端产生设备文件,这种方式主要是为虚拟机提供块存储设备,在虚拟机场景中,一般会用QEMU/KVM中的RBD驱动部署Ceph块设备,宿主机通过librbd向客户端提供块存储服务。应用方案有:SPDK+librbd/librados
RBD 的块设备由于元数据信息少而且访问不频繁,故 RBD 在 Ceph 集群中不需要单独的守护进程将元数据加载到内存进行元数据访问加速,所有的元数据和数据操作直接与集群中的 Monitor 服务和 OSD 服务进行交互。
Ceph数据冗余策略:多副本与纠删码
多副本策略 是Ceph默认采用的机制。其核心是为同一份数据创建多个完全相同的副本,并分散存储于集群的不同节点或硬盘上。例如,在常见的3副本配置中,一份数据会被复制成三份,随机存放于三个独立的物理位置。这种策略的冗余能力直接明了:N副本可以容忍N-1个存储单元(节点或硬盘)同时故障,数据依然可用。它的主要优势在于实现简单,数据恢复时无需复杂计算,直接读取完整副本即可,因此性能优异、延迟低。然而,其代价是存储空间利用率较低,3副本的有效利用率仅为33%,即存储1TB用户数据需要消耗3TB的物理空间。

纠删码策略 则采用了一种更为复杂的数学保护方法。它将原始数据分割成 k 个数据块,并通过算法(如矩阵计算)生成 m 个校验块。这些数据块和校验块(共 k+m 个)随后被分布存储到不同节点。当部分块丢失(故障节点数不超过 m)时,系统可以利用剩余块通过计算重建出丢失的数据。例如,一个 4+2 的纠删码方案将数据切成4份并生成2份校验数据,总计6份存储在6个节点,可容忍任意2个节点故障。纠删码的核心优势在于极高的存储空间效率。同样能容忍两个故障,4+2 方案的利用率约为66%,远高于3副本的33%。配置如 10+4 时,利用率可达71%以上,能容忍4个故障而存储开销仅为40%。但这是以计算开销为代价的:数据的写入、重建和恢复过程需要进行大量编解码运算,尤其在HDD环境或大规模恢复时,性能开销显著,延迟通常高于多副本。

RBD与纠删码存储方案
在 Ceph 环境中,RBD(块存储)使用纠删码(EC, Erasure Coding)模式,本质上是 "元数据放副本池、数据放纠删码池" 的混合方案。这是目前唯一被官方支持、也是生产可用的 RBD + EC 方式。
Ceph RBD(块设备)与纠删码结合使用的核心,在于采用了一种元数据与数据分离存储的混合架构。这种设计的目的是为了在享受纠删码高存储效率的同时,规避其对频繁小IO操作性能不佳的缺点,从而兼顾性能与成本。
具体而言,系统会创建两个不同类型的存储池:一个用于存储元数据的副本池,和一个用于存储实际块数据的纠删码池。RBD镜像的元数据(包括记录镜像大小、特性的 rbd_header 对象、逻辑块到物理对象的映射关系以及快照克隆的父子依赖)全部存放在副本池中。这是因为元数据操作(如创建快照、更新映射)具有低延迟、强一致性和高频小IO的特性,副本池的模型能为此提供最佳的性能和可靠性保障,同时避免了纠删码带来的"写放大"效应。反之,镜像的主体数据块则经过纠删码编码后,分片存储于纠删码池中。这主要是看中了纠删码极高的存储空间利用率,它能够以远低于多副本的冗余开销(例如 4+2 配置仅需1.5倍空间)提供数据保护,尤其适合处理块设备典型的大块顺序IO。
在这样的架构下,I/O路径也相应进行了划分。写入数据时,客户端首先访问副本池中的元数据来确定位置,然后将数据编码并分片写入纠删码池。读取时,则先从副本池获取映射,再从纠删码池读取并解码数据分片。特别值得一提的是克隆操作的实现,它巧妙利用了这种分离结构。在元数据层,副本池仅需记录克隆的引用关系,开销极小;在数据层,纠删码池则执行实际的写时复制(COW)机制------克隆初始时共享父镜像的数据块,仅在首次修改时,才触发将父数据块拷贝至子镜像专属空间的操作,且拷贝后的数据依然以纠删码形式存储。这种设计使得快速创建克隆成为可能,而将较大的数据搬运成本延迟并分散到后续的写入过程中。
RBD 纠删码块设备操作命令
1、添加块设备
1、rbd pool init poolname //命令用于初始化池供rbd使用,poolname是指存储池名称
2、rbd create --size %s --image-feature layering poolname/name --data-pool datapoolname
//在存储池上创建一个空的映像
//%s指的是大小,poolname为存储池名称,name为要创建的映像的名称,datapoolname为要创建的影响的存储池的名称
//--image-feature layering作用是创建映像时启用的rbd特性为layering
2、扩容块设备
rbd resize --size %s%s %s/%s
//%s分别是大小、单位、存储池名称和块设备名称
3、重命名块设备
rbd mv %s/%s %s/%s
//%s分别是存储池名称和旧块设备名称,存储池名称和新块设备名称
4、断开关系链
rbd flatten %s/%s //%s分别为存储池名称和clone名称
//作用是使clone独立,使其成为一个独立的块设备
5、删除块设备
rados -p %s rm rbd_header.%s
//删除块设备信息
//分别是 存储池名称 和 块设备id,通过 "rbd info 存储池名称/块设备名称"命令可以读取
rados -p %s rm rbd_id.%s
//删除块设备信息
//分别是 存储池名称 和 块设备名称
rbd rm %s/ %s //%s
//分别是存储池名称和块设备名称
6、创建快照
rbd snap create %s/%s@%s
//%s分别为存储池名称、块设备名称和快照名称
7、克隆快照
1、防止快照被删除
rbd snap protect %s
//%s为快照名称
2、克隆快照
rbd clone %s %s/%s --data-pool %s
//%s分别为快照名称、存储池名称、克隆镜像名称和克隆镜像的数据池名称
8、快照回滚
rbd snap rollback %s/%s@%s
//%s分别为存储池名称、块设备名称和快照名称
//回滚默认会连容量一起回滚,如果需要原先的容量,请重新resize
9、删除快照
1、取消保护
rbd snap unprotect %s/%s@%s//%s分别为存储池名称、块设备名称和快照名称
2、删除快照
rbd snap rm %s/%s@%s//%s分别为存储池名称、块设备名称和快照名称
OpenStack使用Ceph纠删码rbd块
在openstack中云盘和镜像数据存储在ceph中,涉及到nova和cinder组件,但原生nova和cinder在设计时,主要面向的是传统的副本型RBD池。它们缺乏对纠删码RBD池原生、开箱即用的支持,,因此需要进行适配。
Nova侧
配置文件修改(nova-compute)
[libvirt]
rbd_user = admin
rbd_secret_uuid = 1647e105-d9d7-49cd-9d1c-84e30e74c088
erasure_ceph_fsid =
erasure_rbd_meta_pool = meta
erasure_rbd_data_pool = data
代码修改(nova/virt/libvirt/storage/rbd_utils.py)
def clone(self, image_location, dest_name, dest_pool=None):
_fsid, pool, image, snapshot = self.parse_url(image_location['url'])
if _fsid == CONF.libvirt.erasure_ceph_fsid and \
dest_pool == CONF.libvirt.erasure_rbd_meta_pool:
LOG.debug("Using erasure coding features for cloning")
features = (rbd.RBD_FEATURE_LAYERING | rbd.RBD_FEATURE_DATA_POOL)
RbdProxy().clone(src_client.ioctx,
image,
snapshot,
dest_client.ioctx,
str(dest_name),
features=features,
data_pool=CONF.libvirt.erasure_rbd_data_pool)
else:
RbdProxy().clone(src_client.ioctx,
image,
snapshot,
dest_client.ioctx,
str(dest_name),
features=src_client.features)
Cinder侧
配置文件修改(cinder-volume)
[ceph]
rbd_type = erasure
erasure_data_pool = data
代码修改(cinder/volume/drivers/rbd.py)
def clone(self, image_location, dest_name, dest_pool=None):
_fsid, pool, image, snapshot = self.parse_url(image_location['url'])
if _fsid == CONF.libvirt.erasure_ceph_fsid and \
dest_pool == CONF.libvirt.erasure_rbd_meta_pool:
LOG.debug("Using erasure coding features for cloning")
features = (rbd.RBD_FEATURE_LAYERING | rbd.RBD_FEATURE_DATA_POOL)
RbdProxy().clone(src_client.ioctx,
image,
snapshot,
dest_client.ioctx,
str(dest_name),
features=features,
data_pool=CONF.libvirt.erasure_rbd_data_pool)
else:
RbdProxy().clone(src_client.ioctx,
image,
snapshot,
dest_client.ioctx,
str(dest_name),
features=src_client.features)
总结
OpenStack采用Ceph纠删码RBD的混合存储架构,实现了性能与效率的卓越平衡。该架构将元数据存储于副本池,保障快照、克隆等关键操作的低延迟和高可靠性;同时将块数据存储于纠删码池,利用其高空间效率特性,相比传统3副本可节省50%的存储成本。这种分离设计使云平台能够兼顾高性能操作与大规模数据存储的经济性,支持根据数据库、通用计算、冷备份等不同负载类型灵活配置EC策略。通过智能的分级存储管理,OpenStack可在统一存储后端上,为多样化工作负载提供成本优化且性能可控的块存储服务,是兼顾成本与性能的工程化解决方案。
