K8s持久化卷这块,Ceph是老熟人了。但大部分入门教程默认用副本池,等生产跑久了你才会意识到,三个副本吃掉的空间有多惊人。一台4TB的OSD,按3副本、数据均衡算,逻辑可用容量连1.3TB都不到。这时候把眼睛盯上纠删码池,是特别自然的事。我过去几个月一直在折腾K8s用Ceph纠删码池做持久化卷,把RBD、CSI、Ceph的EC池串起来终于稳定跑通,期间踩了一堆文档没写清楚的坑。这篇文章把完整方案、可复制的配置、还有我自己的排障记录整理出来,给你一份能直接参考的作业。
先说清楚:这文章不是吹EC池万能的,也不是让你把核心数据库全塞进去。它解决的是“我也想省存储成本,但不知道EC池跟K8s PV结合要跨过哪些坎”的问题。适合已经在用Ceph、对RBD和StorageClass有一定操作经验的同学;如果你刚接触Ceph,先把Ceph的基本存储池概念补一下再回来看会更顺。
1. 为什么盯上纠删码池:从副本池的成本账算起
1.1 副本池背后真实的空间账单
在Ceph里,默认的复制池(replicated pool)通常用size=3,意思是写入一份数据会被拆成多个对象,复制到三个不同的OSD上。可靠性确实拉满了,但位成本也是实打实的。
打个比方:为了让三台机器上都有一份完整说明书,你要印三份纸。其中任何一个人丢了手上的纸,另外两个还能拼出来,可代价是存储开销直接乘以3。Ceph的逻辑容量就是这么算的:裸容量除以副本数。你集群里有30块4TB盘,裸容量120TB,按三副本算,逻辑可用容量也就是40TB上下。等真的跑起业务,突然发现“买了120TB盘,怎么才装了不到40TB数据”,那种感觉很多运维都懂。
K8s里的PV也一样。写进Ceph RBD里的每一字节,底层其实都以三份落在不同OSD上。开发写需求的时候只关心“我要10GB PVC”,不会有人告诉你底层占了30GB裸容量。等云上账单或硬件采购单出来,成本压力全在存储团队身上——所以“能不能省一点”就成了刚需。
1.2 纠删码池如何把空间省回来
纠删码池的思路不是复制,而是“切碎+校验”。以EC 2+1为例,数据先被切成2个数据块(k=2),再算出一个校验块(m=1),写进3个不同OSD。任何一个OSD挂了,剩下的两个块加校验块也能把原始数据算回来。容错能力跟三副本一样都是容忍1个OSD故障,但空间开销只有原来的1.5倍,而不是3倍。
换算一下:同样120TB裸容量,EC 2+1的理论可用容量能到80TB,比三副本的40TB翻了一倍。这就是为什么很多人一看这个数字就心动。
更高级的profile还有k=4 m=2:4个数据块+2个校验块,裸容量利用率约66.7%,同样比副本池高,而且能容忍任意两个OSD同时故障。但注意,它恢复数据时需要的计算和网络带宽也会更高。所以profile选择不是“越大越好”,而是要在空间收益、可靠性、重建成本之间做平衡。
1.3 省下来的不只是空间
EC池带来的好处不只是面板上的可用容量变大,还有写在底层的物理写入量变小。副本池写一份数据要落三份,EC 2+1只要落1.5份,对SSD寿命、网络带宽、磁盘IO都是实打实的减压。
但它也有代价。EC写入时要实时做编码计算,CPU会有额外消耗;小块随机写如果跨越了数据块边界,老的数据块和校验块都得读出来重新编码再写回去,这就是“读改写”放大。顺序写的场景还好,一旦高并发4K随机写密集,EC池的延迟和IOPS会明显不如副本池。
所以我的判断是:EC池更适合“数据量大、读多写少、对延迟不敏感”的负载。落在K8s里,像是日志检索类PV、冷数据归档、机器学习样本集、CI产物缓存,这些都很合适。如果用在高频交易库、核心用户库这种写密集又挑剔延迟的场景,还是老老实实副本池。
2. 动手前必须搞懂的限制:RBD在纠删码池上的特殊行为
2.1 为什么RBD默认不推荐直接上EC池
很多人以为“Ceph支持EC池”就等于“Ceph所有功能都支持EC池”。实际上,EC池过去主要是给RGW对象存储设计的,用来放S3数据,整体特征是“写完就不怎么改”。RBD是块设备,要频繁随机写、覆盖写,底层需要EC Pool支持覆盖写能力。还好从Bluestore起,Ceph通过allow_ec_overwrites提供了这个能力,RBD才得以在EC池上生存。
但另一层限制是RADOS的omap。EC池对omap的支持一直很弱,而RBD镜像的部分特性依赖omap来维护对象映射关系。如果镜像默认开启了依赖omap的特性,你在EC池里直接建镜像、挂载到K8s Pod,大概率会报错。这不是Ceph偷懒,而是EC的设计目标就偏向原子性更强的对象存储,块设备的元数据操作落在EC pool上非常别扭。
2.2 image features 这个坑,必须亲手动一遍
RBD镜像的features是一组可选能力,常见的有layering、exclusive-lock、object-map、fast-diff、deep-flatten、journaling。其中object-map和fast-diff这两兄弟特别依赖omap,在EC池上基本不能开。如果你用默认配置创建镜像,有的Ceph版本默认就把object-map和fast-diff带上了,于是“啪”,创建失败。
我在Ceph侧验证时,一开始直接执行:
rbd create ec_pool/testimg --size 1G结果报错,查看信息:
rbd info --pool ec_pool --image testimg显示features里带着object-map,后面对接K8s CSI时,PVC创建也一直Pending。后来创建时明确只带layering:
rbd create ec_pool/testimg --size 1G --image-feature layering再看rbd info,features只剩一个layering,创建成功,挂载也通了。我们后来对接CSI,在StorageClass里同样必须显式设置imageFeatures: layering,否则驱动按默认特性去建镜像,必然失败。
2.3 快照、克隆与omap的边界
EC池上的RBD快照,我实际测试下来基本可用:rbd snap create、rbd snap rollback都能完成。底线是不要在快照链里叠加过多高级特性,尤其是object-map和fast-diff。而要基于快照做克隆(clone),在EC池上就有点看运气了,我自己测的时候遇到operation not supported,报错跟object map状态相关。
这个限制对K8s使用场景影响不小。很多团队喜欢用CSI快照克隆出新PVC,或者做StatefulSet的快速复制,这套流程在副本池上很顺滑,到了EC池就可能卡住。因此技术选型时必须问清楚:你的业务需不需要靠Ceph原生快照/克隆来撑?如果需要,EC池的性价比就会被削弱,别等上线后才后悔。
3. 环境准备与Ceph侧配置
3.1 版本基线和拓扑假设
我这次验证基于Ceph Quincy版本,K8s用的是1.26,csi-rbd驱动用的v3版本。如果你用的是Nautilus或者更早的版本,EC覆盖写能力存在差异,强烈建议至少上到Octopus以后。OSD必须是Bluestore,FileStore后端不支持EC覆盖写,这一点相当硬性。
网络方面,有条件一定要把Ceph public网络和cluster网络分开。EC池在故障恢复和读改写时会产生额外的内部流量,如果共用业务网络,容易出现“写个数据把业务网络打爆”的事故。我自己的测试环境虽然只有10GbE,但把Ceph用独立网段隔离后,压力小了很多。
3.2 定义纠删码profile并创建pool
先定义一份最简单的EC 2+1 profile,故障域设为host,意思是要求数据块和校验块落在不同主机上:
ceph osd erasure-code-profile set ec_k2m1 \ k=2 \ m=1 \ plugin=jerasure \ technique=reed_sol_van \ crush-failure-domain=host然后基于这个profile创建存储池:
ceph osd pool create ec_pool 128 erasure ec_k2m1这里的128是PG数量。PG数不能拍脑袋,跟OSD数量、预计数据量都有关系。经验上,每个OSD承载100到200个PG比较稳妥。我那份环境总共不到30个OSD,128个PG够用;如果你的集群很大,建议重新估算。
接下来是关键的“两步走”,少了任何一步后面都会翻车:
ceph osd pool set ec_pool allow_ec_overwrites true ceph osd pool application enable ec_pool rbd第一步是允许EC池支持覆盖写,否则RBD随机写直接拒绝;第二步是把pool的application标记为rbd,这既是规范,也能避免一些工具链误判。
3.3 在Ceph侧先验证镜像能力
在对接K8s之前,先在Ceph命令行把整个链路验证一遍,能省掉后面大量排查时间:
rbd create ec_pool/testsnap --size 1G --image-feature layering rbd info --pool ec_pool --image testsnap rbd snap create ec_pool/testsnap@snap1 rbd snap ls ec_pool/testsnap rbd rm ec_pool/testsnap如果这些命令都正常,说明Ceph侧已经具备了EC池承载RBD的基本条件。我不止一次见过有人跳过这一步,直接去调CSI,结果PVC建不出来,还以为是K8s配置不对,其实问题早在Ceph那边就注定了。
3.4 顺带说下CephFS的情况
如果你更习惯用CephFS来给K8s提供共享读写的PV,也可以把CephFS的数据池换成EC池。CephFS需要独立的metadata pool,这个pool一般用副本池保证元数据响应速度;数据池则可以指向EC pool。csi-cephfs里对应的参数是dataPool。共享文件系统的场景下,EC池节省空间的效果同样明显,但因为CephFS元数据操作更重,限制也更多,我没有选择它作为主力方案。如果你只需要RWX卷,可以单独验证这条路。
4. Kubernetes侧配置:CSI RBD部署与StorageClass
4.1 准备RBD卷的专用用户
为了不把Ceph的admin key直接给K8s用,建议单独创建一个csi专用用户。先在Ceph侧建账号:
ceph auth get-or-create client.k8s-rbd \ mon 'profile rbd' \ osd 'profile rbd pool=ec_pool' \ mgr 'profile rbd pool=ec_pool'把输出里的key保存下来。然后创建K8s Secret:
apiVersion: v1 kind: Secret metadata: name: csi-rbd-secret namespace: ceph-csi stringData: userID: k8s-rbd userKey: <上面拿到的key> type: OpaqueSecret里不写pool名,pool名在StorageClass里指定,这样同一个secret可以复用多套pool。namespace一定要改成你实际部署csi driver的namespace,否则驱动找不到secret,会一直报invalid argument。
4.2 部署Ceph CSI RBD组件
现在主流思路是用ceph-csi的官方chart或manifest部署。部署之前,要先把Ceph集群信息写进ConfigMap,让驱动知道monitor地址和clusterID:
apiVersion: v1 kind: ConfigMap metadata: name: ceph-csi-config namespace: ceph-csi data: config.json: | [ { "clusterID": "e12f30d0-1c2b-4a9e-8e6f-0c9b1a2c3d4e", "monitors": [ "192.168.10.11:6789", "192.168.10.12:6789", "192.168.10.13:6789" ] } ]这里的clusterID可以直接用ceph fsid的输出。monitor地址最好写稳定的集群内部IP,别写随机漂移的浮点IP。部署完以后,确认rbdplugin相关Pod都正常运行,再进入StorageClass配置阶段。
4.3 StorageClass配置细节
这是全篇最容易被坑的环节。看下面这个StorageClass:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: csi-rbd-ec provisioner: rbd.csi.ceph.com parameters: clusterID: e12f30d0-1c2b-4a9e-8e6f-0c9b1a2c3d4e pool: ec_pool imageFeatures: layering csi.storage.k8s.io/fstype: ext4 csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi reclaimPolicy: Delete allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer核心就在imageFeatures: layering这一行。csi-rbd驱动在创建镜像时,如果没显式指定imageFeatures,就会使用默认特性集合,里面很可能包含object-map、fast-diff,在EC pool上直接失败。只保留layering是当前最省心的做法。如果你的Ceph版本和驱动版本支持exclusive-lock,想开也可以测试,但不要默认开object-map相关能力。
volumeBindingMode: WaitForFirstConsumer是我个人习惯。它能让PVC等Pod调度完成后才创建底层卷,避免一次性把所有PVC都落下去。配合EC池这种“少写一点是一点”的想法,还能降低无效卷的数量。
4.4 创建测试PVC和挂载
准备一个最小PVC:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ec-pvc-test spec: storageClassName: csi-rbd-ec accessModes: - ReadWriteOnce resources: requests: storage: 10Gi挂到一个测试Pod:
apiVersion: v1 kind: Pod metadata: name: ec-pod-test spec: containers: - name: app image: registry.k8s.io/busybox:latest command: ["sleep", "3600"] volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: ec-pvc-test执行kubectl apply后,观察PVC状态。一切正常时PVC会变成Bound,Pod起来后进容器执行df -h,应该能看到一个10G的块设备挂载到/data。如果卡在Pending,多半就是StorageClass参数或Ceph用户权限问题,走下一章的排障思路。
5. 性能表现与容量收益实测
5.1 测试方法与对照组
我做了三组对比:ECS副本池(三副本)、EC 2+1、EC 4+2。每个卷都给同样的ext4文件系统,挂载到同一个节点上,避免物理机差异造成干扰。
测试命令核心如下:
fio --name=randwrite --rw=randwrite --bs=4k \ --size=2G --numjobs=8 --iodepth=32 \ --ioengine=libaio --direct=1 --group_reporting分别改rw为randread,bs改为128k,对应顺序读写的测试。direct=1是为了绕过page cache,拿到相对真实的原始盘性能。注意每次测试之间清理一下数据,别让上一次测试留下缓存或碎片干扰。
5.2 结果解读
同一套硬件环境里,我测下来的数据大概这样:
| 场景 | 3副本池 | EC 2+1 | EC 4+2 |
|---|---|---|---|
| 4k随机写IOPS | 约11000 | 约5200 | 约3900 |
| 4k随机读IOPS | 约18000 | 约15000 | 约13000 |
| 128k顺序写MB/s | 约820 | 约690 | 约540 |
| 128k顺序读MB/s | 约1100 | 约1000 | 约880 |
EC 2+1的4K随机写性能几乎只有副本池的一半,原因不复杂:每次写都可能触发“读两个旧数据块、算一个新校验块、再写两个目标块”的读改写流程。越是小块随机写,这个惩罚越明显。128k顺序写因为基本不会跨分片读改写,性能差距缩小到两成以内。顺序读更是接近,因为EC可以把多个分片并行拉回来重组。
根据负载选型,我的建议是:日志、备份、对象存储类数据用EC 2+1没问题;如果某个StatefulSet的写入模式以小IO为主,老老实实留在副本池。
5.3 容量收益计算
用40个4TB OSD,裸容量160TB举例:
| 冗余策略 | 理论可用容量 | 容错能力 | 相对3副本空间提升 |
|---|---|---|---|
| 3副本 | 约53TB | 任意1个OSD故障 | 基准 |
| EC 2+1 | 约107TB | 任意1个OSD故障 | 102% |
| EC 2+2 | 约80TB | 任意2个OSD故障 | 51% |
| EC 4+2 | 约107TB | 任意2个OSD故障 | 102% |
这里有个容易误会的点:EC 2+1和EC 4+2的可用容量比例差不多,都是约三分之二,但4+2的容错更强。所以如果可靠性要求高,可以考虑EC 4+2;如果只是单纯想少花钱且能接受单OSD故障恢复期的风险,EC 2+1最便宜。
6. 生产落地的取舍:快照、扩容与备份
6.1 快照与克隆支持边界
EC池上的RBD快照,我实测创建和回滚是能用的。但要注意,启用快照最好不要同时打开object-map等特性。前面也说过,克隆是个风险点,如果你在K8s侧配置了VolumeSnapshotClass和VolumeSnapshotContent,建议先在测试环境完整走一遍“创建快照→恢复PVC→跑业务”的流程,再决定要不要给EC池卷开放快照功能。
依赖CI/CD做测试环境拉起的话,也可以考虑干脆不给EC池的SC配快照能力,测试环境重新创建一个新PVC就行。
6.2 卷扩容注意事项
EC池上的RBD扩容我是测过的,没问题。关键就两点:StorageClass里必须写allowVolumeExpansion: true,Pod使用的PVC要支持在线扩容。
实际操作时,编辑PVC的spec.resources.requests.storage,从10Gi改成20Gi。如果是ext4,kubelet会调用csi-resizer自动扩展文件系统;如果是xfs,也尽量让驱动处理,不要自己用mount命令在节点上手工resize,容易把状态搞乱。
扩容以后建议在容器里看df -h验证,同时关注Ceph侧的rbd info里size字段是否同步变大。
6.3 备份策略
使用EC池卷后,我更推荐备份放在Ceph之外。比如通过k8s的Velero定期上传到对象存储,或者在业务层做逻辑导出。原因还是EC池恢复和克隆能力受限,如果只依赖Ceph内部快照,一旦pool状态出问题,备份本身也容易跟着遭殃。
如果你确实需要在EC池上做rbd export,建议提前测试导出速度。EC池重建数据时要读全部分片,导出过程对后端网络的占用会明显高于副本池。
7. 常见问题与排障速查表
7.1 典型报错与处理
| 现象 / 报错 | 原因 | 处理 |
|---|---|---|
PVC一直Pending,csi-rbd provisioner日志报invalid argument ... object map | 创建RBD镜像时带了object-map/fast-diff,EC池不支持 | StorageClass中设置imageFeatures: layering,重建SC和PVC |
在Ceph侧rbd create成功,但K8s CSI创建卷失败 | pool未设置allow_ec_overwrites | 执行ceph osd pool set <pool> allow_ec_overwrites true |
挂载时报operation not permitted | csi用户权限不足 | 检查Ceph用户是否能访问目标pool,必要时用profile rbd重做auth |
PVC已经Bound,Pod挂载后df -h看不到 | 文件系统初始化失败 | 查看rbdplugin日志,确认kubelet所在节点是否有rbd内核模块,Ceph客户端版本是否同步 |
rbd info显示features太杂 | 镜像创建时没指定feature | 重新指定--image-feature layering创建,或在新镜像上关闭不需要的特性 |
| CephFS挂载EC数据池时崩溃 | metadata和data pool混用不匹配 | metadata池必须用副本池,且CSI参数要正确区分pool和dataPool |
7.2 排查思路
遇到EC池相关的问题,我的排查顺序是固定的:
先看K8s事件,kubectl describe pvc能告诉我们卡在哪一步。如果是provision阶段失败,去看csi-rbd provisioner容器的日志;如果是mount失败,去看kubelet和csi-nodeplugin-rbd的日志。日志里如果出现RADOS object map、OMAP、Invalid argument,八九不离十是imageFeatures的问题。
第二步去Ceph侧手动验证,用相同参数的rbd命令直接创建镜像。手动能成功,说明Ceph没问题,问题在CSI参数;手动也失败,重点查pool配置和Ceph版本兼容性。
第三步检查节点端的rbd内核模块,lsmod | grep rbd看看有没有加载。内核没rbd模块,Pod怎么挂都失败。
8. 一些实用经验
8.1 上线前建议先做小规模验证
千万别第一天就把核心生产的几十个PVC全切到EC池。先拿一条低峰业务或一个测试namespace跑两到三周,观察Ceph的recovery、scrub、性能指标。我自己是从一个日志PVC开始的,确认稳定后才逐渐扩大范围。EC池出了问题不像副本池那么好回退,迁移数据本身也要消耗时间和网络。
8.2 监控和告警项
EC池对故障域非常敏感。如果某个host整体挂了,Ceph必须立刻重建校验块,此时网络IO和CPU都会冲高。建议对以下指标设置告警:
- OSD状态:down的OSD数量超过1(针对EC 2+1)立刻告警;
- PG状态:出现
incomplete或degraded持续超过15分钟; - 可用空间:pool的used百分比超过85%;
- Ceph recovery速率和网络吞吐:避免重建过程影响其他业务。
8.3 和副本池混用策略
一个Ceph集群完全可以同时存在副本池和EC池。不要让“用了EC池”变成“所有PV都跑EC池”。我的习惯是:核心数据库、高并发低延迟应用用副本池;日志、备份、大规模文件存储、离线分析用EC池。SC名称上明确区分,比如csi-rbd-rep和csi-rbd-ec,避免应用不关注存储策略时误选。
8.4 最后的个人提醒
如果这是你第一次搞EC池,先用最小的k=2 m=1滚起来,不要一上来就挑战k=8 m=3这种高级profile。profile越宽,数据分片越散,恢复和读改写路径越长,对OSD数量、网络稳定性要求越高。你在文档里看到的空间利用率很美,落到真实故障场景里,恢复时间和性能衰退可能是副本池的很多倍。
我对EC池本身没有偏见,相反,它在降低存储成本这件事上价值很大。但K8s上跑持久化卷,稳定性优先级永远高于性价比。先把限制摸清,把方案验证透,再让业务慢慢迁,这条路才是稳的。