KVM虚拟化环境里,最常见的存储接入方式是把磁盘做成qcow2镜像文件丢给虚拟机用,这种玩法简单、支持快照,磁盘管理也灵活。但总有那么一些场景绕不开直接挂载物理硬盘分区:宿主机上有一块现成的数据盘,里面是ext4或XFS文件系统,已经堆了好几T数据,你不想再复制一份;或者你要做数据恢复、灾难应急,需要让虚拟机直接读写物理磁盘上的原始区块。这时候如果还用镜像文件去搞,先要创建等大的qcow2,再全量拷贝数据,时间成本高得吓人,而KVM本身就支持把宿主机上的真实块设备、物理分区直接“塞”给虚拟机,让虚拟机像使用自己的虚拟磁盘一样去操作这块物理分区。这篇文章就把KVM虚拟机直接挂载物理硬盘分区这件事讲透,从最基础的XML配置、virsh命令,到权限、SELinux、设备标识这些容易翻车的点,再到LVM、RAID、VFIO等进阶形态,一次性梳理清楚。
1. 先搞清楚一件事:直挂分区到底解决了什么问题
1.1 和qcow2镜像的本质差异
先说个最容易被忽略的底层逻辑。qcow2镜像文件存放在宿主机某个文件系统里,QEMU进程要读写这个文件,走的是“文件系统层”,也就是说虚拟机的IO请求会被翻译成对文件的read/write操作,再由宿主机内核完成真正的磁盘IO。qcow2在这个翻译层之上还加了写时复制、快照、压缩、加密这些能力,功能强,但每一层都会带来开销。
直挂物理硬盘分区就不一样了。XML里type='block'的设备,QEMU直接把分区当作一个原始块设备来打开,io请求通过io=’native‘走Linux AIO绕过页面缓存直达硬件,中间少了一层文件系统翻译,也少了一层qcow2的格式处理。用大白话说,镜像文件是“虚拟机把宿主机的文件当成硬盘”,直挂分区是“虚拟机把宿主机的一块真实硬盘区域直接占用了”。所以直挂分区的IO路径短、延迟低、吞吐高,而且不产生额外空间占用,宿主机看到多大分区,虚拟机就用多大分区。
但代价也很明显,qcow2提供的快照、优雅迁移这些“花活”,在直挂裸设备上基本都玩不了,后续章节我会展开讲。
1.2 哪种业务场景必须走直挂
从我实际运维经验来看,以下四类场景基本绕不开直挂分区:
- 已有物理数据盘直接交给VM使用。宿主机上有磁盘阵列或旧数据盘,里面是已经写满数据的文件系统,直接挂给某台VM,省去拷贝。尤其当数据量以TB计的时候,走镜像拷贝的时间成本不是半小时一小时,可能是整整一个晚上。
- 数据恢复与应急取证。物理机故障后,把系统盘或数据盘接到一台Linux宿主机上,直接挂给一个应急VM,在里面挂载原文件系统、读取日志、导数据。这种场景要求VM能访问原始块设备,走镜像文件反而会因空间不足或格式兼容性出问题。
- 特定硬件场景。比如审计设备、存储控制器测试、NAS系统迁移,宿主机的RAID卡或SAS控制器识别出来的逻辑卷,需要原封不动给虚拟化平台里的某台客户机用。
- 高性能IO业务。数据库、大文件顺序读写服务,性能压测时能明显感觉到直挂裸盘比qcow2稳,延迟和抖动都会小一截。
一句话,直挂分区是给“已经存在的物理数据”和“性能敏感业务”准备的,不是给常规新建VM用的。
2. virtio-blk直挂分区的完整实操(XML和virsh两种写法)
2.1 准备阶段:看清磁盘分区、设备号与文件系统
动手之前,一定要先在宿主机上把目标设备看清楚,这一步不能省。我用的三条命令:
lsblk -f blkid fdisk -l /dev/sdb1lsblk -f可以看到设备树和文件系统类型,blkid能输出分区的UUID和文件系统UUID,fdisk -l则会显示分区表详细信息。确认三件事:
- 分区没有被宿主机挂载,
mount | grep sdb1没有输出才安全; - 设备对应的路径、大小、文件系统类型是否符合预期;
- 宿主机内核里没有被LVM或MD阵列占用的痕迹,避免把被阵列控制的成员盘裸挂给VM导致数据损坏。
还有一个最容易踩的坑:如果分区在宿主机上已经挂载了,再直挂给VM,等于两个系统同时去写同一个文件系统,轻则文件系统损坏,重则整盘数据全没。操作前必须umount干净。
2.2 XML配置:直接指定块设备路径
最常用的方式,通过virsh edit修改虚拟机配置。假设虚拟机叫win10,要把/dev/sdb1挂给它作为第二个磁盘,目标设备是vdb,虚拟总线用virtio。
<disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/sdb1'/> <target dev='vdb' bus='virtio'/> </disk>这段XML里有几个参数不是随便写的,逐一说明:
type='block'是核心,它告诉libvirt这是一个块设备,不是文件。如果这里写file,QEMU会把/dev/sdb1当成普通文件打开,那样直挂就失去了意义。driver type='raw'表示不经过任何格式转换,直接把块设备当成raw格式透传。这一点非常关键,如果写type='qcow2',QEMU会尝试按qcow2格式解析一个raw分区,大概率直接报错。cache='none'屏蔽宿主机的页缓存。虚拟化场景最忌讳双缓存,宿主机缓存一份、客户机再缓存一份,数据一致性很容易出问题,性能也受影响。io='native'让QEMU直接用Linux原生AIO,不走缓冲IO,配合cache='none'是直挂方案的推荐组合。
修改完XML后执行virsh define /etc/libvirt/qemu/win10.xml让配置重新生效,然后启动或重启虚拟机。如果VM正在运行,可以先virsh destroy再virsh start,未保存的数据注意提前处理。
2.3 virsh命令行热插拔
不想停VM,可以用virsh attach-disk热插拔,这个操作在工作中很常用:
virsh attach-disk win10 /dev/sdb1 vdb --driver qemu --subdriver raw --cache none --persistent --live参数含义:
--live表示立即生效;--persistent表示写入持久化配置,VM重启后依然存在;--config表示只写入配置,下次启动生效;--subdriver raw等价于XML里的driver type='raw'。
这里要特别注意:很多教程只写了--live,结果VM一重启分区就没了。因为热插拔默认只在运行期生效,不写--persistent或者--config,不代表这件事被“记住”了。我见过不止一次同事在测试环境热插了一块盘,跑了几个星期,某天VM因重启弄丢了磁盘,数据盘没自动挂载,业务直接中断。所以在生产环境,要么用--live --persistent双参数,要么干脆先热插测试,再写进XML正式落地。
移除分区对应的命令:
virsh detach-disk win10 vdb --persistent2.4 虚拟机内的验证与使用
进入虚拟机系统后,先确认设备是否识别:
fdisk -l lsblk如果设备已经出现(比如为/dev/vdb),就可以正常使用了。假如这个分区原本就是有数据的,直接挂载文件系统:
mount /dev/vdb1 /mnt/data如果是给VM的全新裸分区,先在VM里做文件系统:
mkfs.ext4 /dev/vdb完成之后写入/etc/fstab,实现VM内的开机自动挂载。直挂模式下,VM重启后设备路径通常是固定的(virtio设备名跟总线顺序走),所以fstab里建议用UUID而不是设备名,避免路径漂移。
3. 绕不开的权限和设备标识坑
3.1 让QEMU进程有权限碰你的物理分区
直挂裸设备最容易翻车的地方不在XML,而在权限。默认情况下,libvirt会以qemu:qemu用户运行QEMU进程,而/dev/sdb1这样的块设备通常属于root:disk,权限是brw-rw----,qemu用户根本打不开这个设备节点。典型报错出现在/var/log/libvirt/qemu/win10.log里,提示类似“Permission denied”或“Could not open '/dev/sdb1'”。
解决方案有三种,从推荐到不推荐排列:
- 方式一:调整udev规则,让系统自动授权。在
/etc/udev/rules.d/下新建一个规则文件,把分区的属主改成qemu用户:
KERNEL=="sdb1", OWNER="qemu", GROUP="qemu", MODE="0660"改完后执行udevadm control --reload-rules和udevadm trigger生效。这样即使重启后设备节点重建,也会自动带上正确权限,属于一劳永逸的做法。
方式二:手动chown。临时应急可以
chown qemu:qemu /dev/sdb1,但设备节点一旦被系统重建(重启、拔插盘),属主会恢复原样,只适合临时测试。方式三:修改qemu.conf。把
/etc/libvirt/qemu.conf里的user和group改成root,因为权限问题直接让QEMU以root运行。这样确实省事,但等于把整个虚拟化层的隔离性都放弃了,虚拟机里的一个权限漏洞可能直接威胁宿主机,生产环境我坚决不推荐。
还有一种情况要注意:如果sdb1是一个LVM逻辑卷或RAID设备,它的属主和普通分区不同,需要先ls -l /dev/mapper/xxx或ls -l /dev/md0确认。
3.2 SELinux/AppArmor对块设备访问的干扰
在RHEL/CentOS/Fedora这类启用SELinux的系统上,即使权限对了,QEMU也可能被SELinux策略拦下来。最典型的报错是这个:
internal error: process exited while connecting to monitor: qemu-system-x86_64: -drive file=/dev/sdb1,format=raw,if=none: Could not open '/dev/sdb1': Permission denied这种时候用ausearch -m avc -ts recent看审计日志,会发现AVC denial记录。临时解决办法是给设备节点打上适合QEMU访问的标签:
chcon -t virt_image_t /dev/sdb1不过/dev目录下的设备节点在重启后标签会被重置,这种做法不可持续。从根本上解决,可以写SELinux策略或使用semanage fcontext给设备路径预设上下文。但很多生产环境给KVM宿主机配直挂盘时,干脆会将libvirt相关domain的SELinux宽松处理,具体要看公司的安全规范。
Ubuntu/Debian系的AppArmor也有类似问题。检查/etc/apparmor.d/下是否有影响libvirt的profile,必要时在/etc/apparmor.d/local/usr.lib.libvirt.virt-aa-helper里添加对应设备路径的规则,然后systemctl reload apparmor。
3.3 设备路径漂移与重启失效问题
/dev/sdb1这种命名方式是内核按设备发现顺序分配的,重启、换插槽、换HBA卡之后,sdb完全可能变成sdc或sde。配置文件里如果写死了source dev='/dev/sdb1',一旦路径变了,VM启动时会直接找不到盘。
解决思路有两个:
- 用by-id或by-uuid路径。查询方式:
ls -l /dev/disk/by-id/ ls -l /dev/disk/by-uuid/然后在XML里改写成:
<source dev='/dev/disk/by-uuid/xxxx-xxxx-xxxx'/>或者:
<source dev='/dev/disk/by-id/scsi-SATA_WDC_WD4000F9YZ-XXXX'/>这样只要设备本身还在,路径怎么漂移都能找到。如果是LVM卷,建议直接写/dev/mapper/xxx,因为这个路径在LVM激活后是稳定的。
- 做静态软链接。在宿主机上创建一个固定目录,比如
/dev/vmdisks/,把目标分区链接过去:
mkdir /dev/vmdisks ln -s /dev/sdb1 /dev/vmdisks/win10-data1然后在XML里写<source dev='/dev/vmdisks/win10-data1'/>。配合udev规则可以保证链接稳定,不过维护成本略高,一般用by-id就够。
说到“重启失效”,很多用户搜索“mount挂载新硬盘重启没了”其实是同一个问题的两种表现。一种是上面说的VM配置没持久化,另一种是VM内部的/etc/fstab设置不对。我的统一建议是:宿主机侧确认XML配置持久化,VM内部用UUID写fstab,双保险缺一不可。
4. 热插拔、快照与迁移边界行为
4.1 attach-disk的live/config/persistent三种状态
前面提到了--live和--persistent,这里把libvirt对磁盘状态的完整语义说清楚。一个磁盘配置可能处在三种状态之一:
- active:VM运行期间已经在使用的设备。
--live操作会改变这个状态,但不影响持久化配置。 - persistent:写入VM的永久XML定义。
--config或--persistent操作会改变它,重启VM后保留。 - 如果
--live和--persistent都没有指定,命令默认只对当前运行域生效,重启即失。
查看磁盘在两种状态下的差异,可以用:
virsh dumpxml win10 --live virsh dumpxml win10 --inactive--live输出的是当前运行的配置,--inactive输出的是磁盘的重启后配置。如果两者不一致,说明存在“临时生效”的改动,这个细节排查问题的时候非常有用。
4.2 直挂分区能打快照吗
直挂分区的VM能不能打快照?答案是:基本不能,而且原因不在KVM配置,而在块设备的格式限制。
- 内部快照(internal snapshot)需要磁盘格式支持保存快照数据,qcow2可以,raw块设备本身没有快照功能,所以不行。
- 外部快照(external snapshot)虽然理论上可以给raw设备加一个qcow2 overlay,但它要求原磁盘必须保持只读,QEMU才能将新写入导到overlay中。对一个要持续读写的物理分区来说,这个假设也不成立。
如果你确认自己的业务需要快照,建议不要用直挂裸分区方案,改用LVM逻辑卷。因为LVM的lvcreate --snapshot是在宿主机层做块级快照,不依赖QEMU能力:
lvcreate -L 20G -s -n><disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/vg_data/lv_vmdata'/> <target dev='vdb' bus='virtio'/> </disk>需要提醒的是,LVM逻辑卷扩容时,宿主机和虚拟机都在访问这块设备,扩容操作不会影响数据安全,但同一个卷如果同时挂给两台VM(共享读写模式),就会造成严重的文件系统损坏风险,这点必须牢记。
5.2 MD RAID阵列设备直挂的注意事项
宿主机上用mdadm做的软RAID,形成的/dev/md0也可以直挂给VM。写法和普通分区没有区别:
<source dev='/dev/md0'/>但有两个特殊之处需要关注:
- 软RAID设备在系统启动时由mdadm组装,如果RAID成员盘启动顺序变化或其中一块盘掉线,
/dev/md0可能无法在VM启动前出现。建议在/etc/mdadm/mdadm.conf里写死阵列的UUID,并检查初始化脚本能正确组装所有阵列。 - 不要轻易把MD设备同时给宿主机自己使用和虚拟机使用。比如宿主机已经挂载了这个RAID上的文件系统,又直挂给VM,就会出现上节说的双重写风险。
5.3 直接挂载和VFIO直通的取舍
比virtio-blk直挂分区更激进的方案是VFIO PCI直通。它是把宿主机的PCIe设备(HBA卡、网卡、GPU、NVMe控制器)直接分配给虚拟机,虚拟机设备驱动直接操作硬件。和virtio-blk直挂分区相比:
| 维度 | virtio-blk 直挂物理分区 | VFIO PCI 直通 |
|---|---|---|
| 性能 | 靠近物理机,仍有虚拟化层 | 等同于物理机,无虚拟化损耗 |
| 配置复杂度 | 简单,改XML即可 | 需要开启IOMMU、按PCI地址绑定vfio-pci |
| 灵活性 | 分区/磁盘/卷都可挂 | 整个PCI设备独占,不能拆分 |
| 隔离性 | QEMU通过块设备接口访问 | 客户机直接操作物理硬件 |
| 适用场景 | 数据盘、文件系统、LVM卷 | NVMe盘、GPU、网卡、专用加速卡 |
如果是NVMe固态硬盘,追求极致性能又不怕分区粒度粗,可以用VFIO直通整块NVMe控制器;如果只是想给VM挂一块现成的数据分区,virtio-blk直挂是最省事性价比最高的选择。
5.4 四种存储接入方式的选型建议
根据实际业务规模,我给你一个可以直接照抄的选型思路:
- 日常VM、模板机、测试环境:一律qcow2镜像,优先考虑快照、克隆、后端存储迁移能力。
- 性能敏感但数据可以重建:raw格式镜像文件,牺牲快照换取一点性能。
- 已有数据盘、数据恢复、大容量存储:virtio-blk直挂物理分区,优先保证数据可直接访问和IO路径短。
- 数据库等超高性能需求 + 独立物理磁盘:VFIO整卡直通,彻底不要虚拟化IO栈。
命令行速查表,建议收藏:
# 查看VM当前磁盘配置 virsh dumpxml <vm> | grep -A 5 "<disk" # 热插拔挂载(持久+立即) virsh attach-disk <vm> /dev/disk/by-uuid/xxx vdb --driver qemu --subdriver raw --cache none --live --persistent # 热插拔卸载(持久+立即) virsh detach-disk <vm> vdb --live --persistent # 查看设备在VM内的IO统计 virsh domblkstat <vm> vdb # 查看VM实时磁盘信息 virsh domblkinfo <vm> vdb最后再分享一个小技巧。直挂分区前,我习惯先在宿主机上对目标分区做一次只读测试,用dd if=/dev/sdb1 of=/dev/null bs=1M count=1024检查设备能否被顺畅读取。如果这条命令都报IO错误,那么直挂给VM后大概率也会出问题,趁早排查硬件远比在虚拟化层面反复折腾来得实在。直挂物理分区这个功能,KVM做了很多年,技术上非常成熟,真正出问题的地方往往都在权限、路径、持久化这些“应该已经搞定”的细节上,操作前多花两分钟确认,操作中能省出两小时。