资讯动态

KVM虚拟机因磁盘满意外暂停?排查链路与恢复实战解析

发布时间:2026/10/15 10:12:18 来源:尧图企业网站定制
KVM虚拟机跑着跑着突然“停摆”SSH连不上、页面打不开可宿主机本身看着一切正常这种场景大概每个搞虚拟化的运维都遇过。我最近就碰到一回排查到最后才发现事故元凶是宿主机磁盘空间被日志和过期的虚拟机镜像彻底写满从而触发了KVM虚拟机意外暂停。这篇就把当时的完整排查链路、底层原理和恢复操作拆开讲清楚希望能帮遇到同类问题的同行少走点弯路。1. 假死现场客户机没响应宿主机却看着岁月静好1.1 生产环境里的那声“闷雷”我维护的一套业务系统X平时跑在KVM虚拟机里运行也算稳定。结果某天凌晨值班同事电话打了过来客户页面上不去SSH登录卡死业务监控大面积飘红。我第一反应是应用挂了或者网络割接了赶紧登宿主机确认状态。登录之后看到的宿主机一切正常——CPU负载稳定、内存占用不高、物理链路都通、ping宿主机也通。从表面看宿主机完全不像有事的样子。于是我把目光放到虚拟机本身打开virsh list --all结果发现VM那条记录并不是running而是paused。一个“暂停”状态而不是“关闭”或“崩溃”。问题来了KVM虚拟机在什么情况下会自己进入paused常见原因就那么几类快照恢复动作、在线迁移过程、宿主机资源严重争抢以及磁盘I/O错误。当我又敲了一次virsh domstate输出直接给出了答案paused (ioerror)。1.2 “paused (ioerror)”这一行字意味着什么virsh domstate输出里的ioerror就是libvirt对这个虚拟机暂停原因的标注——因为I/O错误而被QEMU强制冻结。表面上是“暂停”其实像“休克”虚拟机进程还在QEMU守护进程也没退出但整个客户机的所有vCPU都被冻结业务里的所有线程都停在原地。这不是进程崩溃也不是guest宕机而是整个guest被按住了暂停键。明白状态含义后我反而不是那么慌了。因为paused状态意味着数据还没走到彻底损坏的最后一刻比直接崩溃/重启强得多。反过来说很多新手会在这种时候犯一个致命错误看到SSH不通直接执行virsh reboot或者virsh destroy重来一次。如果做了内存中所有没落盘的数据就全丢了。所以遇到虚拟机不可连接的情况第一动作永远是先看状态而不是重启。1.3 一开始为什么没怀疑宿主机磁盘说实话在排查早期宿主机磁盘并不在我脑海里的前三个怀疑对象中。原因很朴素——宿主机自己的SSH还正常、还能敲命令、/tmp下还能写临时文件。但这里面其实藏着一个误区宿主机能写不代表虚拟机镜像所在的那个分区还有空间。业务系统X的数据盘是qcow2镜像存放在/var/lib/libvirt/images而这个目录恰好和根分区共用一块磁盘。根分区平时看着总是“还很宽敞”但日志、临时文件、历史备份都在悄悄往里面塞。等真正写满时宿主机上很多小操作仍然正常真正被卡住的恰恰是QEMU写入qcow2镜像的那个环节。于是虚拟机表现就是进程还在、状态却变成paused。这种“宿主机一切正常虚拟机却莫名其妙卡死”的反差正是这类故障迷惑性的来源。2. 为什么磁盘满会让KVM自动暂停底层机制拆解2.1 一次写盘请求的完整路径要理解为什么磁盘满会导致虚拟机“暂停”而不是“报错”或“卡住”得先看KVM虚拟机的磁盘I/O是经过哪些环节的。客户机里的一个应用调用write()系统调用写进客户机的内核页缓存然后由客户机文件系统ext4、xfs这类发起底层的bio请求经过virtio-blk或virtio-scsi驱动把请求放进虚拟队列。宿主机端的QEMU进程会从这个队列里取出请求执行真正的写入落到宿主机本地文件系统上。这条链路里每一步都可能出问题。当/var/lib/libvirt/images所在文件系统的空间已经耗尽时宿主机的文件系统在执行write时会向QEMU返回一个ENOSPCNo space left on device。QEMU收到的就是一次底层写入失败。2.2 QEMU选择冻结虚拟机而不是“报错”的原因这里的关键就是QEMU对磁盘I/O错误有一个默认处理策略。在用-drive配置磁盘设备时有一组相关参数werror写错误处理方式、rerror读错误处理方式取值通常有ignore、stop、report、enospc。在没有显式配置的场景下QEMU对写错误的默认行为是stop。stop的含义既不是退出进程也不是把错误透传而是把整个虚拟机暂停。这个设计思路其实很朴素当前虚拟机内核并不知道宿主机物理磁盘已经满了如果继续往这一块写只会产生更多虚假写入造成“逻辑上成功、物理上丢失”的窗口。与其让guest蒙着头继续写不如先把它冻结住让管理员从宿主机侧介入处理。可以类比成仓库门已经被锁死但送货的人还在往里搬货。如果不拦着箱子堆在门口越堆越多等开门时反而更难收拾。先喊停才给处理留出时间。2.3 不同写入路径和缓存模式下的表现差异不要以为所有KVM环境磁盘满时都会出现paused (ioerror)。是否暂停、什么时候暂停取决于几个因素QEMU自身的错误策略配置磁盘缓存模式writeback、writethrough、none磁盘格式是qcow2还是raw后端块设备是本地文件、逻辑卷还是远程存储比如某些环境下libvirt域定义里给磁盘driver配置了error_policyenospc那ENOSPC错误会被透传给客户机。guest的文件系统会直接看到I/O错误部分文件系统会转入只读状态。这时虚拟机的表现往往不是paused而是“假活”进程还在、业务还在跑但一切写操作都失败事务持续堆积比pause更让人头疼。再比如磁盘缓存配置成writeback数据先写宿主机的页缓存再异步落盘ENOSPC的暴露时间会被延迟。而配置成none或writethrough时写入直接穿透到块设备空间不足的问题会立刻浮出水面。所以现象出现的时间点也和缓存模式有直接关系。下面整理了一张常见策略对照表方便排查时快速比对错误策略行为表现适用场景ignore忽略错误继续执行写操作不推荐数据风险极高report向客户机报告I/O错误客户机自身能处理磁盘故障时stop强制暂停虚拟机默认逻辑安全但要人工介入enospc把ENOSPC透传给客户机需要客户机感知并主动收缩需要提醒的是具体取值组合与QEMU版本、libvirt版本都有关系这个表更多是作为排查时的经验参考。真正到现场还是要以实测行为和日志为准。3. 排查链路复盘从状态确认到挖出磁盘空间真凶3.1 排查顺序virsh domstate到日志取证先看状态再翻日志最后才动盘这个顺序不能乱。第一步永远是确认虚拟机状态。因为KVM虚拟机的状态字非常明确running、paused、shut off、crashed。如果状态是paused且后面跟着ioerror那方向基本就锁定了。第二步翻libvirt的QEMU日志通常是/var/log/libvirt/qemu/虚拟机名.log以及宿主机系统的journalctl -u libvirtd。在日志里重点找这几类关键字符串Domain has been pausedNo space left on deviceI/O errorBlock device error当时日志里确实出现了类似这样的内容Domain id4 statepaused reasonioerror qemu ... /var/lib/libvirt/images/business-x.qcow2: No space left on device看到“No space left on device”那一刻整个排查方向就明确了宿主机磁盘空间问题了。到这一步才轮到df命令出场去看各分区的实际使用率。3.2 du/df/lsof组合拳定位空间元凶df -h确认/分区已经100%之后下一个问题是到底是什么把空间吃满了推荐分三步操作先同时执行df -h和df -i把块空间和inode一起确认。用du -x -sh /var /home /tmp等顶层目录从用量的源头查起或者直接du -ah / | sort -rh | head -n 20把最大的文件list出来。执行lsof | grep deleted看有没有进程还在持有已删除的文件句柄。当时我实际用到的定位命令大致是du -x -sh /* 2/dev/null | sort -rh | head -n 10 du -ah /var/lib/libvirt/images 2/dev/null | sort -rh | head -n 20 lsof L 1 2/dev/null | sort -k 7 -rn | head -n 20最终定位出来的结果很典型/var/lib/libvirt/images目录里躺着好几个过期的内部快照还有一块忘记清理的临时镜像加起来占了几百GB另外/var/log下某个应用日志从部署后就没滚动过也已经吃了几十个GB。两个来源叠在一起把根分区彻底填满了。3.3 插曲块空间没满但inode满了排查过程中还有个差点翻车的插曲。我们一度以为把大文件删掉就能解决但过了一会儿系统又报No space而且df -h明明已经显示空出了一部分。再看df -i才发现inode使用率已经到100%。inode耗尽的含义是文件系统里的文件数量到了极限即使块空间还有剩余也无法再创建任何新文件。很多系统的/tmp、/var/spool、邮件队列或者容器镜像层会堆积大量几KB的小文件正好能把inode吃满。所以排查磁盘问题时df -h和df -i永远要成对查看。这个习惯建议所有KVM主机都养成。还有另一个容易忽略的情况文件虽然被rm删除了但进程还持有该文件句柄空间不会真正释放。这种时候du统计的结果和df显示的可用空间可能对不上要用lsof L 1找出那些“幽灵”句柄并处理掉空间才会真正回到可用状态。4. 恢复流程空间清理后如何安全唤醒一台“休克”虚拟机4.1 清理空间时的优先级取舍清理磁盘空间时最容易犯的错误就是一着急随手删“看起来没用”的文件。我给自己定了个优先级确认过期的qcow2内部快照用qemu-img snapshot -d删除。清理/var/log下轮转后的历史日志.gz、.1、*.old这类。/tmp下超过一周的临时文件。还不够时检查/var/lib/libvirt/images下是否有未挂载的临时镜像。其中qcow2快照要特别强调不要直接用rm删“快照文件”。qcow2的内部快照数据是记录在同一个镜像文件内部的不是独立小文件。必须通过qemu-img snapshot -d来删除否则容易把还在链上的元数据搞坏。如果用的是外部快照情况更复杂需要走块位图合并的流程不要贸然删。4.2 resume之前先确认五件事当磁盘空间已经明显清理出余量我的习惯是先用一个检查脚本确认状态再执行resume。脚本大致是下面这个样子#!/bin/bash VMbusiness-x DISK_PART/var/lib/libvirt/images THRESHOLD85 usage$(df -h $DISK_PART | awk NR2{print $5} | tr -d %) inode$(df -i $DISK_PART | awk NR2{print $5} | tr -d %) echo 当前分区使用率: ${usage}% echo 当前inode使用率: ${inode}% if [ $usage -lt $THRESHOLD ] [ $inode -lt $THRESHOLD ]; then echo 空间充足准备唤醒虚拟机 virsh resume $VM else echo 空间不足请先清理磁盘 exit 1 fi脚本里刻意没有直接resume而是把检查放在前面。原因很简单——如果空间不足就resume虚拟机恢复运行后可能立刻再次因为写入失败而重新进入paused状态来回折腾几次guest内的应用状态会变得更加不可预期。我实际执行前还会多确认几点df -h显示目标分区使用率降到85%以下df -i确认inode使用率正常lsof L 1确认没有进程占用deleted文件导致空间未真正释放qemu-img check对镜像做一次完整性检查确认没有明显的元数据损坏日志里不再出现新的No space或I/O error记录。4.3 resume之后的核验步骤执行virsh resume 虚拟机名称后先确认virsh list --all里状态已经变为running。然后进guest内做几件事执行dmesg | grep -i error|read-only|i/o确认没有文件系统错误。对受影响分区检查挂载状态必要时mount -o remount,rw重新挂载。检查关键业务进程、数据库连接是否恢复。观察一段时间至少十几分钟确认不再掉线。这里有个后来才注意到的细节暂停期间guest的时钟会跳变。如果guest里没有部署chrony或ntpd有些应用会因为时间断层产生奇怪的告警。所以恢复后检查一下时间同步也是实际运维中的必要一步。5. 事故复盘后的持续加固监控、预留与策略调整5.1 监控体系磁盘空间、inode、VM状态一起管这次事故之后我重新审视了整套监控体系。过去只盯CPU、内存、带宽完全没把磁盘空间放在核心监控位置这是很大的疏漏。现在部署的宿主监控至少包含四项宿主机分区使用率85%告警90%严重inode使用率85%告警VM状态变化事件尤其是paused变化libvirt/QEMU日志或系统日志中关于I/O error、No space的持续扫描。一个简单的监控脚本可以做参考#!/bin/bash THRESHOLD85 ALERT_ADDR配置你的告警接收地址 usage$(df -h / | awk NR2{print $5} | tr -d %) inode$(df -i / | awk NR2{print $5} | tr -d %) if [ $usage -ge $THRESHOLD ] || [ $inode -ge $THRESHOLD ]; then echo 宿主机磁盘空间或inode告警使用率: ${usage}%, inode: ${inode}% \ | mail -s KVM宿主机磁盘告警 $ALERT_ADDR fi监控脚本尽可能做得简单直接重点在于覆盖率和告警及时性而不是堆功能。为了配合日常巡检我还会把“虚拟机总数”“paused状态虚拟机数量”这类指标一并纳入汇总一旦出现非预期的paused立刻能定位到具体主机和VM。5.2 磁盘错误策略要不要改我保留stop的理由故障恢复之后我重新研究了域的XML配置。libvirt的driver元素支持error_policy属性例如disk typefile devicedisk driver nameqemu typeqcow2 error_policystop/ source file/var/lib/libvirt/images/business-x.qcow2/ target devvda busvirtio/ /diskstop是默认值也是我目前大多数场景下倾向于保留的值出事后VM进入paused状态管理员有充足时间从宿主机侧处置不会让故障瞬间扩散到大范围业务中断。如果环境里的客户机本身具备很强的I/O错误自愈能力比如上层应用会自己重试和滚动那可以考虑使用enospc或report把错误透传给guest自行应对。但我个人经验是绝大多数业务系统并没有做好“磁盘I/O错误”的预案盲目把错误交给guest反而会导致文件系统转只读、数据库双机分裂等二次事故。所以我的建议是除非你对guest内部应用有充分验证否则保持stop策略但一定要把error_policy显式写进域定义而不是依赖QEMU的隐式默认值。显式写上既有明确意图后续变更时定位也容易。5.3 目录规划与空间预留镜像和日志必须“分居”这次故障的根因之一就是镜像目录和系统日志所在分区共用同一块盘。事后做长期方案时我把/var/lib/libvirt/images从根分区拆了出来单独挂载一块独立磁盘或逻辑卷。根分区只用于系统本身并预留至少20%的空闲缓冲镜像盘则用于存放镜像、快照和备份临时文件同时定时回收过期快照。这样规划的好处很明显即使系统盘被日志写满虚拟机的镜像盘还是稳的反过来即使镜像盘满了也不会拖累宿主机本身的对外服务。空间规划上大原则就是把容易产生大文件、持续增长的内容分别拆分到不同分区。镜像、日志、数据库二进制日志、容器镜像都属于这一类。分区拆细之后容量预警也能更精准不会出现“一个/分区把所有问题都盖住”的局面。5.4 事故后的复盘清单最后整理一份复盘清单供遇到类似情况的同行参考出现VM不可连接第一动作是virsh list --all和virsh domstate不要直接重启。状态为paused (ioerror)时按“状态→日志→磁盘”的顺序排查。df -h和df -i一起看lsof L查deleted文件句柄。清理空间按“先快照→再日志→后临时文件”的优先级不碰业务数据。resume前确认空间余量、镜像完整性、日志中不再出现新错误。恢复后进guest检查文件系统、时间同步、关键进程。长期建设镜像目录独立、监控报警、日志轮转、定时回收快照。我在实际操作中体会最深的一点是处理这类问题的瓶颈往往不在于把空间清理出来也不在于执行resume指令而在于你是否有从容判断和处置的缓冲地带——也就是监控、规划、策略带来的“事前余量”。这次“血案”之后我把每台KVM宿主机都加上了磁盘空间和inode的预警也把镜像目录和系统盘彻底分开了。短期看是多花了一点部署成本长期看是给整个虚拟化环境添了一张保险单。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑