ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

KVM/libvirt外部快照全攻略:创建、回滚与合并收链

KVM/libvirt外部快照全攻略:创建、回滚与合并收链 在 KVM 环境里折腾过快照的人大概率都有过这种经历要升级某个核心服务前想给虚拟机留个后悔药于是顺手敲了virsh snapshot-create-as等真出了问题想回滚才发现要么磁盘文件暴涨要么 revert 根本走不通。我最早踩这个坑是在一个 RHEL 7 的宿主机上当时给一套业务系统做升级前备份用了默认的内部快照结果升级失败后想回滚才发现内部快照对运行中虚拟机的限制一堆镜像文件也跟着涨了不少。后来把 libvirt 的 external snapshot 这套机制彻底吃透之后才真正把“创建”和“回滚”这两个动作从玄学变成了可重复执行的流程。这篇就沿着创建、管理、回滚、合并这条主线把 libvirt 外部快照的完整实操讲清楚环境默认是 RHEL/CentOS 系的 KVM 宿主机管理栈就是 libvirt/virsh。1. 先分清 internal 和 external别让“快照”两个字坑了你1.1 内部快照是怎么回事大多数入门教程里教的virsh snapshot-create-as默认创建的是 internal snapshot也就是内部快照。它把快照数据直接写进 qcow2 镜像文件内部虚拟机的运行状态内存和磁盘状态可以一起保存同时要求在保存内存那一刻把虚机短暂暂停。整个机制听起来很完美但实际用起来有几个硬伤。第一个硬伤是只支持 qcow2 格式。如果你的虚拟机磁盘是 raw 或者用了 iSCSI、LVM 这类块设备内部快照根本做不了。第二个硬伤是镜像文件的体积会快速膨胀因为每次内部快照都会在 qcow2 内部追加新的状态数据快照一多文件体积增长得让你怀疑人生。第三个硬伤也是我最在意的一旦虚拟机已经处于一层层的内部快照之上继续做内部快照、删除中间快照、回滚到指定点这几件事的排列组合很容易触发 qemu 的“当前操作需要重新激活 backing file”之类的报错越复杂的链越难收拾。1.2 外部快照真正改变的是什么external snapshot 的逻辑完全相反它不为原有镜像追加任何数据而是新建一个顶层的 qcow2 overlay 文件让虚拟机从新文件继续读写原来的磁盘文件从此只读、保持不变。命令上只需要给virsh snapshot-create-as加一个--disk-only参数整套切换动作是原子的。举一个实际例子。假设虚拟机 web01 的原始磁盘是/data/images/web01/disk.qcow2执行virsh snapshot-create-as web01 pre-upgrade \ --disk-only \ --atomic \ --description before nginx mainline upgrade执行之后库目录下会多出一个类似disk.pre-upgrade.qcow2的新文件域 XML 里的实际磁盘路径也会自动指向它。此时disk.qcow2变成 backing file内容固定在快照那一刻不再被写入disk.pre-upgrade.qcow2新的活动层虚拟机所有新写入都落在这里虚拟机本身全程不需要暂停业务几乎无感知这才是“给生产系统做升级前保险”的正确姿势原始数据永远有一个不可破坏的基点想回到快照时刻本质就是“把磁盘指针拨回disk.qcow2”而已。1.3 为什么 external 才是可回滚的基础理解 external snapshot首先要建立一条认知快照回滚的可靠程度取决于原始数据是否被完整保留。内部快照虽然也保留旧状态但它是“同一个文件里存了多个版本”版本之间共享元数据一旦某个操作不兼容之前的备份点就成了摆设。外部快照则是“一个版本对应一个文件”底层文件固若金汤回滚时要么切指针要么合并链每一步都是可逆、可验证的。另外external snapshot 也是 KVM 环境里最常见的批量分发玩法的基础维护一个打了补丁和基础软件的标准镜像然后用它加一层 overlay 就能快速孵化出无数子虚拟机这个过程本质上就是给“模板系统”打外部快照。所以无论是单机升级回滚还是“通过 KVM 给多台服务器做系统”external snapshot 都是绕不开的核心机制。2. 创建外部快照命令、参数和文件变化2.1 最小可用命令和必须理解的参数创建一个磁盘级外部快照核心命令就是virsh snapshot-create-as domain snapshot-name --disk-only --atomic三个关键点--disk-only明确只做磁盘快照不保存内存状态。这是 external snapshot 最常用、兼容性最好的形态。--atomic要求所有磁盘要么全部快照成功要么全部失败不会出现“一块盘快照了、另一块盘没快照”的中间状态。多磁盘虚拟机必须加。--description建议每次都写几个月后回来看快照列表没有描述的快照根本记不清当时在干什么。如果虚拟机有多块磁盘比如系统盘 vda 数据盘 vdb可以分别指定快照文件路径virsh snapshot-create-as web01 pre-upgrade \ --disk-only \ --atomic \ --diskspec vda,snapshotexternal,file/data/images/web01/disk.snap1.qcow2 \ --diskspec vdb,snapshotexternal,file/data/images/web01/data.snap1.qcow2不指定 file 时libvirt 会在原镜像所在目录自动生成一个带快照名的新文件命名大致是“原文件名.快照名.qcow2”。生产环境我更推荐显式指定路径把这层信息牢牢攥在自己手里方便后续脚本处理。2.2 快照完成后的三分钟验证快照创建成功不代表万事大吉我每次都会花三分钟做确认防止“快照命令成功但文件没起来”这类隐形问题。先看 libvirt 侧的记录virsh snapshot-list web01 virsh snapshot-info web01 pre-upgrade virsh snapshot-dumpxml web01 pre-upgrade | sed -n /disk/,/\/disk/p再看实际磁盘文件层级virsh dumpxml web01 | grep source file qemu-img info --backing-chain /data/images/web01/disk.pre-upgrade.qcow2正常情况下能看到域 XML 的source file已经指向新 overlayqemu-img info --backing-chain显示新文件 backing file 是原始disk.qcow2。这里有个新手最容易忽略的点原始disk.qcow2虽然从域 XML 里“消失”了但绝对不能删。它是整个链的地基overlay 里没写过的数据都要通过它读取。2.3 一致性--quiesce 的正确打开方式磁盘级外部快照默认是“崩溃一致性”意思是快照时刻磁盘上处于写缓存里的数据可能没来得及落到镜像文件里相当于这台机器瞬间断电后的状态。对普通应用问题不大重启后日志回放就能恢复但对数据库这类对一致性要求高的负载最好配合 QEMU guest agent 把文件系统冻结一下再快照virsh snapshot-create-as db01 pre-maintenance \ --disk-only \ --atomic \ --quiesce--quiesce会调用 guest agent先让客户机内的文件系统达到一致状态freeze快照完成后再解冻thaw。注意如果客户机里没装qemu-guest-agent或者 agent 服务没起来这条命令会直接报错而不是偷偷降级这是对的总比拿一个脏快照强。如果连 agent 都不想依赖另一个土办法是先virsh suspend db01把虚拟机挂起做外部快照再virsh resume db01。代价是业务会有几秒到几十秒的停摆但一致性最稳。我在没有 guest agent 的老系统上用的就是这套组合。3. 管理快照链状态、文件、元数据3.1 virsh 侧常用的查询命令外部快照做得多了虚拟机脖子上就挂了一条快照链。管理链的第一步是看清“现在在哪一层、历史上做过哪些点”。推荐几个我在用的命令# 列表带父子关系 virsh snapshot-list web01 --tree # 当前快照是谁 virsh snapshot-current --name web01 # 查看某个快照包含哪些磁盘文件 virsh snapshot-dumpxml web01 pre-upgradesnapshot-list --tree的输出基本就是一条树状目录能直观看到pre-upgrade之后又有post-upgrade。snapshot-current告诉你当前活动磁盘对应的是哪个快照点。3.2 文件级视角qemu-img 才是最终的裁判libvirt 的元数据只是“台账”真正的数据关系在镜像文件里。检查链完整性时我习惯用qemu-img info --backing-chain /data/images/web01/disk.post-upgrade.qcow2 qemu-img check /data/images/web01/disk.post-upgrade.qcow2--backing-chain会把从当前活动层到最底层 backing file 的整条链展开每一层显示各自的 backing file 路径。看到这里你也就理解了为什么说“备份必须整条链一起备”overlay 里没写过的块全要从 backing 读backing 没了overlay 就是张空头支票。再强调一个容易出事的细节qemu-img info默认可能会尝试获取共享锁碰上正在运行的虚拟机磁盘会报“Failed to get shared write lock”。这时候加-U参数以不加锁的方式只读查看qemu-img info -U --backing-chain /data/images/web01/disk.post-upgrade.qcow23.3 删除快照不能乱来外部快照的删除和内部快照完全不同。直接virsh snapshot-delete web01 pre-upgrade大概率会失败因为 libvirt 会提示这种操作需要先做 merge。实际生产中我总结出两条安全路径先合并、再删除运行virsh blockcommit或virsh blockpull把链收成单层确认文件级数据和虚拟机状态正常之后再virsh snapshot-delete web01 snapshot此时删除是干净利落的。只删台账、保留文件virsh snapshot-delete web01 pre-upgrade --metadata只移除 libvirt 的快照记录磁盘文件原地保留。批量做备份归档时这条命令非常有用因为文件已经拷贝走了没必要让 libvirt 继续盯着一个早已不在现场的引用。千万注意如果快照文件还在链上被上层依赖就别乱删文件否则上层 overlay 会对不上 backing轻则虚拟机起不来重则整条链数据不可读。我处理这类事的原则是“台账可以删文件必须等确认无引用再动”。4. 回滚 external snapshot 的完整实操4.1 一个反直觉的事实snapshot-revert 不总是能救你很多人的第一反应是virsh snapshot-revert web01 pre-upgrade我特别想强调libvirt 对 external snapshot 的 revert 支持历来不是默认就能用的很长一段时间里官方文档都直接写明“reverting to an external snapshot is not supported”少数新版本的实现也附带各种条件。生产环境里如果你贸然执行这条命令很容易遇到“当前磁盘层和快照层不一致”“快照缺少内存状态无法恢复”之类的问题。原因也好理解外部快照的“回滚”在底层不是一个原地操作而是把域 XML 的磁盘指针从当前活动层挪到目标文件上中间还有快照元数据要不要保留、后续链怎么处理的问题。与其赌版本不如掌握一套完全可控的手动回滚流程。4.2 标准手动回滚流程假设当前链是这样disk.qcow2 ← pre-upgradedisk.pre-upgrade.qcow2 ← post-upgradedisk.post-upgrade.qcow2当前活动层现在要回滚到pre-upgrade也就是放弃post-upgrade之后的一切写入。步骤如下。先把虚拟机正常关机virsh shutdown web01 # 等待彻底关机 virsh domstate web01 # 输出应为 shut off接着决定要不要保留原始链。如果后续还想回来继续用post-upgrade的状态就把目标层先复制一份再切换cp -a /data/images/web01/disk.pre-upgrade.qcow2 /data/images/web01/disk.pre-upgrade.revert.qcow2 virsh edit web01 # 把 source file/data/images/web01/disk.post-upgrade.qcow2/ # 改成 source file/data/images/web01/disk.pre-upgrade.revert.qcow2/然后启动虚拟机virsh start web01启动后先检查业务、日志、数据库恢复情况确认无误后再清理后续层。清理后续层的命令是virsh snapshot-delete web01 post-upgrade --metadata rm -f /data/images/web01/disk.post-upgrade.qcow2为什么要“复制一份再切换”而不是直接把指针改回disk.pre-upgrade.qcow2因为disk.post-upgrade.qcow2的 backing 还指向disk.pre-upgrade.qcow2如果你把虚拟机继续跑在原始pre-upgrade文件上就会往里写新数据把原文件内容改掉将来post-upgrade这条链的数据就全错了。复制一份原链完好无损等于把“回滚”和“保留现场”两件事同时做到了。4.3 彻底放弃中间所有状态直接回到底层如果升级彻底失败连pre-upgrade中间态都不想要只想回到最初的disk.qcow2流程更简单virsh shutdown web01 virsh edit web01 # source file 改回 /data/images/web01/disk.qcow2 virsh start web01 # 确认正常后 virsh snapshot-delete web01 post-upgrade --metadata virsh snapshot-delete web01 pre-upgrade --metadata rm -f /data/images/web01/disk.pre-upgrade.qcow2 /data/images/web01/disk.post-upgrade.qcow2改回底层之后disk.qcow2就重新变成可写活动层了整条链回到最初状态。这里有一点值得提醒如果被丢弃的层里还有一些有价值的数据比如临时报错日志、导出了一半的报表先别删文件可以把那个 overlay 以第二块盘的形式挂载给虚拟机把东西拷贝出来再清理。具体做法是用virsh attach-disk把disk.post-upgrade.qcow2挂进去或者用qemu-nbd在宿主机上直接挂载读取数据抢救出来再走删除流程。5. 合并与扁平化让快照链收口5.1 blockcommit把上层变更并回底层回滚解决的是“要不要保留后续变更”的问题而合并解决的是“快照链太长了想收成一层”的问题。日常用得最多的是virsh blockcommit方向是把活动层的数据往前合并到 basevirsh blockcommit web01 vda \ --base /data/images/web01/disk.qcow2 \ --top /data/images/web01/disk.post-upgrade.qcow2 \ --wait --verbose --pivot --delete解释一下参数--base合并的目标层通常是链底的原始镜像--top合并的起始层可以是当前活动层也可以是中间某一层--wait等待合并完成重要不加这条命令很快就返回到 shell但后台还在搬数据--pivot合并完成后让虚拟机磁盘指针切到 base--delete合并成功后删除被并掉的中间/顶层文件执行成功后整条链变成disk.qcow2单层里面已经包含了 pre-upgrade 和 post-upgrade 两个阶段的所有数据snapshot 元数据也只剩一条空壳可以再virsh snapshot-delete web01 post-upgrade --metadata收尾。blockcommit 能在线执行不用关机这是它最大的价值。5.2 blockpull从底层把数据拉到当前层如果想把链往另一个方向收——保留当前活动层把底层全部塞进 overlay 里让活动层“自包含”——就用virsh blockpullvirsh blockpull web01 \ --path /data/images/web01/disk.post-upgrade.qcow2 \ --wait --verbose执行完之后disk.post-upgrade.qcow2变成独立的单层镜像不再依赖 backing file。qemu-img info查看时就没有 backing chain 了。这个操作对“要把镜像从这台宿主机搬到另一台”“要拷贝一份脱离链的完整镜像”这类场景特别有用因为带链的镜像拷走了 base 没跟着走就废了。blockpull 的代价是磁盘占用瞬间变大因为底层数据要全部复制进活动层而且 I/O 压力集中在一块盘上。做之前先df -h确认剩余空间够一般我预留镜像体积两倍以上的余量否则搬到一半磁盘写满虚拟机 I/O 就会卡住。5.3 什么时候用 commit、什么时候用 pull每次有人问这两兄弟怎么选我都直接给这个对照表场景推荐操作原因快照链太长想回到单层且保留链底镜像blockcommit数据并入 baseoverlay 释放体积小要把当前镜像拷走、迁移或归档blockpull活动层自包含拷贝后不依赖链虚拟机已关机离线收链qemu-img commit / rebase不占在线 I/O最直接只想回滚不想要后续链手动切指针 删除 overlay无需搬数据速度最快特别注意blockcommit并回底层之后位于合并区间内的快照点就“消失”了不能再回滚到那些中间点。做合并前先问自己一句这些中间快照还有没有再回去看一眼的价值有的话先复制文件再合并。6. 踩坑记录与生产建议6.1 guest agent 不装quiesce 就会当场翻车我接手过一套系统运维团队写了个脚本定时做外部快照但客户机里根本没装qemu-guest-agent。某天脚本加了--quiesce之后开始报错大家还以为是库的问题查了一圈才发现脚本根本没生效之前的“定时快照”其实一直在裸跑。这里必须记死一条加了--quiesce的命令只要客户机里 agent 不正常命令必失败绝无例外。所以生产环境要么把 qemu-ga 的安装纳入系统初始化流程要么就不要轻易加--quiesce改用应用层协调或短时挂起别让脚本存在“看起来成功、实际没达到一致性”的既定事实。6.2 备份必须按整条链设计外部快照最隐蔽的坑在备份环节。单独备份活动层文件没有任何意义因为没写入的块都要靠 backing file 撑腰。我见过不止一次运维只把/data/images/web01/disk.post-upgrade.qcow2拷贝到备份机宿主机原始盘坏掉之后恢复出来的镜像缺 backing虚拟机直接起不来。正确的做法是要么对整条链做备份base 每一层 overlay要么先virsh blockpull把链拉平成单层再备份要么用qemu-img convert把链导出成独立镜像。三者选哪种取决于你对备份时间窗和空间成本的预算但底线是“备份集里必须自包含不能依赖宿主机上的其他文件”。6.3 快照链不是越长越好外部快照做起来太轻量了很容易让人上瘾一个虚拟机挂十几层 overlay。问题在于链越长读 I/O 就越深当前层没命中的数据要一层一层回溯到 base随机读延迟会明显变高同时任何一层文件损坏整条链都受影响。所以我的习惯是常规升级场景前置快照 验证通过后一周内必须收链commit 或 pull快照层数控制在三层以内多出来的中间态该并就并每隔一段时间跑一次qemu-img check检查链上各文件健康状态6.4 删错文件之后的补救顺序人总有手滑的时候尤其是有多个同名前缀的 qcow2 文件躺在同一目录里。如果发现虚拟机突然进入异常状态首先别慌也别顺手重启宿主机按顺序排查virsh domstate web01看是不是变成了paused并带 I/O 错误virsh domblklist web01 --details确认当前各磁盘对应的文件路径确认文件是否还在如果只是路径被改坏了virsh edit改回正确路径再virsh resume如果文件真的丢了看有没有其他层可以作为 backing 重建或者从备份恢复这套排查顺序的核心思想是先读状态、再诊断、最后再操作千万别在没确认卷在哪、链断在哪的情况下盲目virsh destroy。6.5 版本差异决定功能边界同样是 KVM 宿主机RHEL 7 自带的 libvirt 老版本和 RHEL 9 的新版本对外部快照、在线 blockcommit、revert 的支持程度并不完全一样。我踩过的最典型一个版本坑是在老版本上点virsh snapshot-revert对 external snapshot 直接不支持而换了新版本后行为又有变化。所以在生产环境大规模使用快照之前先在自己的版本组合上把创建、回滚、合并三个动作完整跑一遍记下哪些命令能用、哪些会拒绝并把这个“版本行为记录”写进运维手册。我个人目前的习惯是升级前打一个外部快照验证完业务后立刻 blockcommit 收链让线上永远保持“单层镜像 极少量快照台账”的简洁状态。快照这东西设计得再好也只对纪律严明的人可靠——系统的稳定性最终还是靠操作习惯撑起来的。
返回列表