☰
Ceph RBD删除后空间不释放?从原理到排查的完整指南
2026/10/21 18:58:44 网站建设 项目流程

最近被好几个朋友问同一个问题:删了 rbd 上的数据,ceph 的空间为什么没释放。遇到这类情况先别急着怀疑集群坏了,大部分时候是“删除的姿势不对”。我维护 Ceph 集群这些年,见过太多类似的排查现场,今天就把整套排查思路、背后的机制、以及最终的解决办法完整写出来。不管你是刚上手的小白,还是已经在生产环境跑了好几年的运维,照着这份清单做一遍,基本都能定位到问题。

1. 先别慌:确认空间是真的没释放,还是回收节奏慢

1.1 删除RBD镜像后的正常流程是什么

当你执行rbd rm <pool>/<image>后,Ceph需要做两件事:把镜像的元数据从RBD命名空间中摘掉,然后把镜像对应的每个底层对象逐个删除。底层对象分布在多个OSD上,删除动作由OSD异步执行,所以空间释放天然会有延迟。特别是大镜像、对象数量很多时,这个延迟可能长达几分钟甚至更久。

所以首先要区分“没释放”和“还没释放完”。先执行ceph df看整体使用率,再执行ceph -s看当前有没有 recovery 或 backfill 在跑。如果出现了恢复/回填,说明OSD正在重新调整数据布局,空间变化会逐步反映出来。

用rbd ls <pool>确认镜像是否还在,再用rados df -p <pool>看pool里的对象数。正常情况下,镜像删除后,对象数会明显下降。如果对象数纹丝不动,那就不是“回收慢”,而是某个环节把删除动作拦住了。

1.2 你以为删了,其实只是移进了回收站

很多人不知道,从某个Ceph版本开始,RBD有了“回收站”机制,也就是rbd trash。删除镜像时,有些云平台或管理工具会调用rbd trash mv,把镜像移入trash而不是直接删掉。这样从用户视角看镜像没了,但对象还在占用底层空间。

验证方法很简单:

rbd trash ls <pool>

执行后如果看到一堆带ID的镜像列表,并且显示deleting或expiring状态,那说明空间还没释放完。如果只是平平无奇地躺在trash里,那就需要手动处理。

想彻底清掉,用:

rbd trash purge <pool>

purge会立刻删除trash里所有不处于延迟保护期的镜像。如果你设置了延迟删除时间,比如给镜像指定了过期时间,那么到期之前 purge 也拿它没办法,只能等时间到了再清。想马上删,可以用:

rbd trash remove <pool> <image-id>

其中<image-id>就是rbd trash ls输出里第二列那一串ID。这个坑在OpenStack环境里特别常见,云平台删除虚拟机卷时,底层存储驱动很可能只是把卷移入了trash,为的是可以保留一段时间的误删恢复能力。如果你在OpenStack删除卷后发现集群空间没少,优先去查trash,十有八九能找到答案。

2. 快照和克隆,是“删除不了”的头号元凶

2.1 快照还在,数据就还在

RBD的快照用的是COW(写时复制)机制:创建快照后,快照层会记录底层对象的原始版本,而新的写入会分配到新的对象。这个机制的好处是快照创建快、占用小,坏处是:删除快照时,系统必须把快照和被快照引用到的数据做合并,合并动作会占用空间和时间。

如果镜像里带了快照,你执行rbd rm时,Ceph可能不会把所有快照一起删掉,而是把镜像标记为 deleting,真正释放却卡在那里。更常见的情况是:你在镜像上做过rbd snap create,删除镜像前忘了删快照,结果rbd ls --long里看不到普通镜像,但rbd snap ls实际还能列出快照,空间自然没法释放。

所以删除前建议先把快照清理掉:

rbd snap ls <pool>/<image> rbd snap purge <pool>/<image>

如果快照链很长,snap purge也可能需要一段时间。中途不要手痒去重启OSD或停止OSD进程,合并过程一旦中断,轻则回收卡住,重则引入数据不一致。

2.2 克隆镜子像在背后“拽住”了父镜像

RBD支持快照克隆,也就是把一个带快照的镜像作为parent,生成多个child镜像。如果你删的是父镜像,但克隆出来的子镜像还引用着它,Ceph会拒绝删除,或者只删掉不再被引用的部分。

可以用rbd children <pool>/<parent-image>查看有多少child。如果有一堆child,最简单的做法是对每个child执行rbd flatten <pool>/<child-image>,把父子关系解开。扁平化过程会把父快照的数据全部合并到子镜像里,完成后父镜像才可以安全删除。

这里的代价非常直接:flatten会消费大量空间和IO,因为它是把COW关联数据逐一拷贝。如果一个镜像有几百G快照数据,flatten之后集群使用率可能先涨后跌,不要看到上涨就以为是异常,这属于正常的合并动作。

2.3 设备被“抱着”不放,删除只能等待

镜像可能正在被某台机器mount着,或者被某个QEMU进程打开,甚至被容器挂载。此时执行rbd rm会收到一个类似image has watchers的错误。

解决办法不是去强杀,而是先查谁能“看着”这个镜像:

rbd status <pool>/<image>

输出会显示客户端地址和cookie信息。你去对应的宿主机上把挂载卸载,或者停掉对应的虚机/容器,让watch断掉,然后再执行删除。尽量不要用ceph osd force之类的危险操作来绕过watcher,风险是删除一半出现数据损坏,得不偿失。

3. 最常见的迷惑行为:删了文件,RBD却瘦不下来

3.1 镜像里的文件系统不会自动告诉Ceph“这些块没用了”

这是很多人忽略的核心点。如果你的场景不是删除整个RBD镜像,而是像普通服务器一样,在RBD块设备上建了文件系统,然后在里面删了一些文件,那你一定会发现:文件删了,文件系统里的可用空间增加了,但ceph df里的集群使用率几乎没有变化。

原因很简单。RBD是稀疏分配(thin-provisioned)的,它只关心你实际写入过哪些对象。文件系统删除文件只是把对应的元数据标记为可用,并没有把数据所在的底层块置为“空”。对Ceph来说,那些对象仍然有数据,仍然占着物理空间。换句话讲:你只擦了书架的目录卡片,书本身还放在架子上。

要想让RBD真正释放这部分空间,必须从客户端向下层块设备发出发discard/trim指令,让Ceph知道这些块可以回收了。

3.2 先确认你的RBD支不支持打洞

在客户端上看,用lsblk -D /dev/rbd0,如果DISC-GRAN和DISC-MAX不是0,说明内核认为这块盘的discard是能发的。云平台里的虚拟块设备,通常还需要虚拟化层把discard透传下去。例如在配置里把磁盘控制器设为virtio-scsi并开启discard,或者在QEMU命令行里给设备加discard=unmap参数。如果不透传,虚拟机里看lsblk -D就是0。

另外RBD镜像本身也依赖rbd info里的features。如果你以前用老版本创建的镜像,某些特性没有开启,可能会导致discard无法正确下传。用rbd info <pool>/<image>看features一栏,如果没有exclusive-lock或object-map,部分环境的效率会很低,建议新创建镜像时保持比较新的feature集合。

3.3 手动回收:blkdiscard和fstrim怎么选

如果你有一整块RBD设备要清空,或者希望立即回收所有未被文件系统占用的空间,用blkdiscard最简单:

blkdiscard /dev/rbd0

注意这个操作会丢掉整块设备上的所有数据,必须先确认是可丢弃的设备或已备份。

如果设备已经挂载了文件系统,不想卸载,用fstrim更安全:

fstrim -av

它会扫描文件系统里标记为free的块,然后向块设备发discard。执行完成后,再回控制台看ceph df或rbd du,你会看到实际占用掉了一大块。

在容器里跑fstrim需要额外权限,如果容器内没有CAP_SYS_ADMIN,fstrim会报 operation not permitted。这种情况在宿主机上对底层RBD设备执行,或者给容器补权限。

3.4 别指望一次trim就万事大吉

文件删除是持续发生的,所以更好的做法是让trim自动跑。Linux自带了fstrim.timer,可以配置定期对已挂载的符合条件文件系统执行trim:

systemctl enable --now fstrim.timer

默认每周执行一次,周期可以按需求调整。对于跑数据库等高频删除的场景,这个周期可能不够;对普通文件服务,周级trim已经足够。

如果RBD设备在虚拟机里,你需要在虚拟机内部也配置一个fstrim,前提是hypervisor把discard透传给了客户端。否则即使虚机里fstrim执行成功,宿主机和Ceph也收不到信号。

4. 用对工具,把空间回收状态看得明明白白

4.1 用rbd du看“真实占用”,不要被虚拟大小骗了

很多人只盯rbd ls显示的镜像大小,比如显示1024G,那是虚拟容量,不代表实际占用。看实际占用用:

rbd du <pool>/<image>

输出里的USED字段代表真实分配了多少对象。如果你对镜像做过trim,USED会明显下降;如果没做过trim,USED基本接近文件系统里曾经写入的总量。

这里有个小技巧:对比rbd du和ceph df的变化趋势,能快速判断是“镜像没有真正释放”还是“只是ceph df统计延迟”。前者USED会居高不下,后者等一段时间就会掉下来。

4.2 观察PG和OSD的恢复状态

删除镜像对象后,每个OSD的PG都会经历状态更新,可能出现degraded/peering等变化。执行:

ceph -s

查看全局状态,如果出现recovery 1234/5678 objects degraded之类的行,说明删除对象的影响还在后台处理。这通常不是问题,等recovery完成就好。要是长时间卡在某个PG上,再用ceph health detail去看具体哪个PG有问题。

顺带提一句,为了控制删除风暴,Ceph默认限制了对象删除的并发速度。比如某个OSD上有几万个小对象要删除,可能要好一阵子使用率才会慢慢往下掉。不要一看没变化就手忙脚乱去调max_backfills,除非你确定这是瓶颈。

4.3 另一个确认删除生效的办法:看pool的对象数

rados df可以按pool列出对象数。删除RBD镜像后,对应pool的objects数量应该下降。如果镜像体积巨大但对象数量很小,可能一个大对象只是几十G,删除后数量上不明显;这时候可以看rbd du更直观。

用对象数来验证的好处是,它不受Ceph客户端缓存影响,直接反映底层状态。当你怀疑“空间没释放”是客户端统计问题,用这个看最直接。

5. 从删除到确认释放:一套可复制的排查命令

5.1 按顺序执行的命令序列

下面这套命令,我把删除RBD后排查空间不释放的完整流程串起来了。实际操作时,把<pool>、<image>替换成你的真实名。

# 1. 确认普通镜像列表 rbd ls <pool> # 2. 确认回收站里是否有隐藏镜像 rbd trash ls <pool> # 3. 查看镜像自身信息,确认features和parent rbd info <pool>/<image> # 4. 列出所有快照 rbd snap ls <pool>/<image> # 5. 查看是否有克隆子镜像 rbd children <pool>/<image> # 6. 查看是否有客户端watch着 rbd status <pool>/<image> # 7. 查看真实占用 rbd du <pool>/<image> # 8. 看集群使用率 ceph df # 9. 看pool对象数 rados df -p <pool>

如果你已经走到了第8、9步,发现使用率没降、对象数没少,那就果断回到第2步和第5步去查看trash和children,大多数“删不掉”的场景都能在这里找到线索。

5.2 命令背后为什么要这么查

每一条命令都有它存在的意义。rbd trash ls是查“假删除”,很多平台层工具的删除动作都是进回收站。rbd info是查“配置对不对”,features缺失会影响trim和快照行为。rbd children是查“有没有被引用”,有引用就删不掉。rbd status是查“有没有人正握着”,有watch就等释放。

我建议大家不要跳过任何一步,按顺序跑一遍,顺便把输出存下来,这样就算空间释放是正常的网络延迟,你也有依据可以判断。

5.3 如果所有命令都正常,空间还是没释放,怎么办

这种情况通常指向三个方向:一是底层OSD正在做深度清理,需要更长耐心;二是镜像的对象分布在个别OSD上,而某个OSD因为故障导致删除对象失败,你会在ceph -s里看到HEALTH_WARN或object unfound;三是你删的其实是pool里的其他类型数据,比如CephFS或RGW的数据,这两者的空间回收机制和RBD并不一样,不能用同一套理解去套。

遇到第三种情况,先去确认自己删的对象属于哪个存储接口。把RBD、RGW、CephFS混在一个pool里的时候,尤其容易搞混。这个锅不能让RBD背。

6. 亲身踩过的坑,和一张速查表

6.1 三个真实翻车场景

第一次遇到这个问题时,我在一个OpenStack测试环境里删了一堆卷,集群空间一点没降。排查了很久,最后才发现是trash里堆了成百上千个延迟删除的镜像,而trash默认不显示在常规list里。那次之后,我养成了每次删卷后定期rbd trash purge的习惯。

第二次是客户反馈虚拟机里删了数据,空间就是不释放。我查了半天RBD都没问题,最后发现虚拟机用的磁盘控制器是IDE,根本不支持discard透传。改成virtio-scsi并开启discard后,同样的fstrim命令才真正起了作用。这个教训告诉大家:光在客户端执行fstrim还不够,虚拟化链路上任何一环不支持discard,trim信号都到不了RBD。

第三次是对象数量不多但空间占用极高的场景。我一开始以为是trim没生效,后来发现是镜像创建时开了非常老旧的features,导致文件系统发出的discard在librbd层被悄悄忽略。后来把镜像刷新成新feature,并重新做了一次全盘fstrim,空间才算真正释放。

6.2 问题速查表

现象可能原因检查路径处理动作
rbd ls看不到镜像,但空间未释放镜像被移入trashrbd trash ls <pool>rbd trash purge <pool>或rbd trash remove <pool> <image-id>
删除镜像报 watchers 错误镜像正被客户端打开rbd status <pool>/<image>卸载挂载点或停掉对应虚机/容器
镜像有快照,删除卡住快照链未清理rbd snap ls <pool>/<image>rbd snap purge <pool>/<image>
父镜像有克隆子镜像COW引用阻止删除rbd children <pool>/<image>对子镜像执行rbd flatten
文件删了但Ceph使用率不变文件系统没有发discard在客户端执行fstrim -av用fstrim.timer定期执行;配置透传discard
虚拟机里fstrim没用hypervisor没透传discardlsblk -D /dev/...改用支持unmap的磁盘控制器/驱动
删除后空间慢慢下降OSD同步需要时间ceph -s看recovery等待恢复完成,暂不干预

6.3 写在最后的个人习惯

空间释放这种事,最好的办法是预防而不是事后救火。我现在的习惯是:创建RBD镜像时就把features和discard链路确认好;删除镜像前先看snap和children,别让快照和克隆成为隐形包袱;删除后固定跑一遍rbd trash ls和rados df -p,用对象数说话。这套操作看着繁琐,但能省下大量排查时间。如果你碰到过类似问题,或者拿这份清单排查完仍然有奇怪现象,可以去看看OSD日志里的删除异常信息,最后再提醒一次:别在生产环境里对RBD镜像执行过于激进的底层删除操作,优先走官方CLI。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询