
我花了不少时间实测QEMU/KVM虚拟机快照今天把操作链路和取舍逻辑一次性说清楚。很多人对虚拟机快照的理解还停留在拍个照、能还原的层面真到生产环境上手才发现内部快照、外部快照、磁盘快照、内存快照根本不是一回事选错了存储格式直接拍不出快照回滚的时候又发现虚拟机状态对不上。这篇博文就围绕如何为QEMU/KVM客户机创建快照展开覆盖从原理、环境检查、virsh和qemu-img实操到回滚、删除、踩坑的全流程适合刚接触虚拟化的运维新手也适合已经在用libvirt但没系统梳理过快照机制的同行。1. 快照的本质先搞清楚你拍下的到底是什么快照这个词在QEMU/KVM语境下其实承载了三层不同的东西很多人混为一谈后面操作就踩坑。磁盘快照是最核心的部分。它把虚拟机磁盘在某一时刻的内容保存下来。QEMU的qcow2格式天然支持内部快照它的原理是写时复制Copy-On-WriteCOW快照创建之后新的写入会落到新的数据块原始数据块被标记为快照的一部分。这样一来快照点的磁盘状态就被冻结住了而虚拟机继续运行不受影响。类比一下就是给一份正在编辑的文档做了一个版本备份之后你随便改随时能退回保存点。虚拟机状态快照则是把CPU寄存器、内存内容、设备状态全部保存到一个文件里。这相当于把整台运行中的机器暂停并冷藏起来。virsh命令里的--memspec参数就是干这个的。如果你拍快照时不带内存状态只保存磁盘内容那么回滚的时候虚拟机其实回到的是关机或上次正常停止的状态而不是快照那一刻的运行状态。这个区别在实际操作中非常容易造成误会。虚拟硬件配置快照则记录域XML配置包括内存大小、CPU拓扑、设备列表等。它保证的是你回滚后虚拟机的硬件配置也回到当时的模样。内部快照和外部快照是另一个关键维度。内部快照存储在qcow2镜像文件内部不需要额外生成文件管理方便但缺点是镜像文件会持续膨胀而且一旦镜像文件损坏所有快照一起报销。外部快照则把快照状态写到另一个qcow2文件里原镜像变成后端镜像backing file新文件作为前端覆盖层overlay。外部快照更灵活支持在线热备、增量备份是生产环境更推荐的方向。存储格式决定了你能用哪一种快照。qcow2支持内部快照和外部快照raw格式不支持内部快照LVM逻辑卷快照是存储层面的方案跟QEMU本身无关。很多人上来就拍快照结果发现磁盘用的是rawvirsh直接报错就是这个原因。2. 环境检查动手之前先确认四件事拍快照的操作本身不复杂但前置条件漏掉一个后面就是连环坑。我建议按以下顺序检查环境。第一确认libvirt和QEMU版本。virsh --version和qemu-img --version各自看版本号快照功能的完整性和稳定性跟版本直接相关。老版本QEMU的外部快照、块克隆功能都有不少bug如果版本过低建议先升级再玩快照。我见过CentOS 7自带的QEMU 1.5.x时代版本处理外部快照的blockcommit功能问题很多后来升级到QEMU 4.x才稳定。第二确认虚拟机磁盘的格式和总线。执行以下命令查看磁盘信息virsh domblklist vm-name输出会显示目标设备和对应的磁盘路径比如vda对应的可能是/data/vms/vm-name.qcow2或者/dev/vg0/vm-name。接着用qemu-img检查格式qemu-img info /data/vms/vm-name.qcow2如果file format显示qcow2内部快照没问题如果显示raw就要改走外部快照或LVM快照路线。第三确认存储后端的空间。内部快照和外部快照都需要额外空间。内部快照导致qcow2文件膨胀膨胀幅度取决于快照后新写入的数据量外部快照则是新生成一个覆盖层文件。如果你的宿主机存储分区分分钟就要满了先扩容再拍快照否则快照写一半直接失败。第四确认虚拟机配置里没有使用直通设备或特殊设备。PCI直通、SR-IOV、USB重定向、大页内存这类配置对快照的兼容性影响很大。带内存状态的快照碰到PCI直通设备经常会出现恢复后设备状态不一致的问题。我建议有直通设备的虚拟机只做磁盘快照不要试图连内存状态一起保存。3. 用virsh创建内部快照最常用的操作路径libvirt的virsh命令是管理快照最顺手的方式。内部快照分两种情况只拍磁盘或者连内存一起拍。3.1 只快照磁盘内容这是最常用的一种拍法命令如下virsh snapshot-create-as vm-name snap-before-upgrade --disk-only --atomic参数说明vm-name是虚拟机域名snap-before-upgrade是快照名自己起一个好认的名字--disk-only表示只对磁盘做快照跳过内存状态--atomic保证快照操作要么全部完成要么不产生任何遗留状态这个命令执行很快因为它本质上是让QEMU在qcow2文件内部记录一个状态点然后继续COW。虚拟机无需关机也无需暂停业务无感知。3.2 连内存状态一起快照如果需要保存当前运行状态让回滚之后虚拟机回到你拍快照那一刻的现场需要加内存参数virsh snapshot-create-as vm-name snap-with-memory \ --disk-only \ --memspec file/data/vms/vm-name-memory.snap \ --atomic注意这里有一个容易踩坑的点--disk-only和--memspec配合使用libvirt会把内存状态单独存到external file磁盘快照存到qcow2内部。回滚时可以恢复到内存状态但前提是你得把qcow2里的磁盘快照和memory文件都对齐到同一个时间点。实际上我更推荐的做法是分两步先把虚拟机暂停再做带内存快照virsh suspend vm-name virsh snapshot-create-as vm-name snap-consistent --atomic virsh resume vm-name暂停的时间窗口很短但能保证内存状态和磁盘状态在同一个时间点一致性更好。数据库类应用尤其推荐这种方式否则内存里的日志和磁盘上的数据文件不是完全同步的回滚后可能出现文件系统不一致。3.3 查看、回滚与删除查看所有快照virsh snapshot-list vm-name查看具体快照的详情virsh snapshot-info vm-name snap-before-upgrade virsh snapshot-dumpxml vm-name snap-before-upgrade回滚到指定快照点virsh snapshot-revert vm-name snap-before-upgrade这里有个关键参数要记住--running让回滚后虚拟机自动进入运行状态--paused则停留在暂停状态。如果你拍的是带内存的快照回滚时会自动恢复内存状态如果是纯磁盘快照回滚后虚拟机处于关机状态需要手动启动。删除快照virsh snapshot-delete vm-name snap-before-upgrade删除快照时qcow2内部的数据块会被合并或释放这个操作可能比较耗时而且会占用额外的CPU和IO。一个大快照删除时整个磁盘文件可能出现短暂的性能下降。生产环境不要赶着业务高峰去删快照。3.4 内部快照的最大局限性内部快照虽然方便但有几个绕不过去的毛病。第一qcow2文件会越来越大。假设你拍了5个快照每个快照之后都写入了大量新数据文件里同时存着5份历史数据和当前数据磁盘空间开销非常可观。第二快照链会变得越来越复杂QEMU处理多层快照时的性能会下降。第三所有快照和当前数据都在同一个文件里一旦文件损坏全部一起丢。所以内部快照我一般只在测试环境用或者说在开发机上做快速验证用。生产环境大概率还是走外部快照或者LVM快照。4. qemu-img命令行快照不经过libvirt的另一条路径在某些瘦客户端、无libvirt的纯QEMU环境或者需要在宿主机上直接操作镜像的场景下qemu-img是兜底工具。它的快照命令也很清晰。4.1 创建快照qemu-img snapshot -c snap-name /data/vms/vm-name.qcow2注意这个命令执行时必须确保虚拟机处于关闭状态或者至少磁盘没有活跃写入。如果在虚拟机运行中直接执行qemu-img snapshot得到的快照极大概率是不一致的。qemu-img命令并不知道虚拟机内部的页面缓存和文件系统状态拍出来的快照从QEMU的角度看是某个时刻的块设备状态但从虚拟机内部看可能是文件系统正在写入中间状态回滚后ext4或者xfs很可能会报错。如果确实要在虚拟机运行中用qemu-img方式拍快照可以先执行virsh snapshot-create-as配合--disk-only或者先virsh suspend再操作。4.2 查看快照列表qemu-img snapshot -l /data/vms/vm-name.qcow2输出会列出快照ID、标签、VM状态、创建时间等信息。注意这个VM状态字段其实就是虚拟机内存状态的一个记录但qemu-img快照本身不保存内存内容所以这个字段大多是0或者不准确。4.3 回滚快照qemu-img snapshot -a snap-name /data/vms/vm-name.qcow2-a参数是apply把磁盘内容恢复到快照时刻。同样要求虚拟机处于关闭状态。4.4 删除快照qemu-img snapshot -d snap-name /data/vms/vm-name.qcow2删除后qcow2文件会整理数据块文件大小不一定立刻缩小但内部空间会被释放。qemu-img的好处是可以在宿主机上用shell脚本批量处理多个镜像的快照适合自动化备份场景。坏处是它完全不知道虚拟机内部发生了什么一致性完全依赖你自己控制和把握时机。5. 生产环境更推荐的外部快照与LVM快照内部快照痛点多生产环境更可靠的做法是外部快照和存储级快照。5.1 外部快照的操作方法外部快照的本质是由当前磁盘镜像生成一个新的覆盖文件原镜像成为backing file。执行方式virsh snapshot-create-as vm-name ext-snap-outside \ --disk-only \ --atomic \ --diskspec vda,snapshotexternal执行后vda磁盘会变成一个新的qcow2文件这个文件在XML里成为新的磁盘源而原来的qcow2文件自动成为backing file。这个新文件一开始很小随着虚拟机运行逐渐变大记录从快照点以来的所有增量数据。外部快照的好处原镜像完全不动适合用来做备份基线快照点之间互不干扰支持热备和增量备份策略。坏处撤销和合并的操作比内部快照复杂需要处理blockcommit或blockpull。5.2 外部快照的合并与撤销如果想把外部快照合并回磁盘链使用blockcommitvirsh blockcommit vm-name vda \ --base /data/vms/vm-name.qcow2 \ --top ext-snap-outside \ --active \ --pivot这个命令会把覆盖层的改动合并回base镜像并让虚拟机继续以最终状态运行。执行成功后外部快照文件可以安全删除。如果要回滚到外部快照点操作思路不同把当前覆盖层丢弃重新以原镜像为磁盘启动虚拟机。具体做法是编辑虚拟机XML把disk的source替换为原镜像路径或者用blockpull把backing file拉回来。5.3 LVM快照存储级别的降维打击如果虚拟机磁盘用的是LVM逻辑卷LVM快照是目前最皮实、性能损耗最低的方案。它不需要QEMU参与直接在宿主机上操作lvcreate -s -n vm-name-snap -L 20G /dev/vg0/vm-name-s表示snapshot-L指定快照卷大小20G是给COW数据预留的空间/dev/vg0/vm-name是原逻辑卷快照创建后原卷继续运行所有新写入的数据会被COW机制记录到快照卷里。要恢复时直接把逻辑卷回滚lvconvert --merge /dev/vg0/vm-name-snap这个操作要求对应的逻辑卷处于非活跃状态所以虚拟机关机后执行比较稳妥。LVM快照必须在创建前预留足够的快照空间。预留空间不足时快照会变成失效状态回滚操作直接失败。预留比例取决于快照点到恢复点之间的数据变化量一般我建议至少预留原卷大小的20%到30%数据密集型的系统直接给50%。三种方案对比方案空间开销一致性保障回滚复杂度适用场景内部快照qcow2文件膨胀一般需配合暂停或--disk-only低测试环境、快速验证外部快照增量覆盖文件较高可配合内存快照中生产环境备份链、增量备份LVM快照快照卷占用固定空间高存储层COW低生产环境应急回滚、批量克隆6. 回滚实操、删除顺序与真实踩坑记录6.1 回滚的整体流程无论用了哪种快照方式回滚前有几件必须做的事通知业务方或者选择业务低峰期操作。在虚拟机内执行sync把文件系统缓存刷到磁盘。sync如果是数据库最好先做一次干净关闭或用数据库自身的备份工具转储。快照不是数据库备份工具它只能保证块设备层的一致性不能保证物理上的一致性但是先flush数据库的redo log和buffer pool回滚后的一致性会好很多。MySQL、PostgreSQL、Oracle都有各自的pre-freeze和post-thaw机制能挂脚本调的话一定要先调。关闭虚拟机或者暂停。virsh shutdown vm-name优先实在关不掉的virsh destroy也不是不能用但是destroy模拟的是断电回滚后的文件系统靠日志恢复有风险。执行回滚命令然后启动虚拟机检查内部服务和数据状态。6.2 快照删除顺序快照删除顺序也有讲究。假设有链式的快照A-B-C直接删B会导致libvirt尝试把B和C合并这个操作不仅慢而且如果中途断电整个磁盘链可能会出现损坏。正确做法是从最上层的快照开始删一层一层往下也就是先删C再删B最后删A。磁盘空间压力大的时候不要一次性连续删除多个大快照。中间间隔一段时间观察磁盘I/O和文件系统膨胀情况等合并完成、文件大小平稳后再删下一个。6.3 踩坑记录第一个坑是热备份外部快照的一致性。有一次我执行外部快照后直接拿backing file去备份结果虚拟机内部文件系统在快照之后还在写入数据我没等待qcow2文件的写缓存完全落盘导致备份出来的镜像在挂载验证时出现故障。经验是备份前在虚拟机内部执行sync并且在宿主机上确认无活跃IO再复制文件。第二个坑是qcow2磁盘文件有快照后执行virsh vol-resize扩充磁盘大小新扩充的空间不会自动并入快照链导致原快照数据区和扩展区错位虚拟机启动后出现分区表识别错误。正确的做法是扩充之前在虚拟机内部用growpart或者fdisk扩展分区然后再在宿主层面resize最后确认qcow2文件没问题再考虑快照。第三个坑是NFS存储上的快照。如果虚拟机磁盘挂在NFS上快照创建和删除时的文件锁行为在不同NFS版本上表现不一样。NFSv3上libvirt的快照操作偶尔会出现cant create snapshot: No space left on device之类的迷惑报错实际是文件锁抢占或者属性缓存导致的。解决办法是挂载NFS时加上nfsvers4参数并且关闭attribute cache的副作用mount -t nfs4 -o nfsvers4,hard,timeo600,noac host:/export /data第四个坑是和备份任务互相干扰。很多团队在宿主机上做整文件复制备份比如rsync整个qcow2文件。如果qcow2正在被快照操作写入rsync复制出来的文件可能是损坏的。要复制带快照的qcow2文件要么在虚拟机关机状态下复制要么先执行virsh snapshot-list --tree确认没有活跃操作要么直接改用qemu-img convert -O qcow2生成一份干净的镜像再复制。第五个坑是大页内存HugePages和内存快照的兼容问题。配置了大页内存的虚拟机带内存快照的保存和恢复速度会非常慢而且恢复时可能因为大页内存碎片导致失败。遇到这种情况建议只做磁盘快照必要的时候配合应用层备份。第六个坑是回滚后虚拟机的时钟跳跃问题。如果虚拟机靠NTP校时回滚后时间会大幅回退NTP服务会慢慢调整回来但期间很多业务会报错。建议在回滚操作前先临时停止业务回滚完成后再启动。7. 我把话放在这里的几条经验如果看完前面这些你还是嫌长记住下面这几条就够用了。做内部快照确认磁盘是qcow2记好--disk-only和--atomic回滚时先关机回滚后检查文件系统和数据库一致性。做外部快照用virsh snapshot-create-as配合--disk-only合并用blockcommit撤销时把磁盘XML换回base镜像。做LVM快照空间预留给足虚拟机关机再merge千万不要在快照卷满的时候重启主机。带内存快照务必配合业务方确认停机窗口数据库类应用不要轻易做内存快照回滚。最后我个人的体会是快照不等于备份。快照依赖的是同一份存储上的COW数据存储坏了快照全没。真正可靠的数据安全路径必须包含独立的异地备份和定期的恢复演练。快照是手术台上的那一针麻醉剂让你在做危险操作之前有退路但千万别把麻醉剂当救命药。我实测下来最稳的组合是日常小操作前用内部快照快速留后路重大变更前用LVM快照或者外部快照做保护同时定期备份qcow2文件到独立存储并且每月至少做一次从备份恢复虚拟机的演练。这套逻辑走下来服务器被我折腾出问题的次数大幅减少至少不用再为了一次升级熬夜加班恢复数据。