KVM虚拟化环境里,快照这个功能平时不起眼,等哪天真把一台虚拟机搞到起不来的时候,你才会意识到它有多救命。我用virsh这套命令行管理KVM快照好几年了,从最初给测试机打快照练手,到后来在生产环境升级内核、装补丁、调数据库参数之前都习惯性地留一个快照,可以说它已经成了我肌肉记忆里的标准动作。这篇文章直接把virsh快照从创建、查看、回滚到删除的完整命令链讲清楚,同时把底层机制和踩坑实录一起放出来,适合刚接手KVM环境的新手,也适合已经用了virsh一段时间但没仔细研究过快照原理的运维老手。
我先把结论放在这里:virsh快照本身不复杂,复杂的是搞清楚内部快照、外部快照、磁盘快照、系统快照这几组概念之间的关系,以及哪些场景不能直接用默认命令。下面我按"原理→实操→排错"的顺序逐个讲。
1. 快照是什么,为什么说它是KVM运维的保命符
一开始先说清楚:KVM快照就是在某个时间点给虚拟机"拍一张照片",把当时的磁盘状态(甚至可以连同内存状态)完整保存下来。之后不管你对系统做了什么改动,只要想回到那个时间点,一条virsh snapshot-revert命令就能把虚拟机整个拉回去。这个能力在日常运维里的价值,怎么强调都不过分。
我举个自己踩过的例子。有一回给一台跑着生产业务的CentOS虚拟机升级内核,按理说升级内核这种事很常规,结果那台机器正好赶上内核与某张网卡驱动不兼容,重启之后网络直接起不来。当时如果手里没有快照,就得进机房挂ISO、进单用户模式折腾半天,业务停机时间至少按小时算。幸好我在升级前用virsh snapshot-create-as留了个快照,发现不对之后一条revert命令,两分钟内系统恢复到升级前的状态,业务照常跑。从那以后我定了个规矩:凡是给虚拟机做大版本升级、改内核参数、动数据库配置、批量更新软件包之前,一律先留快照。
快照适合谁来用?只要你的KVM环境使用qcow2镜像,都建议掌握。做虚拟机模板打包的、给客户交付测试环境的、搞自动化运维的,这些都是快照的高频使用场景。甚至在学校机房或实验室里,老师用快照给学生准备一套干净的实验环境,学生随便折腾,下课一条命令还原,也是常见玩法。
2. virsh快照体系的底层逻辑
2.1 磁盘快照与系统快照的区别
libvirt对快照的划分很清晰。磁盘快照(disk snapshot)只保存虚拟机虚拟磁盘在某一时刻的状态,不含内存数据。系统快照(system snapshot)在磁盘快照的基础上,还会把虚拟机当时的内存状态、CPU寄存器状态、设备状态一起保存下来。
这两种快照的使用场景完全不同。磁盘快照用来"恢复数据",比如你在文件系统层面做了错误的修改,回滚磁盘快照就能让文件系统回到过去的状态。系统快照则更接近"挂起恢复"的效果,它连当时正在运行的程序、未保存的临时数据、内存里的缓存都能还原。如果虚拟机里跑着重要的服务,你想在某个业务低谷点留一个"运行中状态"的完整备份,系统快照更合适。
有个细节必须提前说明:创建系统快照需要把内存状态写到磁盘上,这一步对虚拟机的运行会产生短暂影响,libvirt会短暂暂停虚拟机来获取一致的内存镜像。生产环境对业务连续性要求极高的话,建议使用--live参数配合创建,或者干脆只做磁盘快照,把影响降到最低。
2.2 内部快照与外部快照的实现差异
这一组概念很多人容易混淆。内部快照(internal snapshot)的数据保存在qcow2镜像文件内部,快照和原始数据共用一个文件;外部快照(external snapshot)则是把快照状态放到一个独立的新镜像文件里,原来的磁盘文件变成只读的基底(backing file),新产生的写入全部进入新文件。
内部快照的好处是管理简单,virsh snapshot-list就能直接看到,一条命令就能创建和删除,不需要关心附加文件。缺点是快照和原文件绑死在一起,一旦qcow2文件损坏,所有内部快照跟着一起报废。而且内部快照数量一多,文件体量增长很快,每个快照都会占用文件头部和写入区域的空间,整盘性能也会逐渐劣化。
外部快照的好处是快照链清晰、便于做增量备份,也适合配合其他存储手段做更精细的管理。但libvirt对外部快照的回滚支持有限制,不能像内部快照那样随意在快照点之间来回跳。常规做法是通过blockcommit或者blockpull命令把外部快照合并回基底文件,过程比较绕。对于大多数中小环境的日常运维,我建议先用好内部快照,等你对镜像链的管理很熟了再碰外部快照。
我把两组概念整理成一个对比表,方便大家一眼看清差异:
| 对比项 | 磁盘快照 | 系统快照 | 内部快照 | 外部快照 |
|---|---|---|---|---|
| 保存内容 | 仅磁盘状态 | 磁盘+内存+设备状态 | 位于qcow2文件内部 | 独立的新镜像文件 |
| 管理复杂度 | 低 | 中 | 低 | 高 |
| 回滚便利性 | 高 | 较高(需匹配运行状态) | 高 | 受限,需块合成 |
| 数据安全性 | - | - | 与源文件共存,同生共死 | 独立文件,相对安全 |
| 性能影响 | 小 | 创建时有短暂暂停 | 快照多时明显 | 链式影响,可配合策略管理 |
2.3 qcow2镜像格式是快照的地基
快照能不能做、做成什么形式,最终由磁盘镜像格式说了算。raw格式是完全裸的磁盘数据,不支持内部快照;qcow2格式因为天生设计了快照相关的元数据结构,才是内部快照的正统载体。
判断一台虚拟机能不能做内部快照,先看它的磁盘文件格式:
qemu-img info /var/lib/libvirt/images/web01.qcow2如果输出里image format显示raw,那就别指望virsh snapshot-create能成功,得先把raw转成qcow2再做快照。转换要在虚拟机关机状态下进行:
qemu-img convert -f raw -O qcow2 /var/lib/libvirt/images/web01.img \ /var/lib/libvirt/images/web01.qcow2 virsh edit web01 # 修改disk段,把source file和driver type改成qcow2这是最基础、也最容易被人忽略的前置条件。我见过不止一个同事拿着raw格式的盘直接敲快照命令,报错之后一脸茫然地问我为什么创建失败。先把格式搞对,后面的路才顺。
3. 快照管理实操:从创建到回滚的核心命令
3.1 创建快照:snapshot-create-as的参数选择
创建快照有两条命令:snapshot-create和snapshot-create-as。前者不指定名称,libvirt自动生成;后者可以自己命名,并且支持携带描述信息。生产环境中强烈建议用snapshot-create-as,一个可读的名称和一段清楚的描述,在几周之后回看时能省掉大量回忆时间。
最基本的创建方式是:
virsh snapshot-create-as web01 web01-before-kernel-upgrade \ --description "2025-06-01 内核升级前快照,含内存状态"如果你的虚拟机正在运行,又没有加任何额外参数,libvirt会尝试做一个系统快照,也就是把内存状态一并保存。如果再补上--live参数:
virsh snapshot-create-as web01 web01-before-kernel-upgrade \ --description "升级前快照" --live--live的存在是为了保证虚拟机在快照过程中不中断业务,磁盘和内存快照尽量一致。不加--live时,为了拿到一致的内存状态,虚拟机可能会被短暂暂停,体现在业务侧就是瞬时卡顿。
如果只想保存磁盘状态、不碰内存,使用--disk-only:
virsh snapshot-create-as web01 web01-before-config-change \ --description "只做磁盘快照" --disk-only --atomic--disk-only快照默认是外部快照,它不会碰内存,速度快得多。--atomic的含义是让所有磁盘要么全部完成快照、要么一个都不做,不会出现某个磁盘快照成功、另一个失败的中间态。注意,--disk-only搭配--quiesce可以调用qemu-guest-agent先把文件系统刷到一个一致的状态,这个对跑数据库的虚拟机特别重要:
virsh snapshot-create-as db01 db01-before-schema-change \ --disk-only --quiesce --atomic--quiesce生效的前提是虚拟机里安装了qemu-guest-agent,并且agent正常运行。agent的作用等于告诉操作系统"把脏页和缓存都写回磁盘",这样拍出来的磁盘快照才不会在恢复时出现文件系统不一致的问题。没有安装agent的Windows虚拟机尤其要注意,强行用--quiesce大概率会因为agent不响应而失败。
3.2 查看快照:snapshot-list与snapshot-info
快照建好之后,第一件事是确认它真的存在,并且记录了正确的时间点。列出快照用:
virsh snapshot-list web01输出里有一列名称、创建时间和状态。状态这个字段值得多说一句:如果显示running,说明这个快照是在虚拟机运行状态下拍的;如果显示shutoff,则是关机状态拍的。这一点直接决定了后续回滚时虚拟机的状态行为。
想进一步看某个快照的细节,用snapshot-info:
virsh snapshot-info web01 web01-before-kernel-upgrade它会显示名称、域状态、是否包含内存状态、磁盘状态等字段。其中"包含内存状态"那一项是判断该快照能否"原样恢复运行现场"的关键。生产环境排障时,我最习惯把snapshot-list和snapshot-info搭配使用,先列全貌,再看关键快照的类型。
如果要导出某个快照的详细XML配置,比如为了复制一个快照到别的环境,可以用snapshot-dumpxml。这条命令平时用得少,但一旦涉及用libvirt API做自动化快照管理,它就是最关键的调试工具。
3.3 回滚快照:snapshot-revert的使用要点
回滚是整个快照体系里最需要谨慎对待的操作,因为它直接覆盖当前状态。命令本身很简单:
virsh snapshot-revert web01 web01-before-kernel-upgrade但回滚的语义有几种情况,必须区分清楚。如果快照包含内存状态、且虚拟机当前正在运行,回滚时libvirt会把虚拟机的运行状态整体切回快照时刻,包括内存里的进程状态都会恢复,效果很像从休眠中唤醒。如果快照只包含磁盘状态、虚拟机正在运行,libvirt会重启虚拟机来应用磁盘回滚。如果你的虚拟机正处于关闭状态,回滚磁盘快照是最干净利落的,直接改磁盘数据,下次开机即生效。
这里有个我必须反复强调的坑:回滚会丢掉当前磁盘上的所有变更。假设你一周前做了快照,这周业务产生了一大批新数据,在不做任何备份的情况下revert,这些新数据瞬间全部消失。所以在回滚之前,想清楚这周的数据要不要保留,要的话先把需要的文件复制出来,或者先做一个新的快照。
另外,外部快照存在时,回滚不是一句revert就能搞定的。libvirt对"回滚到外部快照链中的某个非当前节点"支持有限,我在实践中遇到的情况是,要先确认当前快照节点,必要时通过blockcommit把覆盖层合并或者用blockpull拉取数据,再执行回滚。这部分内容比较深,建议在测试环境先完整演练一遍,别在生产上临场摸索。
3.4 删除快照:snapshot-delete的常见姿势
时间久了快照会积累很多,该清理就要清理。最基本的删除命令:
virsh snapshot-delete web01 web01-before-kernel-upgrade这条命令会同时删掉快照的元数据和内部快照对应的磁盘数据。如果你的qcow2文件很大,删除内部快照时会明显感觉到文件体积变化,这是正常的。
需要小心的情况有两个。一是快照之间存在父子关系,如果只删父快照不删子快照,系统会提示还有child snapshot存在,这时加--children可以连同子孙快照一起删除:
virsh snapshot-delete web01 web01-before-kernel-upgrade --children如果你只想删子快照、保留父快照,则用--children-only。二是只想保留数据、不要元数据的时候,用--metadata参数。这个参数会保留快照记录对应的数据块,但把virsh管理层面的元数据删掉。常用的场景是磁盘文件已经被外部工具合并过了,libvirt里的快照记录变成了无效残留,此时用--metadata把它们清掉即可。
删除前想清楚,快照一旦删除,那个时间点的状态就再也找不回来了。
3.5 一个完整的快照生命周期示例
把前面的命令串成一个完整流程,方便直接抄作业。假设有一台名为web01的虚拟机,磁盘格式是qcow2,跑着nginx服务。
第一步,确认磁盘格式和虚拟机状态:
qemu-img info /var/lib/libvirt/images/web01.qcow2 virsh list --all第二步,升级前创建磁盘快照,使用--disk-only加--quiesce:
virsh snapshot-create-as web01 web01-before-upgrade \ --description "nginx 1.24升级前快照" \ --disk-only --quiesce --atomic第三步,确认快照生成:
virsh snapshot-list web01 virsh snapshot-info web01 web01-before-upgrade第四步,正式升级,这里可以是yum upgrade,也可以是跑维护脚本。升级之后如果发现系统异常,执行回滚:
virsh snapshot-revert web01 web01-before-upgrade第五步,业务验证通过后,清理掉不再需要的快照:
virsh snapshot-delete web01 web01-before-upgrade这套流程我已经跑了无数遍,稳定可靠。唯一要提醒的是,如果--quiesce没有生效(比如agent没装),建议在打快照前先把nginx或数据库的写操作停掉十几秒,人工保证一致性。
4. 常见问题与排查实录
4.1 快照创建失败:raw格式磁盘不支持
这是出现频率最高的问题。报错信息通常长这样:
error: internal error: unable to execute QEMU command 'blockdev-snapshot-sync': The node has no device and no backing file很多人第一反应是排查权限或者存储空间,实际上最简单的一步就是先看磁盘格式:
qemu-img info /var/lib/libvirt/images/web01.img如果显示file format: raw,结论就很明确:内部快照做不了。解决办法有两个,一是把raw转成qcow2,二是改用外部快照(--disk-only默认就是外部快照)。考虑到后续管理复杂度,我更推荐转qcow2,一次性把基础打牢。
4.2 虚拟机处于关机状态,却要创建带内存的快照
如果虚拟机处于shut off状态,你执行snapshot-create-as并期望带上内存状态,是注定要失败的,因为你根本没有运行中的内存可拍。此时的快照只能包含磁盘状态,命令加上--disk-only就好。反过来,如果你想创建一个完整系统快照,要保证虚拟机处于运行状态。
这个坑在自动化脚本里特别常见。很多人写了个定时任务打快照,却忘了判断虚拟机的运行状态,导致部分任务静默失败。我的建议是脚本里先执行virsh list --all判断state,再决定快照参数。
4.3 --quiesce报错:qemu guest agent没有响应
报错信息类似:
error: Requested operation is not valid: QEMU guest agent is not responding. QEMU snapshot may not be consistent原因不外乎三种:agent没装、agent服务没启动、agent版本与libvirt不兼容。排查方法很直接,进入虚拟机检查agent进程和服务状态:
systemctl status qemu-guest-agent确认agent正常后,再检查监听socket是否就绪。另外,Windows虚拟机记得安装对应版本的QEMU guest agent,并且服务要设为自动启动。如果你实在不想折腾agent,退而求其次,在打快照前手动停掉数据库或者执行sync,也能达到基本一致的效果。
4.4 回滚后虚拟机网络或服务异常
快照回滚成功不等于业务正常。最常见的情况是回滚后网络配置、主机名、密钥等出现错位。原因通常是快照时间和当前时间跨度过大,期间别的管理工具或人为改动引入了新的配置,回滚把旧配置带回来了。
排查思路是这样的:先看虚拟机的控制台,确认系统能正常登录;再看网卡状态和网络连通性;然后看关键服务日志,比如journalctl -xe或者对应应用的错误日志。如果发现回滚后的状态不理想,而你回滚前又留过新快照,可以再revert回去。这也是我前面强调"回滚前先做新快照"的原因,进可攻退可守。
4.5 快照太多导致qcow2文件膨胀和性能下降
内部快照每增加一个,qcow2文件头部就会增加元数据,同时快照间的差异数据全挤在同一个文件里。快照数量超过十个以后,你会发现文件占用比预期大很多,虚拟机磁盘写入也可能变慢。这属于qcow2内部快照的固有特性,目前没有银弹。
我的处理习惯是严格控制快照数量,最长保留时间不超过一到两周,过期的及时删除。确实需要长期保留多个还原点时,优先考虑外部快照加特定备份方案,而不是把所有还原点都压在一个文件里。性能敏感的生产虚拟机,我甚至建议干脆不用内部快照,用定期rsync加备份工具的组合来替代。
4.6 不同宿主之间迁移虚拟机时快照丢失
这个坑比较隐蔽。有些朋友把qcow2文件从一个宿主机拷贝到另一个宿主机,然后在新宿主机上virsh snapshot-list一看,发现快照全没了。原因是快照元数据同时存在于两个地方:一部分在qcow2文件内部,另一部分在libvirt的本地配置里(/var/lib/libvirt/qemu/snapshot/目录下)。直接拷贝qcow2文件,镜像内的数据跟着走了,但libvirt侧的迁移快照定义没有跟着走。
解决办法是把整个虚拟机的定义和快照定义一起导出。用virsh dumpxml导出虚拟机XML,同时把快照的XML也用snapshot-dumpxml逐个导出。迁移到新宿主机后,先定义虚拟机,再用snapshot-create的--xml选项重建快照元数据。流程不复杂,但初次接触的人很容易踩进去。
我把这六个问题整理成速查表,方便大家按症状快速定位:
| 症状 | 大概率原因 | 快速处理 |
|---|---|---|
| 快照创建失败,报node/backing file错误 | 磁盘是raw格式 | 转qcow2或改用外部快照 |
| 关机状态创建内存快照失败 | 虚拟机没在运行 | 加--disk-only或先开机 |
| --quiesce报agent不响应 | 未装或未启动agent | 安装/启动agent,或手动sync |
| 回滚后网络/服务异常 | 快照跨越时间太长,配置漂移 | 查控制台、网卡、服务日志 |
| qcow2文件膨胀、写入变慢 | 内部快照过多 | 及时清理,控制快照数量 |
| 迁移宿主机后快照消失 | 只拷贝了qcow2,没迁移元数据 | dumpxml导出定义,再重建快照 |
5. 我的一些实操心得和建议
讲了这么多命令和排错,最后聊几句软性的东西。快照本质上是一种"后悔药",但它不是备份,这两件事不冲突。真正重要的数据,还是要靠完整备份策略来保障。快照更适合应对短期内的操作失误,而不是作为数据安全的最后一道防线。备份是否完好、能否恢复,需要单独验证,这个意识最好从用快照第一天就开始建立。
还有一个心态上的建议:打快照这件事,成本低收益高,但前提是要养成习惯。不要只在系统升级这种大动作前才想起它,改负载均衡配置、调防火墙规则、甚至批量改文件权限之前,顺手打一个快照,代价可能只有几秒钟,关键时刻能救回来的东西却可能是整个业务。
最后分享一个小技巧,我一直用它来降低误操作风险。给快照命名时,把用途和日期都写进名字里,比如web01-before-upgrade-20250601。回滚前先执行snapshot-list看清楚快照名称,再执行revert,并且revert之前手动再打一个新快照作为"后悔药的后悔药"。这两步看起来多余,但我用它们避免过不止一次生产事故。快照管理说白了就一句话:随手留一手,回滚前再留一手,稳得很。