RBD 块存储:从 pool 到挂载
创建 pool、开 image、映射到主机,再把它接到 K8s 里。
学完这节你能做到
- 创建 RBD pool 与 image 并挂载使用
- 配置 ceph-csi-rbd 让 K8s 动态供卷
- 理解 image 特性、快照与克隆
从池到设备,一条完整链路
RBD(RADOS Block Device)把一个"块设备"拆成很多个固定大小的 RADOS 对象(默认 4MB),条带化分布到整个集群。
一个 100GB 的 RBD image
└─ 拆成 25600 个 4MB 对象
└─ rbd_data.<image_id>.0000000000000000
rbd_data.<image_id>.0000000000000001
...
└─ 每个对象按 CRUSH 分布到不同 OSD
这个设计的直接好处:一个 image 的 I/O 天然分散到整个集群的所有盘上,而不是集中在几块盘。
第 1 步:创建池
# 创建池(PG 数交给 autoscaler)
ceph osd pool create rbd
# 声明用途,让 Ceph 应用对应的默认参数
ceph osd pool application enable rbd rbd
# 初始化
rbd pool init rbd
# 确认副本策略
ceph osd pool get rbd size # 3
ceph osd pool get rbd min_size # 2
上一阶段算过这笔账:EC 会把一次小写放大成 k+m 次分散写,而块存储的负载恰恰以随机小 I/O 为主。RBD 池默认且应当使用副本。
Ceph 技术上支持给 RBD 配 EC 数据池(元数据仍需副本池),但那是为归档类冷数据准备的,不要用在虚拟机或数据库场景。
第 2 步:创建并挂载 image
# 创建一个 100G 的 image
rbd create rbd/disk01 --size 100G
# 查看
rbd ls rbd
rbd info rbd/disk01
rbd image 'disk01':
size 100 GiB in 25600 objects
order 22 (4 MiB objects)
features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
# 映射到本机
rbd map rbd/disk01 # → /dev/rbd0
# 之后就是一块普通的盘
mkfs.xfs /dev/rbd0
mount /dev/rbd0 /mnt/data
# 取消映射
umount /mnt/data
rbd unmap /dev/rbd0
rbd map 用的是内核态客户端,老内核可能不认识某些新特性,报错长这样:
rbd: image disk01: image uses unsupported features: 0x38
处理方式是创建时只开内核支持的特性:
rbd create rbd/disk01 --size 100G --image-feature layering
# 或对已有 image 关掉不支持的
rbd feature disable rbd/disk01 object-map fast-diff deep-flatten用户态客户端(librbd,QEMU/KVM 和 CSI 走的路径)支持全部特性,没有这个限制。
第 3 步:快照与克隆
# 创建快照(瞬时完成,写时复制)
rbd snap create rbd/disk01@snap-20260813
rbd snap ls rbd/disk01
# 回滚到快照(会丢失快照之后的所有修改)
rbd snap rollback rbd/disk01@snap-20260813
# 保护快照后可以克隆出新 image
rbd snap protect rbd/disk01@snap-20260813
rbd clone rbd/disk01@snap-20260813 rbd/disk02
# 克隆是"薄"的,只记录差异;flatten 后才独立
rbd flatten rbd/disk02
克隆的典型用途是虚拟机模板:一份基础镜像做快照,克隆出几十台虚拟机的系统盘,只占一份基础镜像的空间。
快照和原始数据在同一个集群、同一批盘上。集群级故障、误删池、机房事故会让快照和数据一起消失。
快照解决的是"误删文件"和"回滚到某个时间点",不解决"存储集群没了"。真正的备份必须在另一套系统里(rbd export 到对象存储、或用备份软件)。
第 4 步:接到 K8s(ceph-csi-rbd)
# 1. 在 Ceph 侧创建给 CSI 用的用户
ceph auth get-or-create client.kubernetes \
mon 'profile rbd' \
osd 'profile rbd pool=rbd' \
mgr 'profile rbd pool=rbd'
# 2. 拿到集群 ID 和 MON 地址
ceph fsid
ceph mon dump
# 3. StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-ceph
provisioner: rbd.csi.ceph.com
parameters:
clusterID: 8d2f1c3a-7b41-4c9e-9f2a-5c6d8e0a1b23
pool: rbd
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: default
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
# 4. 申请一个卷
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteOnce # 块存储只能 RWO
resources:
requests:
storage: 20Gi
storageClassName: block-ceph
块设备是单挂载语义。把同一个 RBD 挂到两个节点上各自跑 XFS,两边的元数据缓存会互相覆盖,文件系统必然损坏,而且没有任何报错提示。
需要多 Pod 共享读写,必须用 CephFS(ReadWriteMany)。这个约束不是 K8s 加的,是块存储语义本身决定的。
扩容
# Ceph 侧扩容 image
rbd resize rbd/disk01 --size 200G
# 客户端还要扩文件系统
xfs_growfs /mnt/data # XFS
resize2fs /dev/rbd0 # ext4
K8s 环境下,只要 StorageClass 开了 allowVolumeExpansion: true,改 PVC 的 resources.requests.storage 即可,CSI 会自动完成两步。
rbd map 报错 image uses unsupported features。原因是什么?
团队想让 3 个 Pod 同时读写同一份数据,有人建议用 RBD 配 ReadWriteMany。该怎么回应?
关于 RBD 快照,下面哪些说法正确?(多选)
这节课的落点
- RBD 把 image 切成 4MB 对象条带化分布,天然利用全集群的盘
- 块存储池用副本不用 EC
- 内核客户端的特性兼容问题用
--image-feature layering规避 - 快照是写时复制、瞬时完成,但不是备份
- RBD 只能 ReadWriteOnce,这是语义决定的,不是配置限制
- 扩容要 Ceph 侧和文件系统侧两步,CSI 会自动处理
延伸资料
- ·k8s-in-action
storage/cephadm/4-deploy-rbd.md - ·k8s-in-action
storage/ceph-csi-rbd/