SR-IOV 虚拟化

Posted by Liao on May 16, 2019

概念

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

虚拟机使用网卡的几种方式

vm-network

可以看到 SRIOV 的方式经过的软件栈比较少,因此一般效率会更高。

配置 SR-IOV

参考:

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>

Bond 与 SR-IOV

宿主机和 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 后,会进行:

  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 缓存:
$ 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