SR-IOV(Single Root Input/Output Virtualization) 是一项硬件虚拟化技术,它的目的是将一个 PCIe 设备(例如网卡),虚拟成多个互相隔离的设备,提供给不同的使用者(例如虚拟机)使用。使用 SR-IOV 的设备包含如下组件:
- 一个 PF(Physical Function),PF 具备 PCIe 设备的所有功能,可以将 PF 等同于一个普通 PCIe 设备一样来进行管理和配置。
- 多个 VF(Virtual Function),VF 类似于 PF,它具有设备所有的 IO 相关的功能。但是 VF 只能操作 PCIe 设备上的数据,并不能对设备进行配置管理。
PF 和 VFs 都有各自独立的 PCIe RID,允许 I/O 内存管理单元 (IOMMU) 来区分不同的数据流,应用内存和中断及 PF 与 VFs 之间的转换。
SR-IOV 将设备虚拟成多个 VFs 之后,每个 VF 可以直接 pass through 给虚拟机使用,虚拟机以独占的方式直接操作 VF,就像直接操作物理设备一样。一般来说,使用这种方式需要启用 Intel 虚拟化技术 VT-d
可以看到 SRIOV 的方式经过的软件栈比较少,因此一般效率会更高。
参考:
- virtualization_deployment_and_administration_guide
- configure-sr-iov-network-virtual-functions-in-linux-kvm
1. 打开 CPU VT-d 支持
首先,需要在 BIOS 中打开 CPU VT-d 的支持。然后需要给 kernel 添加如下内核参数: 编辑 /etc/default/grub,添加启动参数:
intel_iommu=on
重新生成 grub 配置:
$ grub2-mkconfig -o /boot/grub2/grub.cfg
重启操作系统让参数生效。
2. 查看网卡是否支持 SR-IOV
$ lspci -v | grep -A30 'Ethernet controller'
3d:00.0 Ethernet controller: Intel Corporation Ethernet Connection X722 for 10GbE SFP+ (rev 09)
Subsystem: Intel Corporation Device 0000
Flags: bus master, fast devsel, latency 0, IRQ 47, NUMA node 0
Memory at b6000000 (64-bit, prefetchable) [size=16M]
Memory at b7808000 (64-bit, prefetchable) [size=32K]
Expansion ROM at b8580000 [disabled] [size=512K]
Capabilities: [40] Power Management version 3
Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
Capabilities: [70] MSI-X: Enable+ Count=129 Masked-
Capabilities: [a0] Express Endpoint, MSI 00
Capabilities: [e0] Vital Product Data
Capabilities: [100] Advanced Error Reporting
Capabilities: [140] Device Serial Number ec-a7-44-ff-ff-a5-d7-04
Capabilities: [150] Alternative Routing-ID Interpretation (ARI)
Capabilities: [160] Single Root I/O Virtualization (SR-IOV) # 支持 SR-IOV
Capabilities: [1a0] Transaction Processing Hints
Capabilities: [1b0] Access Control Services
Kernel driver in use: i40e
Kernel modules: i40e
支持 SR-IOV 的 Intel 网卡:intel-ethernet-products
3. 创建 VFs
使用如下命令创建 VFs
$ echo ${num_vfs} > /sys/class/net/enp14s0f0/device/sriov_numvfs
其中 num_vfs 为需要创建的 VF 个数,可以查看 /sys/class/net/enp61s0f1/device/sriov_totalvfs 查询支持的最大 VFs 个数。
4. 保存创建 VFs 的配置
保存配置以使下次重启 VF 仍然存在。
$ vim /etc/udev/rules.d/enp14s0f0.rules
ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="ixgbe", ATTR{device/sriov_numvfs}="2"
ENV{ID_NET_DRIVER}
表示使用的 driver,上面的配置会创建 2 个 VF
查看网卡 driver 的方式:
$ find /sys | grep drivers.*3d:00
其中3d:00
为设备的 PCI bus/slot
5. 查看新建的 VFs
可以使用 lspci | grep Eth
或者 ip link
查看网卡的 VF
lspci 可以查看到 VF 的 PCI 设备号
6. 使用 virsh 查询 VF
使用 virsh nodedev-list
查看 VF
$ virsh nodedev-list | grep 0b
pci_0000_0b_00_0
pci_0000_0b_00_1
pci_0000_0b_10_0
pci_0000_0b_10_1
pci_0000_0b_10_2
pci_0000_0b_10_3
pci_0000_0b_10_4
pci_0000_0b_10_5
pci_0000_0b_10_6
pci_0000_0b_11_7
pci_0000_0b_11_1
pci_0000_0b_11_2
pci_0000_0b_11_3
pci_0000_0b_11_4
pci_0000_0b_11_5
virsh
命令会把 PCI 设备号中的 : 替换为 _ ,使用 virsh nodedev-dumpxml
查看设备的详细信息:
$ virsh nodedev-dumpxml pci_0000_03_11_5
<device>
<name>pci_0000_03_11_5</name>
<path>/sys/devices/pci0000:00/0000:00:01.0/0000:03:11.5</path>
<parent>pci_0000_00_01_0</parent>
<driver>
<name>igbvf</name>
</driver>
<capability type='pci'>
<domain>0</domain>
<bus>3</bus>
<slot>17</slot>
<function>5</function>
<product id='0x10ca'>82576 Virtual Function</product>
<vendor id='0x8086'>Intel Corporation</vendor>
<capability type='phys_function'>
<address domain='0x0000' bus='0x03' slot='0x00' function='0x1'/>
</capability>
<iommuGroup number='35'>
<address domain='0x0000' bus='0x03' slot='0x11' function='0x5'/>
</iommuGroup>
</capability>
</device>
当添加 VF 到虚拟机时,需用使用上面的 bus, slot 和 function 参数的值。
7. 虚拟机使用 VF
虚拟机使用 VF 的 xml 配置如下:
<interface type='hostdev' managed='yes'>
<source>
<address type='pci' domain='0x0000' bus='0x03' slot='0x10' function='0x2'/>
</source>
<mac address='52:54:00:6d:90:02'/>
</interface>
宿主机和 VM 同时使用 Bond 的问题参考:discussion-questions-sr-iov-virtualization-and-bonding
配置参考:bonding_and_sr_iov
1.arp_monitor
在虚拟机配置 bond 网卡时,不可以使用 arp_monitor
,只能使用mii monitor
2. fail_over_mac 和 garp
虚拟机内使用 bond active-backup 模式时,必须打开 fail_over_mac 参数(Ref: https://support.hpe.com/hpsc/doc/public/display?docId=emr_na-c02695249)
BONDING_OPTS="mode=active-backup fail_over_mac=active miimon=100"
Bond 使用 active-standby 模式时,默认情况 bond 网卡的地址会保持不变,当 failover 时会切换 active slave 网卡的 MAC 地址。但是当 VF 网卡分配给虚拟机使用之后,虚拟机就不能再更改 VF 的 MAC 地址了。因此虚拟机内部 bond 就无法再操作 slave 网卡的地址了。因此需要设置 fail_over_mac=active
,让 salve 网卡的 MAC 地址保持不变,bond 在 failover 时通过更改 bond 网卡自身的 MAC 地址完成切换。
garp
设置了 fail_over_mac=active
后,bond 网卡在发生 failover 后,会进行:
- 选择新的 active slave 网卡
- 将 bond 网卡的 MAC 地址更改为 active slave 网卡的 MAC 地址
- 发送 garp 通知网络内其他机器更新 arp 缓存 如果 grap 广播包丢失或未送达,则可能造成 bond 网地址访问中断。 PS: 可以对 bond 网卡增加 num_grat_arp 配置来配置发送 garp 包的次数,默认为 1,可以配置为 1~255 的值。
可以在其他机器上抓包查看切换和 grap 报文:
1. 在同网络内其他机器 ping 虚拟机地址,查看 ARP 缓存:
$ arp -an
? (10.0.21.200) at 52:54:00:4d:2a:81 [ether] on bond1
抓包查看 arp 报文:
$ tcpdump -nnei bond1 arp
2. 在虚拟机内切换 bond 的 active slave 网卡,通过命令完成(也可以直接拔掉 active slave 的网线):
$ ifenslave -c bond0 ens4
[ 338.690203] bond0: Setting ens4 as active slave
[ 338.691841] bond0: making interface ens4 the new active one
3. 查看抓包结果
$ tcpdump -nnei bond1 arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on bond1, link-type EN10MB (Ethernet), capture size 262144 bytes
...
17:03:25.007671 52:54:00:4d:2a:80 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 60: Request who-has 10.0.21.200 tell 10.0.21.200, length 46 # garp 广播
RHEL7 对网卡命令使用 Consistent Network Device Naming,对于 SR-IOV 设备,在虚拟机内部即为一个 PCI-E 设备,因此网卡的命名会使用 PCI Slot ID 来命名。
为了让命名保持一致,我们可以对虚拟机 XML 配置中增加设备地址的配置,让网卡的 PCI Slot 号始终保持不变,例如:
<devices>
<emulator>/usr/libexec/qemu-kvm</emulator>
<channel type="unix">
<target name="org.qemu.guest_agent.0" type="virtio"></target>
</channel>
<interface type='hostdev' managed='yes'>
<source>
<address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x0'/>
</source>
<!-- 这里使用 bus id = 1, slot id =1,使用 bus id = 1 是为了和系统默认的 bus 0 上的设备 slot id 产生冲突 -->
<address type='pci' domain='0x0000' bus='0x01' slot='0x01' function='0x0'/>
</interface>
<interface type='hostdev' managed='yes'>
<source>
<address type='pci' domain='0x0000' bus='0x3d' slot='0x06' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x01' slot='0x02' function='0x0'/>
</interface>
</devices>
使用上面的配置,就可以使两个 SR-IOV 设备的网卡名在虚拟机中始终为 ens0 和 ens1