
很多人第一次听到“Ceph镜像快照”这个说法第一反应是快照就是镜像镜像就是快照。我在生产环境里处理过上百次跟rbd快照、rbd镜像相关的故障可以很明确地说这两个东西在Ceph里不是一回事但又是配合最紧密的一对。这篇文章基于我长期的存储运维经验把Ceph镜像快照从概念、命令、方案设计到故障排查整个链路梳理一遍。适合正在用Ceph做虚拟机存储、云平台后端存储或者准备搭建跨机房容灾的运维和架构师也适合刚上手Ceph、想搞懂RBD数据保护机制的朋友。1. 先分清快照与镜像这两个概念在Ceph里不是一回事1.1 我用一个踩坑案例说明白“rbd image”和“rbd snapshot”的关系先讲个真实案例。之前有个同事在测试环境执行了这样一条命令rbd snap create rbd/myvmbackup_before_upgrade他以为这条命令创建了一个“镜像备份”于是放心大胆地去升级虚拟机内部的应用。结果升级到一半发现版本不兼容他想回滚执行了rbd snap rollback rbd/myvmbackup_before_upgrade然后虚拟机照样启动失败他当场就懵了快照明明存在为什么回滚了还是起不来这个案例暴露了新手对Ceph块存储最常见的误解。在Ceph RBD里image是基础块设备你可以把它理解成一块裸磁盘snapshot是这块磁盘在某个时间点的只读状态。回滚操作做的事情本质上是“把当前盘的内容覆盖成快照时的内容”但如果你回滚的是一个正在被虚拟机使用的盘虚拟机文件系统层面的缓存、日志、分区表信息大概率是残缺的结果就是系统能启动但文件系统挂载失败或者直接内核panic。更关键的是很多人以为快照是“拷贝了一份完整数据”其实快照是基于COW的机制。Ceph RBD快照默认使用写时复制创建快照的那一刻几乎占不了什么空间真正的空间消耗发生在这个快照创建之后的新数据写入时。所以快照不能当成镜像备份来用它在设计上就是为了“短时间点保护”和“快速回滚”服务的。1.2 那“镜像”又是什么RBD Mirroring是跨集群的数据复制Ceph里的“镜像”指的是RBD Mirroring即块设备镜像同步。它解决的是单集群故障时的数据可用性问题如果一个机房整体断电、网络隔离甚至硬件损坏你可以从另一个集群继续提供服务。RBD Mirroring分两种模式模式复制粒度适用场景池级镜像Pool Mode池内所有镜像自动复制整池保护运维成本低镜像级镜像Image Mode只复制指定的镜像只保护关键业务盘节省网络带宽这个机制是异步复制的不是同步双写。RBD Mirroring的RPO取决于你的网络延迟和复制周期极端情况下如果主集群瞬间崩掉最近几秒的数据可能来不及同步到备集群。这和快照完全是两种定位快照管的是“误操作回滚”镜像管的是“数据中心级灾难恢复”。1.3 三种能力定位对比快照、镜像、克隆再补充一个经常被搅在一起的克隆。Ceph RBD支持从快照做COW克隆也就是rbd clone。克隆出来的镜像可以独立使用也可以继续打快照。三者的关系可以这么理解快照给一个image做“存档点”用于回滚。克隆基于某个快照创建新的image用于快速交付新虚拟机或测试环境。镜像把整个image或者整个pool的数据持续同步到另一个Ceph集群。我在设计存储方案的时候通常会把三层的节奏定下来快照做小时级保护克隆做环境快速交付镜像做机房级容灾。这样一套组合拳下来单点故障和数据误删基本都能兜住。2. RBD快照的实战套路创建、回滚、克隆与分层2.1 快照创建简单但应用一致性的坑很多直接命令创建快照很简单# 查看当前image列表 rbd ls mypool # 创建快照 rbd snap create mypool/mysql-datasnap_20250118_0300 # 列出快照 rbd snap ls mypool/mysql-data # 查看快照详情包含创建时间、大小、保护状态 rbd info mypool/mysql-datasnap_20250118_0300但这里有个极其重要的问题你到底在快照什么的一致性状态如果你的虚拟机里跑着MySQL你直接用rbd snap create创建快照得到的只是“磁盘在某毫秒的内容”不代表MySQL的数据文件是一致的。MySQL可能正在写binlog可能redo log刚好处于半提交状态这个快照即使回滚过去数据库也可能启动失败。所以生产环境的正确姿势是先通过应用层接口做一致化处理。比如MySQL用FLUSH TABLES WITH READ LOCK或者启用XtraBackup的备份锁如果用的是KVM虚拟机可以先virsh snapshot-create --quiesce让QEMU Guest Agent帮忙冻结文件系统再打Ceph快照。我自己的经验是Ceph快照做的是“存储层时间点”应用层一致性必须由上层配合完成。不要指望存储工程师一个人能把所有事都干了。2.2 回滚不是唯一的恢复手段cloneflatten才是正经做法遇到数据损坏第一反应是rbd snap rollback。这个命令本身没问题但如果你直接对正在运行的虚拟机镜像做rollback大概率会出问题——虚拟机还在用这个盘块设备突然被覆盖文件系统直接进入不可预测状态。正确做法是# 停止使用该镜像的虚拟机 # 1. 先基于快照做保护防止快照被错误删除 rbd snap protect mypool/mysql-datasnap_20250118_0300 # 2. 克隆出一个新镜像用于恢复 rbd clone mypool/mysql-datasnap_20250118_0300 mypool/mysql-data-restore # 3. 新镜像是COW类型依赖父快照如果需要独立使用执行flatten rbd flatten mypool/mysql-data-restore # 4. 回滚原镜像 rbd snap rollback mypool/mysql-datasnap_20250118_0300这套流程有几个好处克隆出来的镜像和原镜像互不干扰可以先挂载到新虚拟机做数据校验。flatten之后新镜像完全脱离父快照依赖成为独立的image即使父快照被删除也不受影响。回滚原镜像操作在虚拟机停机之后执行避免在线回滚导致的数据错乱。我在实际恢复中几乎都是这个顺序先clone出来验证再决定是直接启用克隆镜像还是回滚原镜像。直接回滚往往是最危险的路径。2.3 快照差异分析用rbd diff做增量备份快照的另一个高频用途是备份。很多人做Ceph备份用的还是全量导出其实Ceph原生就支持差异导出效率高很多# 导出快照与上一个快照的差异 # 格式rbd export-diff image快照 目标路径 rbd export-diff mypool/mysql-datasnap_20250118_0300 /backup/mysql-data_0300.diff # 如果需要从裸镜像导出全量初始数据则可以这样 rbd export-diff mypool/mysql-data /backup/mysql-data_initial.diff差异导出基于快照链第一次导出全量之后每次都只导出上一个快照到当前快照之间的变更数据。恢复时按顺序导入即可rbd import-diff /backup/mysql-data_initial.diff mypool/mysql-data-restored rbd import-diff /backup/mysql-data_0300.diff mypool/mysql-data-restored这套机制对带宽和存储空间都非常友好。我用它做过一套备份方案每天凌晨对数据库打快照然后把差异数据同步到另一套Ceph集群的冷存储池里保留30天。恢复时只需要按时间点导入即可不需要一直开着RBD Mirroring。3. 跨集群RBD Mirroring落地从命令行到故障切换3.1 环境准备两个集群同步状态检查RBD Mirroring需要两个Ceph集群我以最常见的场景为例主集群在业务机房备集群在灾备机房。两个集群的Ceph版本必须一致或者差异极小否则可能因为rbd协议版本不匹配导致复制失败。部署前先检查两个集群的状态# 主集群 ceph -s ceph osd tree # 备集群 ceph -s ceph osd tree另外需要确认两个集群的pool名称和pg_num设计保持一致。Mirroring是按pool名匹配的如果你的pool在主集群叫rbd在备集群叫rbd-bak虽然可以手动指定remote pool但会给自己增加很多配置复杂度。我建议一开始就让两个集群的池名、配额、pg分布尽量一致。3.2 配置池级镜像的完整流程以下是在主备集群之间启用RBD Mirroring的标准流程# 1. 在备集群创建同名的pool ceph osd pool create rbd 128 128 # 2. 在两个集群分别建立keyring并且互相授权 # 这个步骤通常用cephx的client.rbd-mirror账户 ceph auth get-or-create client.rbd-mirror mon profile rbd-mirror osd profile rbd ceph auth export client.rbd-mirror # 3. 在备集群配置rbd-mirror守护进程 systemctl enable ceph-rbd-mirrorrbd-mirror systemctl start ceph-rbd-mirrorrbd-mirror # 4. 在主集群的pool上启用pool级镜像 rbd mirror pool enable rbd pool启用之后可以查看同步状态rbd mirror pool status rbd # 输出示例 health: OK daemon health: OK ...如果看到WARNING多半是备集群的rbd-mirror进程没有启动或者网络不通先查这两个。这里有个关键point启用pool级镜像之后已经存在的image不会被自动复制。pool mirror mode只对后续新建的image生效历史image需要手动逐个启用# 手动为已有镜像启用镜像同步 rbd mirror image enable rbd/mysql-data这个坑我踩过一次。当时以为pool级别开启镜像就万事大吉结果备集群空转了好几天主集群上面的旧数据根本没同步过去。后来写脚本扫了一遍所有image逐个enable。3.3 主备切换与故障恢复promote、demote与split-brainRBD Mirroring的故障切换流程核心就两个命令demote和promote。正常情况下主集群的镜像状态是primary备集群是non-primary。当主集群挂掉你需要把备集群提升为primary# 在备集群执行 rbd mirror image promote rbd/mysql-data恢复之后备集群开始作为主提供读写业务侧把虚拟机的存储指向改成备集群的Ceph即可。等到原主集群修好了再把它作为新的备集群接入反向同步数据。但这里有一个非常严肃的坑split-brain。如果在主集群没有降级demote的情况下直接把备集群提升为primary两个集群都认为自己有写入权数据会各自发展等网络恢复后重新同步时根本无法自动合并。这就是脑裂。所以正确的切换顺序是如果主集群还能操作先执行rbd mirror image demote rbd/mysql-data。再在备集群执行rbd mirror image promote rbd/mysql-data。如果主集群彻底不能操作需要强制提升并且标记主集群原数据为分离状态。# 强制提升备集群 rbd mirror image promote --force rbd/mysql-data注意使用--force之后原主集群上的数据将被视为“过期数据”后续恢复同步时Ceph不会自动比对哪个数据新而是以当前primary为准。这个行为是正确的但前提是你必须确认原主的数据已经没用了否则一同步就把新主的数据覆盖掉了。4. 快照和Mirroring撞上掉盘故障一次真实排错复盘4.1 故障现象OSD掉盘后快照“消失”了有一次业务反馈某台虚拟机的数据盘访问异常我上Ceph集群看了一眼ceph -s显示一个OSD downPG状态里有不少degraded。然后同事试图回滚一个快照来恢复数据结果发现rbd snap ls列出来的快照数量比预期少了很多。“快照是不是丢了”这是我当时的第一反应。4.2 排查链路从health状态到pg状态再到rbd映射千万不要一上来就去查快照先查Ceph集群整体健康状况ceph -s ceph health detail ceph osd tree ceph pg dump | grep -E active|degraded|peered|incomplete当时我看到的问题在于某个OSD所在的主机硬盘彻底挂掉导致该OSD上的PG进入degraded状态部分PG甚至停留在peered也就是只完成了部分数据对账Ceph暂时还没有把它置为active。rbd快照元数据本身存储在monitor和相关的OSD上当某个PG无法正常服务时rbd snap ls就可能出现快照列表不完整的情况。Ceph的底层机制是这样的一个rbd image的数据被切分成多个对象这些对象分布在不同的PG中。快照的元数据也是对象的一部分。如果掉盘的OSD上恰好有这些对象PG没有完成恢复之前客户端就无法读到完整的数据。也就是说不是快照被删了是暂时读不到。4.3 快照在副本机制下的最终一致性保障Ceph采用多副本机制默认是3副本掉一个盘并不会直接导致数据丢失。degraded状态意味着剩余副本还足够提供服务但Ceph为了保证数据一致性在某些操作上会等待PG状态恢复。这个时间取决于你的副本数、网络、磁盘IO以及是否开启了noout、noscrub等集群标志。我当时做的第一件事是确认没有开启误导性的集群标志ceph osd getcrushmap | ... ceph osd dump | grep flags然后等待集群自动恢复。如果掉盘的OSD短时间内无法拉起要把故障OSD从集群中剔除让Ceph启动数据重平衡# 确认该OSD确实没救了 ceph osd out osd-id ceph osd crush remove osd.osd-id ceph osd rm osd-id之后Ceph会在剩余的副本上重建对象数据PG回到activeclean。这时候再看快照列表之前“消失”的快照全部回来了。整个过程大概是两个小时取决于数据量。4.4 掉盘后恢复镜像数据的实操顺序掉盘事件之后如果你计划恢复某个镜像的数据推荐顺序是先等集群状态恢复HEALTH_OK即所有PG都activeclean。在恢复过程中不要盲目做快照回滚否则会干扰后台数据恢复。等集群健康后用rbd info确认镜像元数据完整。用rbd snap ls确认快照链完整。用rbd diff做一次快照校验确认数据可读。再执行clone或rollback。这套顺序的核心逻辑是先保证基础设施层面数据完整再考虑应用层面恢复。如果你在PG还没恢复的时候就做回滚可能会触发大量读丢失反而拖慢Ceph自身的恢复进度。5. 基于快照的备份与容量管理别让快照反过来卡死集群5.1 快照吃容量的机制COW之外还有ROW快照不是零成本。Ceph RBD快照默认是写时复制COW新写入数据时Ceph会把原始数据复制一份保留到快照区域然后在原位置写入新数据。所以快照越老占用的空间可能越大因为从快照点开始之后的数据变化都累积在这份快照引用里。此外Ceph还支持重定向写时复制ROW这种模式下新数据写到新的位置原始位置保持不变快照引用的数据永远不会被覆盖。ROW的优点是快照性能好不改变原数据地址缺点是长时间不合并的话数据位置碎片化严重读取需要查找多个位置。生产环境里用户不会直接感知COW和ROW的区别但你会看到rbd du输出的空间占用rbd du mypool/mysql-data多建几个快照之后你会发现磁盘使用量暴涨。这不是Ceph的bug而是快照的本质。5.2 给存储池设置配额防止快照洪峰打满数据池快照增长不可控是让很多集群翻车的元凶。尤其是备份脚本有bug或者应用层每次升级都打快照但从不清理几个月后整个存储池被快照数据占满新数据写入直接失败。我的建议是从一开始就给pool设置配额# 限制pool的总容量为10TB ceph osd pool set-quota mypool max_bytes 10995116277760 # 限制pool的最大对象数 ceph osd pool set-quota mypool max_objects 100000000设置配额之后当快照数据把pool塞满时Ceph会拒绝新写入虽然这个行为也会影响正常业务但至少不会让整集群崩溃。更好的做法是配合告警监控在容量达到50%、70%、85%时分别报警。另外我强烈建议在运维手册里写清楚快照保留策略比如核心业务盘保留最近5个快照每个快照保留24小时普通开发盘保留最近3个快照。自动化清理脚本用rbd snap rm按时间戳删除旧快照即可。5.3 定期清理过期快照怎么删、删不动怎么办删除快照本身很简单rbd snap rm mypool/mysql-datasnap_20250118_0300但如果这个快照被clone过且clone镜像没有flatten删除会失败提示快照被保护或者是父快照。这时候有两个选择先解除保护再删除rbd snap unprotect mypool/mysql-datasnap_20250118_0300 rbd snap rm mypool/mysql-datasnap_20250118_0300或者先flatten所有依赖它的clone镜像再执行删除。我在生产环境里碰到过最麻烦的情况是某个基础镜像打了快照然后从这个快照clone了三十多台虚拟机。后续想清理这个快照但clone镜像全部没有flatten导致快照无法删除而基础镜像又不敢动。最终只能让所有虚拟机逐个执行rbd flatten再删除快照。这个过程非常耗时。所以我现在提倡如果clone出去的数据需要长期使用直接flatten短期测试才允许依赖父快照。6. 设计一套可靠的快照与容灾方案时我踩过的那些坑6.1 快照保留策略不是越久越好很多人觉得快照是个好东西留得越多越安全。这是错误的。快照越久对存储空间的占用越大而且恢复性能会随着快照链的加深而下降。Ceph在读取一个长时间未写覆盖的数据块时如果快照链很长需要回溯多个快照层虽然RBD内部做了优化但性能依然有影响。我的建议是数据等级快照频率保留时间说明核心数据库每小时24小时快速回滚误操作核心数据库每天7天支持最近一周的恢复一般业务每天3天避免容量失控测试环境不保快照0用完即删6.2 应用层一致性必须写进SOP快照的创建本身不贵但应用层一致性的配合经常被忽略。我见过很多团队Ceph快照打了但应用数据恢复后无法使用于是得出结论Ceph快照没用。其实问题出在快照的时间点不是应用一致的时间点。运维团队必须和应用团队约定一套标准操作流程比如应用侧先进入维护状态再触发快照最后确认快照创建完成后再恢复业务。这个过程要自动化不能靠人肉指挥。6.3 容灾演练平时不切出事了不敢切RBD Mirroring配置好之后如果半年不做一次切换演练真到灾备切换的时候大概率会出乱子。我的习惯是每季度做一次模拟切换把主集群网络隔离强制提升备集群检查业务数据完整性和性能然后再切回来。这一步看起来没什么技术含量但能暴露很多问题备集群的rbd-mirror进程是不是挂了备集群的容量够不够备集群的存储池配置是否和主集群一致网络高峰期同步是否跟得上这些问题只有在演练中才会暴露。6.4 最后再分享一个小技巧如果你经常做快照清理建议养成写脚本的习惯。Ceph本身不带“按时间自动清理”的功能你需要用rbd命令配合shell脚本自己实现。一个简单的清理逻辑#!/bin/bash POOLmypool IMAGEmysql-data KEEP5 rbd snap ls ${POOL}/${IMAGE} | tail -n 2 | awk {print $1} | sort /tmp/snap_list.txt TOTAL$(wc -l /tmp/snap_list.txt) if [ $TOTAL -gt $KEEP ]; then REMOVE$((TOTAL - KEEP)) head -n $REMOVE /tmp/snap_list.txt | while read snap; do rbd snap rm ${POOL}/${IMAGE}${snap} echo Removed ${snap} done fi这个脚本只做最基本的按数量清理。生产环境建议加上快照时间戳解析按天保留。另外清理动作尽量放在业务低峰期因为删除快照会触发元数据更新如果快照数据量很大也会产生不小的IO压力。我在实际使用Ceph镜像快照的过程中最大的感受是快照和镜像本身都是很成熟的技术真正的风险往往出在流程设计和运维习惯上。把概念理清把命令跑熟把容灾预案做扎实这套存储体系才能真正可靠。