ARTICLE DETAIL

资讯详情

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

QEMU/KVM快照实战:virsh与qemu-img操作详解与避坑指南

QEMU/KVM快照实战:virsh与qemu-img操作详解与避坑指南 做虚拟化运维这些年QEMU/KVM 环境里的快照是我用得最勤的功能之一。调试内核、测试软件包、给客户机做系统升级手滑搞崩了一个快照就能让你从“白血病患者”恢复到“刚体检完的健康人”。这篇文章就把我在实际操作中怎么用 virsh 和 qemu-img 给 QEMU/KVM 客户机做快照、什么时候用哪种方法、那些文档里不会写的坑一次性说清楚。内容既覆盖刚接触虚拟化的新手需要的基础也包含已经会敲命令但想搞明白底层原理的老手关心的细节。1. 先弄清快照的底层原理和分类1.1 内置快照与外置快照的本质区别快照在虚拟化里的含义简单说就是在某个时间点把客户机的磁盘状态甚至内存状态完整地“记一笔”。这里最关键的是磁盘层怎么实现而这又取决于镜像格式。QEMU/KVM 最常用的格式是 qcow2它原生支持快照而 raw 格式本身就是一块裸数据想在上面做原生快照几乎不可能只能靠 LVM 快照这类外部存储层方案。qcow2 能支持快照靠的是 COWCopy-On-Write写时复制机制。这个机制可以这么理解你有一本纸质台账里面记着各种数据COW 就是在你动笔改某一页之前先给这一页拍一张照保存到抽屉里然后再改动。快照记录的就是这些“被改动前的旧数据”。所以你哪怕把当前数据改得面目全非只要拿着快照记录就能把被改过的数据块还原成拍照那一刻的样子。内置快照和外置快照的区别主要在于快照数据存在哪里对比项内置快照外置快照存储位置全部保存在同一个 qcow2 文件内部独立生成一个新的 qcow2 覆盖文件创建速度快但要改写原镜像内部结构极快几乎瞬间完成只需要新建文件链式关系内嵌在单文件里的快照表中base 镜像 overlay 覆盖文件组成链条恢复方式通过 qemu-img / virsh 直接在当前文件上回滚把覆盖文件废弃或合并回 base管理复杂度单文件便于拷贝迁移多个文件需要一起管理链条容易乱适用场景模板镜像、开发调试、单机离线快照生产环境在线快照、需要零停机的场景内置快照的优点就是干净整个虚拟机就一个 qcow2 文件拷走、备份、换机器都方便。缺点是快照多了以后这个文件内部结构会变得复杂而且做在线内存快照时QEMU 要把整个内存镜像塞进同一个文件里体积会急剧膨胀。外置快照则把“基础盘数据”和“改动增量数据”拆开了越写越多的是 overlay 文件base 可以始终保持原始状态不可变。这样一来即使 overlay 哪天出了问题base 依然能作为干净基线使用。1.2 在线快照与离线快照的差异除了内置/外置的区别还要搞清楚在线快照和离线快照。离线快照就是在客户机关机状态下打快照这时候磁盘上没有进程在写数据文件系统处于一致状态快照内容干净可靠。恢复的时候也不会遇到什么奇怪问题这是我最推荐也最常用的方式。在线快照则是客户机运行中直接做快照。virsh 默认做快照时会把内存状态一起抓下来这意味着 QEMU 需要短暂暂停客户机的执行把内存镜像写入磁盘再恢复执行。暂停时间可能只有几百毫秒也可能长达数秒具体取决于内存大小和磁盘写入速度生产环境里这是一个需要注意的停顿窗口。在线快照还有一个更隐蔽的问题文件系统一致性。虚拟机里跑着 MySQL、Redis 这类对持久化很敏感的服务内存 cache 里可能堆积着大量尚未落盘的数据。如果这些脏数据还没刷到磁盘快照恢复后就会出现数据回退、甚至文件系统损坏。做在线磁盘快照之前最好在客户机内部先执行 sync或者配合 QEMU Guest Agent 的 fsfreeze 冻结文件系统确保所有缓存都先落盘。这也是为什么很多生产环境的自动化脚本里做快照前都要先走一步“冻结文件系统”的操作。2. 实操用 virsh 命令管理 libvirt 快照2.1 创建快照snapshot-create-as 的完整参数如果你是通过 libvirt 管理 KVMvirsh 是操作快照最顺手的入口。我最常用的命令是virsh snapshot-create-as它能在指定域名下创建以名字标记的快照方便之后查找和恢复。一条完整的离线快照命令长这样virsh shutdown testvm virsh snapshot-create-as testvm clean-install --description 刚装完系统未做任何配置 virsh start testvm第一行是优雅关机等 guest 完全停止后再打快照这是做离线快照的标准动作。第二行创建名为 clean-install 的快照并附带描述信息方便记忆。最后一行恢复启动。整个过程逻辑清晰适合手工操作。snapshot-create-as的参数比较多实际中挑着用就行关键参数整理如下参数作用使用场景--disk-only只做磁盘快照不保留内存状态在线做外置快照时常用--atomic要求快照操作要么全部成功、要么完全不执行追求强一致性的自动化脚本里--quiesce配合 guest agent 冻结文件系统后做磁盘快照在线快照且客户机运行着数据库等应用--memory-only只保存内存状态不保存磁盘状态需要保存当时运行时现场但磁盘变化不太关心--xml使用 XML 文件定义快照的详细内容高级定制场景需要特别注意--atomic选项是较高版本 libvirt 才支持的早期版本没有。低版本上如果强行加这个参数会直接报错所以我一般建议先查一下自己环境里的 libvirt 版本再决定参数能不能用。--quiesce则依赖 QEMU Guest Agent 已经安装在客户机里并且 virtio 通道正常通信否则参数会被静默忽略看起来快照成功了实际文件系统并没有被安全冻结这种“表面稳定”比直接报错更可怕。另外一个实操经验给快照起名千万别拍脑袋。我见过有人用 date、data1、test 这种名字连续打了几十个快照两周后想回滚自己都分不清哪个该用。最稳妥的命名约定是“日期-用途”比如20250105-before-upgrade既保留了时间线又点明了这是干什么用的快照。配合 description 参数写上业务场景后续任何人接手运维都能看懂。2.2 列出、回滚与删除快照快照建好之后查询信息用virsh snapshot-listvirsh snapshot-list testvm --tree--tree参数会以树状结构显示出快照之间的父子关系。如果一个快照是在另一个快照的基础上创建的这种层级关系在排查问题时特别有用。virsh snapshot-info testvm --current可以查看当前所在快照的信息确认自己挡在哪个时间点上。回滚快照的命令是snapshot-revertvirsh snapshot-revert testvm clean-install这里有几个容易踩的坑。如果快照里包含了内存状态回滚时默认会用快照里的内存状态去恢复客户机。但如果客户机当前正处于运行状态回滚会面临状态冲突这时需要显式指定--running或--paused告诉 libvirt 回滚后客户机应该以什么状态启动。更安全的做法是回滚前先把客户机关机让回滚操作在干净的环境下进行然后再启动。虽然这比在线回滚多花了一点时间但成功率和可控性高得多。删除快照用virsh snapshot-delete domain snapshot-name。如果快照有子快照直接删父快照会失败除非加上--children把子快照一起删掉或者使用--metadata把快照关联的元数据清干净。删除当前活动快照时后续的快照链会重新组织这里务必想清楚再动手因为误删会导致与其关联的增量数据无法恢复。2.3 一个完整的离线快照流程示例下面给出一段我平时在半自动脚本里使用的离线快照流程每一步都有明确目的# 1. 优雅关闭客户机等待进程退出、缓存落盘 virsh shutdown testvm while [ $(virsh domstate testvm) ! shut off ]; do sleep 2; done # 2. 创建快照名字带日期前缀便于后期识别 SNAP_NAME$(date %Y%m%d)-before-yum-update virsh snapshot-create-as testvm $SNAP_NAME --description 升级前的备份点 # 3. 确认快照创建成功信息无误 virsh snapshot-list testvm --name # 4. 启动客户机 virsh start testvm这段脚本看似简单但每一步都带着问题意识。第一步的 while 循环是关键virsh shutdown只是发送 ACPI 关机信号guest 内部可能需要几十秒才能真正退出直接 sleep 固定时间往往不够可靠轮询 domstate 才是稳妥做法。第二步创建快照前用户已经确保磁盘处于一致状态所以不需要--quiesce。第三步肉眼确认快照已经出现在列表里属于“操作后验证”的习惯这个习惯能省掉很多后续排查的麻烦。3. 进阶qemu-img 与块设备级快照操作3.1 qemu-img 的快照命令详解当不再经过 libvirt直接使用 QEMU 的镜像文件时qemu-img就是操作内置快照的主力工具。它最典型的三个子命令# 创建快照 qemu-img snapshot -c clean-state /var/lib/libvirt/images/testvm.qcow2 # 列出快照 qemu-img snapshot -l /var/lib/libvirt/images/testvm.qcow2 # 回滚到指定快照 qemu-img snapshot -a clean-state /var/lib/libvirt/images/testvm.qcow2-c即 create-l即 list-a即 apply。这三个命令都直接作用于 qcow2 文件内部的快照表不需要虚拟机管理器的配合所以特别适合在不启动 libvirt 的纯 QEMU 环境里使用。qemu-img 的快照命令只对磁盘层生效不包含内存状态。如果虚拟机正在运行直接跑qemu-img snapshot操作同一个镜像文件很容易造成镜像损坏或数据不一致所以这条命令的限制场景也很明确离线操作时用在线操作时别碰。qemu-img 还有一层更底层的角色。在批量克隆虚拟机、制作基础模板时qemu-img 是行业内最常用的工具。用 qemu-img 把一块装好系统的 qcow2 做成黄金模板打上一个 clean 快照后续所有新虚拟机都从这份模板复制出来。这种场景里qemu-img 快照命令的价值是“定义一份不可变的基线状态”。3.2 外置快照与 blockdev-snapshot 的取舍外置快照可以手动创建核心命令是qemu-img create -f qcow2 -b /var/lib/libvirt/images/testvm-base.qcow2 /var/lib/libvirt/images/testvm-overlay.qcow2-b指定 backing file基础镜像新建的 overlay 会记录自创建以来发生的所有改动。把 overlay 作为虚拟机的当前磁盘继续使用就形成了一个“基础盘不变、增量写新盘”的外置快照形态。这种方案的创建时间几乎为零非常适合大镜像的快速快照。代价是文件数量变多而且一旦把 overlay 和 base 的对应关系搞乱恢复就困难得多。所以我把这个方案限定在“确认自己清楚基础镜像是什么、改动增量是什么”的前提下使用。多了一层 libvirt 或 QEMU 监控台之后还有更规范的做法。QEMU 的 QMPQEMU Machine Protocol里提供了blockdev-snapshot它能在 QEMU 进程的块设备写入路径上直接做原子切换从技术层面避免手动文件切换时可能出现的竞态。使用 QMP 一般通过 socat 连接 QEMU 的 QMP socket然后发送 JSON 格式指令。如果你正在做 QEMU/KVM 相关的底层开发或者需要通过脚本对 QEMU 做细粒度控制blockdev-snapshot 是值得深入了解的方向。相比之下libvirt 的virsh blockcopy、virsh snapshot-create-as --disk-only更像是对外封装好的“安全入口”。外置快照还有一个不得不提的后续问题合并链。创建多个外置快照后base、overlay1、overlay2 一层叠一层链越深读性能越差管理越麻烦。合并用qemu-img commitqemu-img commit /var/lib/libvirt/images/testvm-overlay.qcow2这条命令会把 overlay 中的数据修改写回 base然后 overlay 文件就可以从链里移除了。不过在 commit 之前务必确认没有虚拟机正在使用这些文件否则会导致镜像损坏。链合并是快照运维里最耗费精力的一环也是区分“会用快照”和“会管快照”的分水岭。3.3 快照链合并与清理快照链一深事情就开始变得复杂。比如 libvirt 环境里virsh blockcommit testvm --base vda --top vda --wait --verbose可以把当前活动的顶块数据提交到底层减少链的长度。libvirt 会把这个过程封装好不需要手工处理 QMP JSON。手动用 qemu-img 操作则需要更多注意合并前确认虚拟机已关闭且没有其他进程打开这些镜像文件。合并后确认 overlay 文件可以安全删除或归档。如果有后续 overlay 指向当前正在合并的 overlay需要把 backing file 重新指向上层否则链就断了。快照链清理的另一个关键点是空间。qcow2 文件支持稀疏文件特性删除快照后被释放的数据块不一定真正还给存储文件可能在物理层面仍然占着空间。在生产环境里我见过某个虚拟机打了几十个快照后来逐个删除但宿主机的存储空间却没有明显减少这就是稀疏文件在“占用空洞”上的典型陷阱。遇到这种情况可以考虑用qemu-img convert把 qcow2 重新整理一遍把无用的空洞去掉但这个过程同样需要停机操作。4. 结合应用场景的快照运维实践4.1 开发调试场景交叉编译与内核调试做嵌入式开发、内核调试的人十有八九会用 QEMU 模拟非本机架构比如在 x86 主机上用qemu-system-aarch64跑 ARM64 客户机。这种场景下快照的用法非常典型每到一个稳定的代码编译状态就打一个快照作为“里程碑”之后无论怎么折腾内核参数、驱动模块、设备树跑崩了都能秒退回里程碑状态重头再来。我认识不少做内核调试的同行日常流程就是 VSCode 远程连到开发机上QEMU 启动 ARM64 虚拟机GDB 连接 QEMU 的 gdbstub 端口做断点调试。如果不用快照保护环境每改一次内核参数都可能把虚拟机的文件系统弄坏然后重新部署环境、重新交叉编译、重新拷贝 rootfs一上午的时间就这么蒸发了。有了快照试错成本直线下降打好断点前先打快照实验失败就回滚实验成功才继续往下推进。开发环境的特殊之处在于快照的语义更接近“保存现场”而不是“备份数据”。所以这类场景里哪怕只是内存快照也很有价值。比如调试一个偶现的并发问题抓到现场后做一个--memory-only快照之后可以反复分析这个内存状态而不必担心现场因磁盘写入被破坏。4.2 服务器与生产环境的快照策略给服务器做系统、扩容磁盘、迁移数据这类高危险操作正确的姿势是先打一个快照再动手。很多人习惯直接操作等出了问题才发现没有后悔药。这个教训我亲眼见过不止一次。有一位同行给一台生产服务器做系统升级操作到一半发现新内核和旧驱动不兼容系统直接起不来。因为没有快照最后只能从备份里恢复折腾了好几个小时才把服务带回可用状态。如果当时花一分钟打个快照回滚只需要几十秒。生产环境中快照不能替代备份这个认知必须刻在脑子里。快照文件通常和虚拟机的镜像文件在同一台宿主机、甚至同一块存储上存储坏了快照和原盘一起消失。所以生产环境里的快照策略我的建议是操作场景快照方式保留时间注意事项日常小版本升级内置快照验证通过后删除升级后做基本验证再清理旧快照内核升级 / 系统迁移外置快照至少保留一周合并时间安排在业务低峰期存储路径变更外置快照全程保留至迁移完成迁移期间不可删除 base日常例行维护不建快照-配合备份系统做数据兜底是不是所有操作都必须打快照我的建议是分场景。高风险、影响面大的操作必须打无风险、可重复执行的例行操作不必打因为快照本身也占用空间和产生管理成本。4.3 Windows 客户机与共享目录配合Windows 客户机在 QEMU/KVM 环境里也非常常见。很多人为了方便会用 virtiofs 或者 SMB 共享文件夹的方式让 Windows guest 直接挂载宿主机目录。这种共享目录的挂载点状态在做快照时需要额外留一个心眼。如果 Windows guest 里某个网络驱动器或者 virtiofs 挂载点正处于读写状态快照恢复后这个挂载点对应的文件内容和宿主机的实际文件很可能不一致。Windows 的文件系统缓存行为比 Linux 更激进缓存里的数据没及时写回宿主机快照恢复的只是 guest 视角里的“目录影印”并非宿主机里的真实文件。这个问题即使在快照包含内存状态的情况下也无法完美解决因为外挂的目录本质上不属于虚拟磁盘的数据范围。我在实际操作中的处理方式很直接做 Windows 客户机快照之前先在 guest 内部把共享目录里打开的应用程序关掉然后执行一次强制同步。Windows 下可以用工具触发 flush或者直接断开再重新挂载共享目录。完成之后再创建快照这样得到的快照中共享目录里的数据引用才是干净一致的。这个细节文档里很少提到只有被坑过的人才懂为什么。5. 常见问题与排查技巧实录5.1 快照创建失败或卡住快照创建失败的常见原因我整理成一张速查表方便直接对号入座异常现象常见原因处理方式snapshot-create-as报错内存不足在线快照尝试保存内存镜像但磁盘空间不够清理镜像所在存储或改用--disk-only创建快照时客户机一直卡住QEMU 暂停客户机保存内存状态的时间较长等待或强制中断评估是否改用离线快照Could not open disk imageoverlay 丢失或 backing file 路径被移动检查磁盘镜像路径和 backing file 关联快照列表里看不到刚创建的快照libvirt 元数据与磁盘镜像不一致重启 libvirtd 前先确认磁盘状态必要时手动 metadata 修复--quiesce没有真正冻结文件系统客户机里没有安装 QEMU Guest Agent安装 qemu-guest-agent 并确认 virtio 通道正常卡住的问题最常见于在线快照且内存特别大的虚拟机。我之前遇到过一个 64GB 内存的客户机做在线快照的时候整整卡了差不多一分半钟服务当然跟着断了。从那以后我的原则变成了只要客户机能短暂停机就绝不使用在线快照在线快照只留给确实不允许停机的核心业务。5.2 恢复后客户机启动异常快照恢复后客户机起不来或者文件系统报错也是绕不开的问题。恢复后卡在启动阶段大概率是文件系统不一致。尤其是在线做的磁盘快照没有冻结文件系统恢复时 ext4 或 xfs 的日志里有大量未重放数据。处理方法是进入急救模式或 LiveCD 引导对根分区执行 fsck。xfs 文件系统用xfs_repairext4 用fsck.ext4别搞混了。另外一类情况是恢复快照后客户机的网络配置、磁盘设备名错乱。设备名错乱在 Linux guest 里比较常见因为恢复后内核枚举设备的顺序和之前不同旧系统使用eth0这种名称时最容易踩坑。现在主流用ens*或enp*这种基于 PCI 位置的命名一般不容易出现这个问题但老镜像就难说了。建议在客户机里提前配置好 udev 规则把网卡名固定下来给快照恢复加上一层保险。还有一类问题是 UUID 冲突。如果你从快照克隆出多台虚拟机它们的根文件系统或者磁盘的 PARTUUID 完全相同。两台机器同时接入同一个网络时可能会因为文件系统 UUID 相同导致 mount 问题。这种情况下建议在克隆后重新生成 UUID。这也是为什么快照更适合“回滚”而不是“克隆”克隆衍生出的副本需要额外处理身份信息。5.3 磁盘性能下降与快照文件膨胀快照用得越久文件越大性能越慢这是正常现象。qcow2 的 COW 机制天然存在写入放大问题每次修改一小块数据可能要先读取原来的数据块、写入快照记录、再写入新数据一次写操作被放大成多次读写。测试过的环境里快照链过深时持续 4K 随机写入性能可能下降一半以上。性能下降的另一个来源是碎片化。qcow2 文件随着反复写入、删除、覆盖数据块在物理层面变得零散随机 IO 性能直线下降。快照回滚之后qocw2 文件内部的映射表也会出现大量空洞进一步加剧磁盘碎片问题。解决方案也不复杂定期做链合并和镜像整理。链合并不是写脚本定期跑就行还要考虑业务低谷期因为 commit 期间虚拟机不能正常提供读写服务。整理镜像使用qemu-img convert把旧 qcow2 转换成新的 qcow2相当于把碎片化数据物理重排效果立竿见影。不过这条命令也是重量级操作必须先停机。问题表现根因解决思路虚拟磁盘写入慢COW 写入放大 overlay 链过深做 blockcommit 合并链或者定期 convert 整理qcow2 文件体积异常膨胀内部快照残留大量旧数据块删除无用快照必要时 convert 重做镜像删快照后宿主机磁盘空间没释放qcow2 稀疏文件无法自动收缩使用 convert 或 qemu-img measure 评估真实占用overlay 文件无法删除有客户机进程还在使用该文件先停虚拟机再清理文件能用默认参数跑通和知道为什么这么跑是两码事。我见过太多人把快照当成万能保险出了问题就回滚然后重试却从不思考这次回滚会不会把自己之前提交的数据也一起带走。快照是一个基于某个时间点的一致性状态它记录的只是那台虚拟机的数据处理现场不代表你的所有业务数据都安全落地。最后再分享一个小技巧无论哪种快照方案打完快照先别急着做操作先启动客户机、检查关键服务、确认系统日志正常再开始真正的危险操作。多花这几分钟能帮你判断这次快照到底打没打干净是不是一个可用的回滚点。这个习惯养成了你的快照运维才算是真正入门了。
返回列表