尧图精选

KVM/QEMU/qcow2/RAW/COW/ROW分层解析与磁盘选型调优

🕒 发布时间:2026/10/1 4:31:52 📁 来源:尧图网络
事情得从上周一次扯皮说起。同事在群里问给服务器做系统用 kvm 还是 qemu另一个回qcow2 和 raw 到底哪个快第三个人甩出一句COW 和 ROW 不是一回事吗。三个问题搅在一起本质上是在问三个完全不同层面的东西跑虚拟化用什么组件、磁盘镜像存成什么格式、镜像格式内部用的什么写入策略。把这三层揉成一团就会出现kvm、qemu、qcow2、RAW、COW、ROW 傻傻分不清的经典混乱。我把这套东西重新梳理了一遍顺手在 Ubuntu 24.04 上从零跑了一台机器踩过的坑也都记下来了。适合两类人看一类是刚开始接触 Linux 虚拟化、想搞明白这套名词到底谁是谁的人另一类是在生产环境里用 KVM 跑业务、需要做磁盘格式选型和性能调优的人。看完之后你至少能做到三件事说清这几个缩写分别在栈的哪一层、根据业务场景选对镜像格式、用 qcow2 的快照链做安全的回滚和瘦身。1. 先把这堆缩写的层级关系钉死名词混乱的根源是它们压根不在一个层面上。KVM 是内核里的东西QEMU 是用户态的程序qcow2 和 RAW 是磁盘文件的格式COW 和 ROW 是数据写入的策略。硬要类比的话KVM 是发动机QEMU 是整车qcow2/RAW 是油箱的规格COW/ROW 是加油的方式。你不能问发动机和油箱哪个好同理也不能问kvm 和 qcow2 哪个快。1.1 KVM内核里的一颗模块只负责让 CPU 跑起来KVM 全称 Kernel-based Virtual Machine它不是一个软件包也不是一个能双击启动的程序。它本质上是 Linux 内核的一组模块通用的kvm.ko加上平台相关的kvm_intel.ko或kvm_amd.ko。这三个模块加载之后内核会多出一个字符设备/dev/kvm用户态程序通过 ioctl 调它就能把一段代码丢进 CPU 的虚拟化扩展Intel VT-x 或者 AMD-V里直接执行。这件事的意义在于如果没有 KVM虚拟机里的每一条指令都得靠软件模拟翻译一遍性能会掉到个位数百分比有了 KVM客户机的特权指令由 CPU 硬件直接兜住只有真正需要假装的部分比如模拟一块老式网卡才走软件路径。所以我经常跟人说KVM 的贡献是让虚拟机的计算性能接近物理机它不管磁盘、不管网络、不管设备它只管 CPU 和内存的隔离。验证 KVM 是否可用两条命令就够grep -E -c (vmx|svm) /proc/cpuinfo # 大于 0 说明 CPU 支持硬件虚拟化 ls -l /dev/kvm # 存在说明内核模块已加载vmx是 Intel 的旗标svm是 AMD 的。如果第一条返回 0通常要去 BIOS/UEFI 里开一下虚拟化相关选项如果 CPU 支持但/dev/kvm不存在检查kvm_intel是不是被别的模块占用了——嵌套虚拟化、部分安全策略、某些云厂商的实例类型都会屏蔽它。1.2 QEMU真正在用户态拼出一台电脑的整机模拟器QEMU 是一个纯用户态的程序它做的事是完整模拟一台计算机主板芯片组、PCI 总线、磁盘控制器、网卡、显卡、USB 控制器、UEFI/BIOS 固件全都由它用软件搭出来。它有两套执行引擎一套是纯软件解释TCGTiny Code Generator另一套是接上 KVM 走硬件加速。这就解释了为什么装了 KVM 还得装 QEMU。KVM 只提供CPU 能跑这个能力至于客户机看到的是一块 virtio 磁盘还是 IDE 磁盘、网卡是 e1000 还是 virtio-net、启动固件是 SeaBIOS 还是 OVMF这些全得由 QEMU 来构造和分发。你敲的qemu-system-x86_64就是 QEMU 的主程序-accel kvm就是告诉它这条虚拟机用 KVM 加速别用 TCG 慢慢磨。顺带说一句qemu-system-aarch64、qemu-system-riscv64这些也是 QEMU只是模拟的目标架构不同。KVM 加速只在宿主机架构等于客户机架构时才能用也就是 x86 上跑 x86跨架构就必须退回 TCG 软件模拟性能会差两个数量级。这是很多人做 arm64 模拟时最大的预期落差来源。1.3 libvirt/virsh/virt-manager站在 QEMU 上面的管理层裸敲 QEMU 参数能干所有事但参数太长、太容易写错而且没有统一的虚拟机清单概念。libvirt 就是为了解决这个它把虚拟机的定义写成一份 XML存到固定位置然后用virsh或virt-manager去操作。libvirt 底层还是调 QEMU它不替代 QEMU只是加了一层编排和生命周期管理。日常能感受到的差别是裸 QEMU 启动的虚拟机关了终端进程就没了virsh list里也看不见走 libvirt 的虚拟机virsh list --all能列出全部还能设置开机自启、做快照、热插拔磁盘、查询块设备层信息。做生产环境我强烈建议走 libvirt 这套不是因为它更好看而是因为它的可观测性和可重复性完全不是一个量级。1.4 另一个 KVM键盘鼠标显示器共享器这里必须插一句因为搜kvm的人里有一半其实在找硬件设备。KVM SwitchKVM 切换器、KVM 共享器是把一套键盘、鼠标、显示器接到多台主机上用按键或按钮切换的硬件盒子和虚拟化里的 KVM 除了缩写一样没有任何关系。至于热词里提到的怎么看 KVM 的 8K 认证判断逻辑很直白先看带宽8K60Hz 在 4:4:4 无色度抽样的情况下需要 HDMI 2.1 的 48Gbps 或者 DisplayPort 2.0 的 UHBR 通道标称 18Gbps 的老设备一定做不到完整 8K再看它写的具体规格很多产品标的是 8K30Hz 或者 8K60Hz 4:2:0这两种和完整 8K 60 帧是两码事最后看线材主机到切换器这段如果用的还是老线切换器再强也没用。买之前把这三个数字对一遍比看任何宣传语都管用。1.5 一张表把六层关系收拢名词所属层级具体是什么少了它会怎样KVM内核模块/dev/kvm字符设备提供 CPU 硬件虚拟化虚拟机只能用 TCG 软件模拟性能骤降QEMU用户态程序整机模拟器构造主板/设备/固件内核有能力但没人去用它组机器libvirt管理服务XML 定义 virsh/virt-manager 前端能跑但没有统一清单和生命周期管理RAW文件格式无元数据字节即扇区无法内建快照无法增量备份qcow2文件格式带 L1/L2 映射表、引用计数、快照缺了灵活快照与后端文件链能力COW / ROW写入策略数据落盘时的重定向方式决定写放大、碎片与快照成本2. RAW 与 qcow2磁盘格式到底怎么挑搞清层级之后第二个高频问题就是格式选型。RAW 和 qcow2 是 QEMU 支持的一堆格式里最常用的两个很多人只知道raw 快、qcow2 功能多但不知道具体快在哪、功能多在哪选的时候全靠感觉。2.1 RAW 不是格式是没有格式RAW 的本质是文件里的第 N 个字节就对应磁盘的第 N 个字节。没有头部、没有映射表、没有元数据。好处是很实在的QEMU 读写的时候不需要做任何地址翻译也不需要维护额外的元数据结构I/O 路径最短性能上限最高而且这文件可以直接挂给别的工具用——dd、losetup、离线取证工具都能直接读。代价也很明显。第一它不支持内部快照想做快照只能在下层文件系统或者 LVM 上想办法。第二它不支持后端文件链做增量备份得自己搭一套。第三如果底层文件系统不支持稀疏文件创建 40G 的 RAW 就真占 40G。这里有个容易被忽略的细节qemu-img create -f raw disk.img 40G默认会创建稀疏文件实际占用接近 0。你可以用du -h disk.img和ls -l disk.img对比一下前者是实际占用后者是逻辑大小。但在 ext4 上稀疏文件写入过程中会不断分配新块长期运行之后碎片会比较厉害顺序读性能会衰减。生产环境里我一般会配fallocate预分配把碎片问题提前消掉。2.2 qcow2 的目录结构L1/L2 与引用计数qcow2 全称 QEMU Copy-On-Write 格式它做的核心事情是间接寻址。客户机发出的是读第 12345 号扇区qcow2 要把它翻译成读宿主文件里的第 X 个字节。这套翻译靠三级结构完成文件头里有 L1 表的指针L1 表指向若干张 L2 表L2 表里存的是真正数据簇的位置。理解这个结构两个参数才算真的懂了cluster_size簇大小默认 64K。L2 表每一项占 8 字节所以一张 64K 的 L2 表能装 8192 项每项覆盖一个簇也就是一张 L2 表覆盖 8192 × 64K 512MB 的客户机地址空间。如果你把簇改成 1M单张 L2 表就能覆盖 8GB元数据开销显著下降但小文件写入的放大也会变严重。refcount_bits引用计数位宽qcow2 v3 默认 16 位。引用计数用来记录每个簇被多少个快照/后端文件共享着共享计数归零才能回收。位数越高能同时容纳的快照层数越多但元数据占用也越大。这两个参数在qemu-img info里都能看到qemu-img check还能校验引用计数有没有算错——快照删不干净、文件删了空间不释放八成是引用计数出了问题。2.3 预分配四种模式的真实取舍qemu-img create -o preallocation有四个取值很多人随手写metadata但说不清为什么。我在下面这张表里把它们的实际行为写清楚预分配模式元数据是否预分配数据块是否预分配创建耗时适用场景off否否秒级临时测试机、开发环境metadata是否秒级到十秒级生产主力兼顾创建速度和运行稳定性falloc是是用 fallocate 快速占位内容为空洞秒级需要固定占用、避免中途扩容抖动full是是并且逐字节写零分钟级取决于盘大小需要严格保证空间、审计场景full和falloc的差别值得单独说full会真把每个字节写成 0创建 1T 的盘可能要十几分钟falloc只是让文件系统把这些块预留下来不写内容速度快得多。除非有合规审计要求磁盘必须写零否则falloc就够了。一个实际的经验用metadata预分配之后qcow2 在客户机第一次写某个簇的时候仍然要分配新簇并更新 L2 表这段时间的写入延迟会有轻微抖动。如果你的业务对延迟敏感比如数据库用falloc更稳。我做过一组对比同样跑随机写metadata模式的前 30 秒写入吞吐波动比falloc明显大一些之后就趋于一致了。2.4 选型表什么业务配什么格式业务特征推荐格式理由单机数据库、高 IOPS 场景RAW LVM 快照无间接寻址I/O 路径最短需要频繁做快照回滚的测试环境qcow2 外部快照秒级创建回滚成本低需要在线迁移、增量备份qcow2 后端文件链只传变更簇带宽省得多磁盘一次性交付、只读挂载RAW或qemu-img convert后转出格式通用任何工具都能读桌面虚拟化、镜像模板分发qcow2 压缩镜像 后端文件模板共享增量盘极小我的默认建议是没有特殊理由就上 qcow2。RAW 的性能优势在纯 SSD、virtio、cachenone的组合下实际差距往往在个位数百分比而 qcow2 带来的快照、克隆、迁移便利性在运维层面的价值远超这点性能差。只有在明确测出瓶颈落在存储路径时才值得为 RAW 放弃那些能力。3. COW 和 ROW被混淆最多的一对概念这两个词是混乱的重灾区。很多人以为它们是 qcow2 和 RAW 的别称其实不是。COW 和 ROW 描述的是当你要修改一份被共享的数据时怎么处理旧数据这件事它是文件系统、存储快照、甚至内存管理里通用的一套策略。3.1 COW 的操作顺序先抄旧的再写新的写时复制Copy-On-Write的流程是数据块 A 被两个快照共享现在要改 A。系统先把 A 的原始内容完整复制到新位置 B然后对 B 做修改最后把指针从 A 指向 BA 的引用计数减一。全程 A 的内容始终不变所以任何还指向 A 的快照都还能读到改动前的状态。这个顺序决定了 COW 的代价写操作变成读旧 写新 改指针三步。在随机小写入的场景下这个放大效应很明显而且长期运行会产生碎片因为新块的位置是随机的。为什么很多文件系统在开了快照之后写入性能会明显下滑原因就在这里——只要底层还有快照引用每次写都得走这套流程。放在 qcow2 里看这个机制就体现在客户机第一次写某个未分配的簇时QEMU 要在宿主文件里找一个空闲簇写入数据更新对应的 L2 表项同时更新引用计数。这不是复制旧数据那么严格但逻辑内核是一致的——不改共享结构改了就新建。3.2 ROW 换了个思路新数据写新地方旧块直接还给分配器重定向写Redirect-On-Write的动作顺序不一样数据要改直接把新内容写到全新的位置 C把指针指过去然后立刻把旧块 A 的引用计数减一如果减到零A 可以被回收。整个过程中旧数据不需要被复制只需要被保留到引用归零为止。省掉复制旧数据这一步就是 ROW 相对 COW 的核心优势写放大更低写路径更短快照创建几乎是纯元数据操作速度极快。代价是碎片化更严重——每次写都跑到新位置数据在物理介质上的连续性会越来越差而且空间回收依赖后台的垃圾回收机制如果 GC 跟不上写入速度就会出现空间明明释放了但可用容量涨不回来的现象。3.3 两者对照对比项COWCopy-On-WriteROWRedirect-On-Write写操作顺序先复制旧块到新位置再改新块直接写新位置旧块引用减一首次写延迟较高包含一次读取较低只写不回读空间回收相对及时旧块立即可释放依赖后台 GC可能滞后碎片倾向中等偏高快照创建成本低极低典型落地qcow2 簇分配、多数文件系统快照部分存储阵列的快照、日志结构文件系统回到最初的问题上qcow2 名字里带 COW指的是它采用写时复制的分配策略而 ROW 是另一条技术路线不是 qcow2 的子集也不是 RAW 的别名。搞清这个以后再看到COW 和 ROW 哪个好这种问法就知道问题本身少了限定条件——得先问是哪一层的 COW。3.4 qcow2 快照链就是 COW 的落地形态qcow2 的实际用法里COW 最直观的体现就是后端文件链backing file chain。做法是先做一个只读的基础镜像然后基于它派生一个可写的增量盘增量盘只记录相对于基础镜像的变更。开十台测试机共用一份 20G 的基础镜像每台的增量盘可能只有几百兆。创建方式很简单qemu-img create -f qcow2 -b base.qcow2 -F qcow2 vm01.qcow2-b指定后端文件-F指明后端文件的格式。读的时候QEMU 从最上层往下找找到哪个簇在顶层有映射就用顶层没有就往下层找。写的时候只有被修改的簇才会分配在顶层文件里。这套机制的坑在于链条不能太长。每多一层一次未命中就要往下遍历一层元数据缓存压力也会累加。我见过有人用脚本每天派生一层跑了三个月叠了九十层读性能掉到没法用。经验值是链条深度控制在三层以内超过就做一次blockcommit把中间层合并掉。virsh blockcommit vm01 vda --active --pivot --verbose--active表示合并当前活跃层--pivot表示合并完成后把虚拟机切换到新文件上不用关机也不用重建虚拟机定义。4. 在 Ubuntu 24.04 上从零建一台 KVM 虚拟机前面讲原理这一节全是操作。我用的环境是 Ubuntu 24.04 Server物理机开了 VT-x网络是普通千兆。整个过程大约十分钟。4.1 装包三个包解决 90% 需求sudo apt update sudo apt install -y qemu-system-x86 qemu-utils libvirt-daemon-system \ libvirt-clients virtinst bridge-utils ovmf sudo usermod -aG libvirt,kvm $USER newgrp libvirt包的分工要说清qemu-system-x86提供qemu-system-x86_64主程序qemu-utils提供qemu-img、qemu-nbd这些工具libvirt-daemon-system是守护进程libvirt-clients提供virshvirtinst提供virt-installovmf是 UEFI 固件。usermod那行是把自己加进 libvirt 和 kvm 两个组否则每次virsh都得 sudo。装完之后检查服务状态systemctl is-active libvirtd virsh version virsh net-list --allvirsh version会打印两行版本号一行是 libvirt 库一行是 QEMU 二进制。这两个版本号要记住后面遇到兼容性问题经常要从这里对。4.2 建磁盘参数不是随便填的sudo mkdir -p /var/lib/libvirt/images sudo qemu-img create -f qcow2 \ -o preallocationmetadata,cluster_size64K,lazy_refcountson \ /var/lib/libvirt/images/ubuntu24.qcow2 40G三个参数我逐个解释。preallocationmetadata把 L1/L2 表提前建好避免运行中第一次写的时候还要分配元数据cluster_size64K是默认值如果你的客户机主要是大文件顺序读写比如视频转码、大数据临时盘可以调到1M减少 L2 表数量但小文件随机写的放大也会变大lazy_refcountson让引用计数延迟更新配合cachewriteback能提高写入性能代价是掉电时可能要做一次qemu-img check修复。建完验证一下qemu-img info --outputjson /var/lib/libvirt/images/ubuntu24.qcow2 qemu-img check /var/lib/libvirt/images/ubuntu24.qcow2check的输出里如果corruptions是 0说明元数据是干净的。以后每次虚拟机异常关机之后都建议跑一遍。4.3 建机BIOS 还是 UEFI老规矩用 SeaBIOS传统 BIOS能跑但 Ubuntu 24.04 的安装镜像对 UEFI 支持更好而且 UEFI 支持 Secure Boot。我这边直接用 UEFIsudo virt-install \ --name ubuntu24 \ --memory 4096 --vcpus 4 \ --cpu host-passthrough \ --machine q35 \ --disk path/var/lib/libvirt/images/ubuntu24.qcow2,formatqcow2,busvirtio,cachenone,ionative,discardunmap \ --cdrom /data/iso/ubuntu-24.04-live-server-amd64.iso \ --network networkdefault,modelvirtio \ --graphics vnc,listen127.0.0.1,port5901 \ --boot uefi \ --os-variant ubuntu24.04 \ --noautoconsole几个参数值得展开。--cpu host-passthrough把宿主 CPU 的指令集特性直接透传给客户机性能最好代价是这台虚拟机不能再迁移到 CPU 型号不同的宿主机上——如果你要做集群迁移改用--cpu host-model。--machine q35是模拟现代芯片组支持 PCIe比默认的 i440fx 更贴近真实硬件UEFI 场景下基本是必选项。磁盘那一串里busvirtio用的是 virtio-blk 半虚拟化驱动比模拟 IDE 快得多cachenone让 I/O 绕开宿主页缓存直接走 O_DIRECT避免双重缓存ionative启用原生异步 I/O只有cachenone时才生效discardunmap让客户机发来的 TRIM 指令能真正传导到宿主文件这对 qcow2 瘦身至关重要后面还会细说。--graphics vnc,listen127.0.0.1是只监听本地回环装系统的界面通过 SSH 端口转发看。这样不用把 VNC 端口暴露出去安全性更好。首次启动之后在另一个终端里把安装界面转出来ssh -L 5901:127.0.0.1:5901 userhost # 本地用任意 VNC 客户端连 127.0.0.1:59014.4 网络默认 NAT 够用桥接才进生产libvirt 默认会创建一个叫default的网络宿主上多出一块virbr0客户机通过它做 NAT 上网能出不能进。开发和测试完全够用但生产环境通常要客户机和宿主机在同一个二层网络里。桥接的配置在不同发行版和网络管理方式下差别很大。用 NetworkManager 的话命令行是sudo nmcli connection add type bridge ifname br0 con-name br0 \ ipv4.method manual ipv4.addresses 192.168.1.50/24 \ ipv4.gateway 192.168.1.1 ipv4.dns 223.5.5.5 sudo nmcli connection add type ethernet ifname enp3s0 master br0 con-name br0-slave sudo nmcli connection up br0然后把 libvirt 的默认网络切到桥接或者在virt-install时直接指定--network bridgebr0,modelvirtio。注意桥接会把物理网卡的 IP 挪到桥上中间会短暂断网远程操作的话最好配个定时恢复脚本兜底别问我怎么知道的。如果不想动宿主机网络还有一个折中方案是 macvtap客户机直接借用物理网卡的 MAC 直通配置量小代价是宿主机和客户机之间不能直接通信这是 macvtap 的固有特性且部分交换机对多 MAC 场景有限制。4.5 日常管理几个我会背下来的命令virsh list --all # 所有虚拟机状态 virsh dominfo ubuntu24 # 配置概览 virsh domblklist ubuntu24 # 磁盘列表 virsh domiflist ubuntu24 # 网卡列表 virsh vncdisplay ubuntu24 # 当前 VNC 端口 virsh console ubuntu24 # 串口控制台需客户机配好 ttyS0 virsh autostart ubuntu24 # 开机自启 virsh domstats ubuntu24 --block # 块设备实时统计virsh domstats --block这个命令我强烈建议记住。它会打印每个磁盘的读写次数、字节数、延迟做性能排查的时候能一眼看出瓶颈是在客户机内部还是在存储路径上。5. 不用 libvirt 的裸 QEMU以及跨架构模拟有时候就是需要裸跑 QEMU排查 libvirt 层的问题、做一次性的系统安装、或者跑一个临时环境。这时候理解 QEMU 的参数结构就很有价值。5.1 一条裸 QEMU 命令逐段拆qemu-system-x86_64 \ -accel kvm \ -machine q35,accelkvm \ -cpu host \ -smp 4,sockets1,cores2,threads2 \ -m 4G \ -drive filedisk.qcow2,ifvirtio,formatqcow2,cachenone,aioio_uring \ -netdev user,idn0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevn0 \ -vnc 127.0.0.1:1 \ -monitor stdio-accel kvm是加速开关老教程里的-enable-kvm已经废弃了。-smp 4,sockets1,cores2,threads2这种写法比单纯写-smp 4更明确某些客户机操作系统会根据拓扑调整调度策略。-netdev user是 QEMU 内置的用户态网络hostfwd把宿主的 2222 端口映射到客户机的 22 端口做临时调试最方便不需要任何 root 权限和网络配置改动。-monitor stdio把 QEMU 的监视器接到当前终端进去之后能敲info block、info network、snapshot这些命令。这个监视器不依赖客户机哪怕系统已经卡死了也能从宿主机侧看到状态是排查虚拟机毫无响应这类问题的第一现场。5.2 qemu 模拟 arm64预期要放低跨架构的场景很常见比如在 x86 开发机上验证 arm64 的镜像构建。QEMU 能做到但要走 TCG 软件模拟性能大概只有原生的十分之一到五分之一。qemu-system-aarch64 \ -machine virt \ -cpu cortex-a72 \ -smp 4 -m 4G \ -bios /usr/share/AAVMF/QEMU_EFI.fd \ -drive filearm64-rootfs.qcow2,ifvirtio,formatqcow2 \ -netdev user,idn0,hostfwdtcp::2223-:22 \ -device virtio-net-pci,netdevn0 \ -nographic-machine virt是 arm64 下最通用的虚拟平台-bios指向 AArch64 的 UEFI 固件Debian/Ubuntu 系在qemu-efi-aarch64包里路径通常是/usr/share/AAVMF/QEMU_EFI.fd。注意-nographic会把串口输出直接接到终端arm64 的virt平台默认会在串口上输出启动日志比折腾图形界面省事得多。这里有个必须强调的点arm64 的 TCG 模拟无论怎么调参都不可能快。如果你的目的是大规模构建 arm64 镜像正确做法是找一台真的 arm64 机器或者用支持原生 arm64 的构建服务。QEMU 模拟适合做功能验证不适合做性能测试。5.3 qemu-user-static另一条完全不同的路上面说的是 full-system 模拟还有一条路是用户态模拟只模拟指令集不模拟整机。装qemu-user-static之后配合 binfmt_misc可以直接在 x86 上运行 arm64 的单个二进制sudo apt install -y qemu-user-static binfmt-support sudo systemctl restart systemd-binfmt # 验证 docker run --rm --platform linux/arm64 arm64v8/alpine uname -m这条路比整机模拟轻量得多适合跑单进程、构建镜像、跑测试套件。代价是没有内核涉及内核模块、io_uring、特定系统调用的程序可能跑不起来。判断标准很简单只要能跑成容器镜像里的普通应用用这条路需要装系统、改内核参数、做网络实验走 full-system 那条路。6. 空间回收与性能调优qcow2 的三个后续动作虚拟机跑起来之后真正让人头疼的是 qcow2 文件只涨不跌。客户机里删了 20G 数据宿主的 qcow2 大小一点没变。这不是 bug是 COW 机制的必然结果——被删掉的簇在宿主文件里仍然占着位置只是客户机的文件系统不再引用它们了。6.1 让 TRIM 指令真正传导下去第一条要配的链路是discard。客户机的文件系统发出 TRIMvirtio-blk 传到 QEMUQEMU 再通过unmap告诉宿主文件系统这块可以还回去了。任何一个环节断掉空间都收不回来。客户机侧确认挂载参数mount | grep discard如果没带discard可以手动跑一次全盘 TRIMsudo fstrim -av我个人的习惯是不用挂载参数里的discard它在每次删除时都触发有额外开销而是配一个定时任务每周跑一次fstrim。这样开销可控空间也不会无限涨。宿主机侧确认discardunmap已经写进虚拟机定义virsh dumpxml ubuntu24 | grep -i discard如果没有编辑一下virsh edit ubuntu24 # 在 driver .../ 里加上 discardunmap改完之后需要重启虚拟机才生效。6.2 压缩矩阵三种压缩算法的实测取舍qcow2 支持在转换时压缩QEMU 5.0 之后还支持 zstd。压缩只在qemu-img convert时发生运行中的虚拟机不会实时压缩。# 转换成带压缩的 qcow2用 zstd qemu-img convert -p -O qcow2 -c -o compression_typezstd \ /var/lib/libvirt/images/ubuntu24.qcow2 \ /data/img/ubuntu24-zstd.qcow2三种压缩方式的取舍大致是这样zlib 兼容性最好任何版本的 QEMU 都能读压缩率中等zstd 压缩速度快、压缩率也不错但要求 QEMU 5.0 以上且镜像 compat 版本为 1.1lzo 压缩和解压都很快压缩率略低。如果镜像要分发给不确定环境的同事用 zlib 最稳妥。注意 zstd 有个前置条件镜像必须是 compat 1.1qcow2 v3。老镜像是 1.0 的话先升一下qemu-img amend -o compat1.1 disk.qcow26.3 快照链整理别让层数无限长用virsh snapshot-list能看到当前有哪些快照用virsh snapshot-create-as建一个virsh snapshot-revert回滚。qcow2 内部快照会直接生成一个新的层快照多了链就长了。整理的方式前面提过blockcommit还有一个是blockpull把后端文件的内容拉到当前盘里这样可以把基础镜像独立出去virsh blockpull ubuntu24 vda --wait --verbose整理完成之后用qemu-img map看一下实际分配的块分布确认没有大量细碎的空洞qemu-img map --outputjson /var/lib/libvirt/images/ubuntu24.qcow2 | head -20如果看到大量很短的 extent说明碎片化比较严重这时候做一次离线整理virt-sparsify --in-place /var/lib/libvirt/images/ubuntu24.qcow2--in-place是在原文件上操作省空间但风险高一定要先关机并备份。要更保险就用输出到新文件的方式virt-sparsify --compress --convert qcow2 \ /var/lib/libvirt/images/ubuntu24.qcow2 \ /data/img/ubuntu24-slim.qcow27. 常见问题与排查实录前面讲的都是正常路径实际运维里遇到的多半是异常路径。这一节我把遇到过的问题整理成速查表再挑三个典型场景展开说。7.1 问题速查表现象大概率原因排查动作处理方式虚拟机启动报Could not access KVM kernel module/dev/kvm不可用或权限不足ls -l /dev/kvmgroups加载 kvm 模块把用户加进 kvm 组qcow2 删除数据后体积不降discard 链路断掉virsh dumpxml查 discard 参数客户机跑fstrim配discardunmap定期 fstrim写入延迟周期性抖动元数据预分配不足或 cache 模式不当qemu-img info看 preallocationvirsh domstats看延迟改preallocationfalloccachenone快照回滚后磁盘变得很大内部快照层叠加过多virsh snapshot-listqemu-img info --backing-chain做 blockcommit 合并qemu-img check报引用计数错误异常掉电导致 lazy refcount 未落盘qemu-img check输出详情qemu-img check -r all修复迁移到另一台机器失败CPU 型号不兼容对比两台宿主virsh capabilities改--cpu host-model并重启虚拟机客户机网络时通时断MAC 地址冲突或网桥配置问题virsh domiflist看 MACbrctl show看端口重新生成 MAC检查网桥成员7.2 实录一qcow2 从 40G 涨到 180G 的真相有台跑日志分析的虚拟机分配的盘只有 40G跑了两周之后宿主上的 qcow2 文件涨到了 180G看起来完全不合理。第一反应是是不是被攻击了但qemu-img info显示虚拟大小还是 40G只是实际占用异常。排查路径是这样的先在客户机里df -h看到根分区只用了 12G。再用du -sh逐层扫发现/var/log/journal下面有个将近 100G 的目录——日志系统在磁盘满的时候反复写入失败、又反复重试写进去了大量无效数据然后被删除但 qcow2 里对应的簇已经分配出去了客户机删除并不会自动还给宿主。根因是discard链路没配。修复动作有三步先限制 journal 的最大占用避免再爆然后在宿主的虚拟机定义里加上discardunmap最后在客户机里跑一次sudo fstrim -av。执行完之后宿主上的 qcow2 从 180G 掉到了 26G。经验教训是只做客户机侧的容量监控是不够的宿主文件的实际占用必须单独监控。我现在的告警规则里qcow2 实际占用与虚拟大小的比值超过 1.5 就会触发提醒这时候去看一眼基本都能提前发现问题。7.3 实录二快照链叠了 27 层之后测试环境有个脚本每天给虚拟机打一个内部快照。跑了将近一个月有人反馈这台机器启动要三分钟跑起来也卡。我用qemu-img info --backing-chain一看链深度到了 27 层。原理层面说每读一个没在顶层命中的簇都要一层一层往下查 L2 表QEMU 会做缓存但缓存容量有限链一深缓存命中率就掉下来随机读的延迟会变得很难看。写入更麻烦每次写都要定位到当前活跃层元数据压力全堆在这一层上。处理方式是blockcommit但要注意顺序不能一次性把 27 层全合掉那样单次操作耗时太长中途出错风险大。我的做法是分批合并每次合并 5 到 8 层中间观察virsh domstats --block的延迟指标。virsh blockcommit testvm vda --active --pivot --verbose --wait之后我把脚本改成保留最近 3 个快照更早的自动删除。这里有个细节删除内部快照不会自动释放宿主空间得配合一次fstrim或者qemu-img convert重写一遍文件空间才会真正掉下来。7.4 实录三arm64 模拟环境里的诡异超时有次在 x86 机器上用 QEMU 模拟 arm64 跑 CI 构建。单次构建本地跑 20 分钟没问题但放进 CI 之后频繁超时。一开始怀疑是资源不足加了 CPU 和内存改善不明显。后来把-smp拆成明确的拓扑-smp 4,sockets1,cores4,threads1并且关掉了宿主的 CPU 节能策略把电源模式固定成性能模式超时率明显下降。根本原因是 TCG 模拟对宿主的单核性能极其敏感宿主一旦降频模拟出来的客户机时间就直接被拉长客户机里的超时检测很容易被触发。顺带说清一个容易误解的点QEMU 的 TCG 在多核场景下的扩展性并不好加 vCPU 不等于线性提速有时反而因为跨核同步开销导致更慢。跨架构模拟场景下我一般建议 vCPU 数量不超过 4把预算花在提高单核主频上。8. 我踩过之后总结的几个判断口径写到这里前面所有内容其实都能压缩成几个判断口径遇到类似问题时直接套用就行。第一个口径是先分层再选型。看到一个名词先问它在栈的哪一层。如果它属于内核层KVM那就去谈硬件支持属于用户态程序层QEMU那就去谈参数和版本属于格式层RAW/qcow2那就去谈元数据和预分配属于策略层COW/ROW那就去谈写放大和空间回收。分完层很多看似矛盾的说法就自动和解了。第二个口径是格式跟着业务走不要跟着习惯走。需要快照回滚和模板分发就用 qcow2需要极限 IOPS 并且能接受运维复杂度就用 RAW两者不是非此即彼一台宿主机上混用完全正常——模板盘用 qcow2 方便克隆核心数据库盘用 RAW 保性能。第三个口径是空间问题永远先看回收链路。qcow2 涨了不降十次里有八次是discard没配或者客户机没跑fstrim剩下两次是快照层没合。先把这两件事确认掉再去怀疑别的。最后一个我自己用得很顺手的习惯每次给虚拟机做重大变更之前改格式、合快照、调预分配先跑一次qemu-img check把输出存下来。变更之后再跑一次两份输出对比一下元数据有没有被改坏一目了然。这个小动作救过我至少两次比事后从备份里恢复省事得多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →