news 2026/9/30 1:18:13

KVM虚拟机直挂物理硬盘分区:从virtio-blk配置到权限与迁移实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KVM虚拟机直挂物理硬盘分区:从virtio-blk配置到权限与迁移实战

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/sdb1

lsblk -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 --persistent

2.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做了很多年,技术上非常成熟,真正出问题的地方往往都在权限、路径、持久化这些“应该已经搞定”的细节上,操作前多花两分钟确认,操作中能省出两小时。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 1:18:00

Jupyter Notebook安装指南:Python环境、conda配置与常见问题排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:17:38

C语言内存四区详解:栈、堆、全局区、代码区原理与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:17:38

企业AI大模型数字底座设计方案与落地避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:17:04

从原生到 Promise:手写一个实用的 Ajax 封装指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:17:03

嵌入式Linux ASoC音频驱动:Codec驱动与音频控件核心实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华