SR-IOV 虚拟化

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


SR-IOV 虚拟化 - 图1 可以看到 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,添加启动参数:

  1. intel_iommu=on

重新生成 grub 配置:

  1. $ grub2-mkconfig -o /boot/grub2/grub.cfg

重启操作系统让参数生效。

2. 查看网卡是否支持 SR-IOV

  1. $ lspci -v | grep -A30 'Ethernet controller'
  2. 3d:00.0 Ethernet controller: Intel Corporation Ethernet Connection X722 for 10GbE SFP+ (rev 09)
  3. Subsystem: Intel Corporation Device 0000
  4. Flags: bus master, fast devsel, latency 0, IRQ 47, NUMA node 0
  5. Memory at b6000000 (64-bit, prefetchable) [size=16M]
  6. Memory at b7808000 (64-bit, prefetchable) [size=32K]
  7. Expansion ROM at b8580000 [disabled] [size=512K]
  8. Capabilities: [40] Power Management version 3
  9. Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
  10. Capabilities: [70] MSI-X: Enable+ Count=129 Masked-
  11. Capabilities: [a0] Express Endpoint, MSI 00
  12. Capabilities: [e0] Vital Product Data
  13. Capabilities: [100] Advanced Error Reporting
  14. Capabilities: [140] Device Serial Number ec-a7-44-ff-ff-a5-d7-04
  15. Capabilities: [150] Alternative Routing-ID Interpretation (ARI)
  16. Capabilities: [160] Single Root I/O Virtualization (SR-IOV) # 支持 SR-IOV
  17. Capabilities: [1a0] Transaction Processing Hints
  18. Capabilities: [1b0] Access Control Services
  19. Kernel driver in use: i40e
  20. Kernel modules: i40e

支持 SR-IOV 的 Intel 网卡:intel-ethernet-products

3. 创建 VFs

使用如下命令创建 VFs

  1. $ echo ${num_vfs} > /sys/class/net/enp14s0f0/device/sriov_numvfs

其中 num_vfs 为需要创建的 VF 个数,可以查看 /sys/class/net/enp61s0f1/device/sriov_totalvfs 查询支持的最大 VFs 个数。

4. 保存创建 VFs 的配置

保存配置以使下次重启 VF 仍然存在。

  1. $ vim /etc/udev/rules.d/enp14s0f0.rules
  2. ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="ixgbe", ATTR{device/sriov_numvfs}="2"

ENV{ID_NET_DRIVER} 表示使用的 driver,上面的配置会创建 2 个 VF

查看网卡 driver 的方式:

  1. $ 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

  1. $ virsh nodedev-list | grep 0b
  2. pci_0000_0b_00_0
  3. pci_0000_0b_00_1
  4. pci_0000_0b_10_0
  5. pci_0000_0b_10_1
  6. pci_0000_0b_10_2
  7. pci_0000_0b_10_3
  8. pci_0000_0b_10_4
  9. pci_0000_0b_10_5
  10. pci_0000_0b_10_6
  11. pci_0000_0b_11_7
  12. pci_0000_0b_11_1
  13. pci_0000_0b_11_2
  14. pci_0000_0b_11_3
  15. pci_0000_0b_11_4
  16. pci_0000_0b_11_5

virsh 命令会把 PCI 设备号中的 : 替换为 _ ,使用 virsh nodedev-dumpxml 查看设备的详细信息:

  1. $ virsh nodedev-dumpxml pci_0000_03_11_5
  2. <device>
  3. <name>pci_0000_03_11_5</name>
  4. <path>/sys/devices/pci0000:00/0000:00:01.0/0000:03:11.5</path>
  5. <parent>pci_0000_00_01_0</parent>
  6. <driver>
  7. <name>igbvf</name>
  8. </driver>
  9. <capability type='pci'>
  10. <domain>0</domain>
  11. <bus>3</bus>
  12. <slot>17</slot>
  13. <function>5</function>
  14. <product id='0x10ca'>82576 Virtual Function</product>
  15. <vendor id='0x8086'>Intel Corporation</vendor>
  16. <capability type='phys_function'>
  17. <address domain='0x0000' bus='0x03' slot='0x00' function='0x1'/>
  18. </capability>
  19. <iommuGroup number='35'>
  20. <address domain='0x0000' bus='0x03' slot='0x11' function='0x5'/>
  21. </iommuGroup>
  22. </capability>
  23. </device>

当添加 VF 到虚拟机时,需用使用上面的 bus, slot 和 function 参数的值。

7. 虚拟机使用 VF

虚拟机使用 VF 的 xml 配置如下:

  1. <interface type='hostdev' managed='yes'>
  2. <source>
  3. <address type='pci' domain='0x0000' bus='0x03' slot='0x10' function='0x2'/>
  4. </source>
  5. <mac address='52:54:00:6d:90:02'/>
  6. </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)

  1. 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 后,会进行:

  1. 选择新的 active slave 网卡
  2. 将 bond 网卡的 MAC 地址更改为 active slave 网卡的 MAC 地址
  3. 发送 garp 通知网络内其他机器更新 arp 缓存 如果 grap 广播包丢失或未送达,则可能造成 bond 网地址访问中断。 PS: 可以对 bond 网卡增加 num_grat_arp 配置来配置发送 garp 包的次数,默认为 1,可以配置为 1~255 的值。

可以在其他机器上抓包查看切换和 grap 报文:

1. 在同网络内其他机器 ping 虚拟机地址,查看 ARP 缓存:

  1. $ arp -an
  2. ? (10.0.21.200) at 52:54:00:4d:2a:81 [ether] on bond1

抓包查看 arp 报文:

  1. $ tcpdump -nnei bond1 arp

2. 在虚拟机内切换 bond 的 active slave 网卡,通过命令完成(也可以直接拔掉 active slave 的网线):

  1. $ ifenslave -c bond0 ens4
  2. [ 338.690203] bond0: Setting ens4 as active slave
  3. [ 338.691841] bond0: making interface ens4 the new active one

3. 查看抓包结果

  1. $ tcpdump -nnei bond1 arp
  2. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  3. listening on bond1, link-type EN10MB (Ethernet), capture size 262144 bytes
  4. ...
  5. 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 号始终保持不变,例如:

  1. <devices>
  2. <emulator>/usr/libexec/qemu-kvm</emulator>
  3. <channel type="unix">
  4. <target name="org.qemu.guest_agent.0" type="virtio"></target>
  5. </channel>
  6. <interface type='hostdev' managed='yes'>
  7. <source>
  8. <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x0'/>
  9. </source>
  10. <!-- 这里使用 bus id = 1, slot id =1,使用 bus id = 1 是为了和系统默认的 bus 0 上的设备 slot id 产生冲突 -->
  11. <address type='pci' domain='0x0000' bus='0x01' slot='0x01' function='0x0'/>
  12. </interface>
  13. <interface type='hostdev' managed='yes'>
  14. <source>
  15. <address type='pci' domain='0x0000' bus='0x3d' slot='0x06' function='0x0'/>
  16. </source>
  17. <address type='pci' domain='0x0000' bus='0x01' slot='0x02' function='0x0'/>
  18. </interface>
  19. </devices>

使用上面的配置,就可以使两个 SR-IOV 设备的网卡名在虚拟机中始终为 ens0 和 ens1