☰
宿主机磁盘耗尽致KVM虚拟机paused?原理、恢复与预防全攻略
2026/10/14 22:46:17 网站建设 项目流程

凌晨两点,某监控平台突然弹出一连串告警,某业务系统多个接口超时,主节点无法连通。登录宿主机检查时,KVM虚拟机列表里赫然显示着一个此前从未见过的状态——paused。尝试virsh resume却毫无反应,再一看df -h,根分区已塞到 100%。那一刻基本就明白了:这不是普通的业务故障,而是一场被磁盘空间耗尽悄然引爆的KVM 暂停“事故”。

这种故障很典型,也非常隐蔽。它不会直接让你看到“磁盘满”的报错,而是先表现为虚拟机无响应、服务中断,如果你不了解 KVM 在底层 I/O 失败时的处理逻辑,很容易陷入“网络不通查防火墙、服务异常查进程”的无效排查里。这篇文章就完整复盘一次宿主机磁盘耗尽导致 KVM 虚拟机意外暂停的过程,从现象、原理、恢复步骤到后续的预防机制,希望能给正在运维 KVM 环境的同行一个可直接“抄作业”的避坑参考。

1. 故障现象与初步处置

1.1 业务中断的奇怪表现

那晚最先收到的告警是业务侧发来的:某关键应用的主节点 10 分钟前开始无法访问,部分依赖它的服务陆续进入降级状态。从监控面板看,一台承载核心数据库的虚拟机已经丢包 100%,但宿主机本身并没有重启,其他几台虚拟机也还正常。

当时第一反应是虚拟机内部的服务崩溃了,于是通过宿主机尝试virsh console直接登录虚拟机排查。结果发现控制台没有任何输出,滚动键盘也没反应。再执行virsh list --all,状态栏显示的是paused,而不是常说的running或shut off。

这里有个容易忽略的关键点:KVM 虚拟机进入paused状态时,进程还活着,网络和磁盘设备看起来也都在,但对外的业务已经全部中断。它既不像宕机那样干脆,也不像重启那样会自动恢复,而是“卡死”在原地等人工介入。

1.2 初步排查:宿主机与虚拟机的“割裂”状态

登录宿主机后,我先看了一下资源负载,CPU 和内存都正常,说明不是宿主机整体过载。再执行virsh dominfo <虚机名>,输出里明确标注了状态为paused,但没有多给出暂停的具体原因。

既然虚拟机进程还在,最直接的办法是查看系统日志。宿主机上执行dmesg -T,很快翻到了类似这样的记录:

[日期时间] qemu-system-x86_64: -drive file=/var/lib/libvirt/images/vm01.qcow2,format=qcow2: No space left on device [日期时间] qemu-system-x86_64: Failed to write to disk, VM paused

到这里基本锁定了方向:磁盘 I/O 写入失败,触发了 KVM 的暂停保护机制。为了确认宿主机空间状况,执行df -hT,果不其然,根分区使用了 100%,/var/lib/libvirt/images所在的挂载点已经没有剩余空间。

先记录当前所有虚拟机的状态,接着排查是什么占满了磁盘。这里用到一个非常实用的命令组合:

du -sh /var/lib/libvirt/images/* | sort -rh | head -20 lsof | grep deleted

第一条命令查看各镜像文件的体积排序,第二条查看是否存在“已被删除但仍被进程占用”的文件。最终发现问题很有代表性:某台虚拟机的 qcow2 镜像在快速膨胀,同时系统日志目录里躺着一个体积异常巨大的日志文件,这两个因素叠加,直接挤占了最后那点空间。

2. KVM 暂停机制的底层逻辑

2.1 QEMU 对 I/O 错误的“自我防御”

KVM 虚拟机本质上是宿主机上的一个 QEMU 进程,虚拟机里的磁盘读写请求最终都会转化为宿主机的文件系统操作。当宿主机磁盘分区被打满,任何新的写入请求都会得到ENOSPC(No space left on device)错误。虚拟机内部的应用不会直接看到这个错误,因为它和物理磁盘之间隔着一层虚拟化转换。

QEMU 在设计上对 I/O 错误有明确的处理策略。当它向虚拟机磁盘镜像写入数据失败时,默认行为是暂停虚拟机,而不是把错误透传给虚拟机内部系统。为什么选择暂停而不是直接终止进程?因为磁盘写入失败意味着内存中的脏数据无法落盘,如果此时直接关闭进程,所有未保存的数据都会丢失,这比业务短暂中断更致命。

暂停状态相当于给运维人员留了一个“抢救窗口”:进程被冻结,内存状态保留,磁盘不再进行写入操作,等待管理员清理空间后再恢复。把整个过程类比一下,就像你正在往一个已经装满的仓库里送货,卸货员发现实在塞不下就按下了暂停键,而不是把整车的货直接扔了。

2.2 为什么不是关闭而是暂停

在qemu.conf或虚拟机的 XML 配置中,磁盘设备可以指定 error 策略,包括stop、ignore和enospace。如果配置为stop,一旦发生 I/O 错误,虚拟机立即进入暂停状态;如果配置为ignore,QEMU 会继续运行但忽略错误,这种策略非常危险,因为虚拟机的文件系统可能已经出现数据损坏;enospace则可以理解为“空间满了就暂停”的专用策略。

大多数 Linux 发行版的 libvirt 默认配置,会把错误策略级联到 QEMU 层并采用暂停逻辑,所以我们在排障时看到的才是paused而非彻底的shut off。理解了这一层机制,再回头排查就顺理成章了:先在宿主机日志中找到对应的 I/O 错误记录,再去检查存储空间的占用情况,而不是盯着虚拟机内部的应用日志瞎忙。

注意:ignore策略虽然看起来能“保持业务连续”,但实际上会让虚拟机在满盘状态下继续写入,导致文件系统产生不可预测的损坏,后续恢复成本远超一次快速暂停。除非你有底层的卷快照保护机制可以随时无损回滚,否则不建议在生产环境使用。

3. 磁盘空间耗尽的全路径复盘

3.1 磁盘是怎么被“吃光”的

这次故障的根因不是单一事件,而是两个问题叠加的结果。第一,日志目录里有一个超过 20GB 的应用日志文件没有参与 logrotate 轮转,它的增长一直没有被约束;第二,那台问题虚拟机的 qcow2 镜像因为业务量突增,在短短几小时内膨胀了十几 GB。

如果只是其中一个因素,磁盘可能还不会马上打满,但两者恰好同时发生,宿主机根分区的剩余空间直接归零。这里还有一个非常隐蔽的细节:日志文件其实可能已经被logrotate改名甚至删除了,但持有该文件的进程依然在往里面写入,磁盘空间并不会因此释放。

这就是为什么排查磁盘满时一定要执行lsof | grep deleted。那种“文件明明删了但空间没回来”的情况,通常就是某个进程还保持着对已删除文件的写句柄。这次也不例外,日志轮转其实是生效了的,但由于进程没有重启,旧文件描述符始终被占用,20GB 空间被“隐形”锁住。

3.2 从镜像增长到 ENOSPC 的连锁反应

qcow2 是一种按需分配的镜像格式,虚拟机内写入数据时,宿主机上的镜像文件会按需增长。当宿主机分区没有足够空闲空间时,写入请求触发 qcow2 元数据分配失败,进而冒出来No space left on device的错误。QEMU 捕获到这个写入失败后,按照预设策略将虚拟机置为暂停状态。

从这个角度回看,问题还出在没有给镜像文件所在分区预留足够的“弹跳空间”。很多运维习惯把所有资源都塞到一个根分区里,用满了才想起扩容。虚拟化环境下镜像文件的增长往往是非线性的,触发一次大批量数据写入(比如频繁更新索引、大量导入日志),磁盘可能瞬间告急。

经验教训:给/var/lib/libvirt/images所在挂载点预留至少 20% 的空闲空间,不要只盯着“当前够用”。qcow2 镜像增长很少给你“慢慢来”的机会,一旦写满,所有活动虚拟机会在同一时间被集体冻结,那个场面可比单台暂停精彩多了。你可以在监控里针对这个分区的使用率单独设置较早的告警阈值,比如 80% 就提醒,90% 就要着手清理或扩容。

4. 恢复操作与验证细节

4.1 清理空间与恢复虚拟机的正确顺序

遇到虚拟机因磁盘满被暂停,第一反应往往是直接执行virsh resume。但在空间尚未释放之前,这个操作通常会直接失败,或者虚拟机恢复几秒钟后再次被暂停,甚至可能引发更复杂的文件系统问题。正确顺序应该分三步走。

第一步,先释放磁盘空间。这轮操作里,我直接对那个被进程占用的日志文件做了处理:通过kill -HUP <进程PID>让持有句柄的进程重新打开日志文件,立刻释放了那 20GB 空间。第二步,确认空间确实释放成功,执行df -hT看到使用率下降到安全水位以下。第三步,再对暂停的虚拟机执行virsh resume <虚机名>。

恢复时使用virsh resume后,可以执行virsh list --all和virsh dominfo <虚机名>确认状态从paused变为running。如果虚拟机内部时钟出现明显漂移(暂停期间时间会停滞),还需要登录虚拟机执行时间同步操作,比如:

chronyc -a makestep # 使用 chrony 同步 systemctl restart systemd-timesyncd

4.2 业务恢复后必须做的数据一致性检查

这一步是最容易被跳过的环节。虚拟机在暂停之前,正处于磁盘写入途中,部分页面缓存可能在内存里尚未落盘,或者已经写入磁盘的块与文件系统元数据之间存在不一致。恢复运行后,这些不一致可能不会立刻暴露,但数小时或数天后可能突然演化为文件系统只读或服务周期性崩溃。

对于 ext4/xfs 这类文件系统,建议在故障恢复后安排一次短时间维护窗口,执行文件系统检查。虚拟机关机状态下可以使用virt-rescue或直接挂载 qcow2 镜像进行fsck,如果虚拟机本身是数据库节点,更稳妥的做法是让数据库自行进行崩溃恢复流程,必要时将实例切换至从库进行增量数据校验。

那台承载数据库的虚拟机在恢复后暴露了一个更棘手的问题:部分数据文件在暂停前处于“已写入一半”的状态,数据库进程虽然启动了,但某些表的索引报告损坏。最终只能从备份中抽取增量数据结合 binlog 进行一致性修复。这里想说的是,KVM 的暂停机制虽然是防丢数据的“缓冲带”,但它并不能替代你本身的备份与恢复流程。故障恢复后如果你只看到服务起来了就觉得一切正常,隐患迟早会在未来某个时刻加倍找回来。

实际操作里,我建议在恢复虚拟机之前先记录暂停持续的时间段,然后与虚拟机内部的应用监控做交叉比对。凡是暂停期间可能涉及写入的业务操作,都应该在上线前做一轮数据抽检。别嫌麻烦,这一步能省掉后面大量的“疑难杂症”排查时间。

5. 预防与长效机制

5.1 磁盘水位监控与告警阈值设置

磁盘满这类故障和网络故障不一样,它通常是渐进式的,完全可以通过监控提前拦截。除了基础的 CPU、内存、网络监控,宿主机必须单独建立磁盘分区的使用率监控项,并且阈值要分级别设置:

阈值建议动作说明
使用率 70%观察趋势记录增长速度,估算剩余可用时间
使用率 80%发出警告排查是否存在异常增长文件,评估扩容
使用率 90%立刻处理清理日志、下线临时业务或扩容存储
使用率 95% 以上紧急告警人工介入,准备应急流程

告警方式不仅限于监控面板弹窗,还可以接入企业即时通信机器人推送。阈值设置时尤其要注意:虚拟化宿主机与常规物理服务器的规则不同,不要等 90% 才想起处理,因为 qcow2 镜像增长往往是瞬时的,可能在几分钟内从 85% 跃升至 100%。

5.2 KVM 层的“护栏”:预留空间与错误策略

给虚拟化环境增加一道“缓冲垫”非常有价值。一种常见做法是单独使用一个大分区或者独立的存储池来存放所有虚拟机镜像,根分区只放系统文件。这样即使根分区出现日志堆积,也不会直接影响虚拟机的运行。镜像所在分区则可以通过 LVM 或者存储阵列的快照技术,在空间不足时快速扩容。

也可以在虚拟机 XML 配置中对每个磁盘明确设置 I/O 错误处理策略。如果业务能接受“磁盘满立刻暂停”,那么保持默认的stop策略即可;如果你使用的是具备快照回滚能力的分布式存储,可以考虑更激进一些的策略。但这里我要明确地提醒一句:对于传统本地磁盘存储,强烈不建议把策略调整为ignore。我见过一个案例,对方为了让业务“不中断”把策略改成了忽略错误,结果虚拟机继续在满盘状态下运行了十几分钟,最后文件系统损坏到无法正常挂载,只能从备份恢复,损失比暂停一次大得多。

5.3 演练与应急预案

故障预案一定要定期演练。很多人虽然写了文档,但真到故障现场,紧张情绪会让人忽略最基本的排查步骤。建议每个季度抽出时间做一次“磁盘满故障模拟”演练:手动创建一个大文件占满分区,观察哪台虚拟机最先进入暂停状态,记录从告警到恢复的平均时间,检验监控通知链路是否畅通。

演练时还可以顺手验证几个参数:virsh resume在空间不足时是否如预期失败、日志文件被占用句柄时lsof | grep deleted能否快速定位、清空 space 后虚拟机能否无障碍恢复。一个值得养成的习惯,是在每台宿主机上保存一份简单的排障命令速查清单,内容包括常用检查命令、存储路径、联系人信息,这样即使换人值班也能快速跟进。

6. 实操心得与避坑总结

6.1 故障排查速查表

现象排查方向常用命令
虚拟机显示paused,业务中断检查宿主机磁盘空间df -hT
磁盘未满但虚拟机暂停检查底层存储设备状态dmesg -T、journalctl -u libvirtd
空间满了但无法释放查找已删除仍被占用的文件lsof | grep deleted
恢复后将数据不一致需要数据一致性检查fsck、数据库崩溃恢复流程
虚拟机频繁暂停检查 qcow2 镜像容量与快照层qemu-img info <镜像>

6.2 几条值得牢记的实操经验

如果在dmesg里看到“Failed to write to disk”,不要立刻重启宿主机或强制关闭虚拟机。先把空间释放出来,再尝试virsh resume。强制关闭会让内存中的数据彻底丢失,有些场景下这比暂停更伤。

磁盘空间满了之后,尽量不要在虚拟机内执行大量写操作来“清理内部文件”。因为虚拟机内部文件删除只会释放虚拟磁盘的块,而这些块是否归还给宿主机文件系统取决于 qcow2 是否执行了 trim 操作。如果配置不支持 discard,虚拟机内删除文件并不会减少宿主机上的镜像体积,处理起来又慢又容易让人产生误解。

排查时间线一定要记录清晰。从虚拟暂停到恢复运行,这段时间窗口内,所有对虚拟机内部数据的有状态操作都应该被视为“可能受影响”。我通常是先查看监控平台的时间线,确认暂停开始的确切时间,再结合应用日志决定哪些数据需要重新校验。这样操作下来,故障处理完不仅心里有数,后续写复盘报告也有据可依。

还有一个小细节,日志文件的轮转配置一定要加进宿主机基线配置里。这次的问题根源是应用日志进程长时间持有写句柄,而日志轮转系统没有将这类进程纳入copytruncate模式,导致空间永不释放。你可以在 logrotate 配置里针对这类应用加一行copytruncate配置,它会让日志轮转以复制和截断的方式完成,这样即使进程不重启,旧文件的空间也能正常释放。设置完再配合定期的日志体积巡检,基本可以杜绝这类“隐形占盘”问题再次发生。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询