Basics
SR-IOV的全称是Single Root I/O Virtualization。
虚拟机中的网卡看起来是真实的硬件,实际则是宿主机虚拟化出来的设备,也就是运行的软件程序;这也意味着,虚拟设备是需要CPU去运行的,这样设备的性能会随着宿主机性能而改变,可能会产生额外的延时。
VT-D技术可以将物理机的PCIe设备直接分配给虚拟机,让虚拟机直接控制硬件,来避免上述问题。但是虚拟机会独占直通的PCIe设备,如果一台宿主机上有多个虚拟机,那就要求对应数量的物理网卡,这显然不现实。
为解决这样的问题,Intel提出来SR-IOV技术,该技术最初应用在网卡上。简单来讲,就是将一个物理网卡虚拟出多个轻量化的PCIe物理设备,再分配给虚拟机使用。
启用SR-IOV技术,可以大大减轻宿主机的CPU负荷,提高网络性能、降低网络延时,也避免了PCI-passthrough下各个物理网卡被各个容器独占的问题。
根据Oracle Solaris的文档描述,SR-IOV规范定义了新的标准,创建的新设备能将虚拟机直接连接到I/O设备,并获得于本机性能媲美的I/O性能。具体规范由PCI-SIG定义和维护。
Functions
SR-IOV包含2种新功能类型:
- 物理功能(Physical Function, PF):用于支持SR-IOV功能的PCI功能。
- 虚拟功能(Virtual Function, VF):与物理功能关联的一种功能。
更具体来说,PF是包含完全的PCIe功能,可以像其他任何PCIe设备一样进行发现、管理和处理;PF拥有完全配置资源,可用于配置或控制PCIe设备。
VF是一种轻量级的PCIe功能,可以与物理功能及同一物理功能关联的其他VF共享一个或多个物理资源,且仅允许拥有用于其自身行为的配置资源。
每个SR-IOV设备都可以有一个PF,每个PF最多可以关联64,000个VF。PF可以通过专用寄存器创建VF。
一旦在PF中启用了SR-IOV,就可以通过PF的总线、设备和路由ID访问各个VF的PCI配置空间。每个VF具有一个映射了其寄存器集的PCI内存空间,VF设备驱动程序对寄存器集进行操作以启用其功能,并显示为实际存在的PCI设备。
创建VF后,可以直接将其指定给IO guest域的各个应用程序。
此功能试的虚拟功能可以在没有CPU和VMM开销的情况下执行I/O。
缺省状态下,SR-IOV功能处于禁用状态,PF充当传统PCIe设备。
SR-IOV技术也有其局限性,比如同一PF可关联的VF数量是有限的,而且是硬件绑定的,不支持容器迁移。
Docker with SR-IOV
SR-IOV在虚拟化中应用广泛,理所当然地可以被用在容器上。
Intel官方就维护了一个SR-IOV的CNI插件,Intel/sriov-cni。除此之外,其开发的clearcontainers项目下也有一个SR-IOV的network plugin,支持docker指定容器运行时为runc或clearcontainer时使用,见clearcontainers/sriov。
更多有关Docker Plugin的信息,请参考Docker Engine managed plugin system。
下面我们采用clearcontainers提供的SR-IOV Docker Plugin进行安装。
Install Clear Containers SRIOV Docker Plugin
环境要求:
- 物理机(VM中可能找不到支持SR-IOV的PF)
- Golang
- Docker Engine
克隆源码。
git clone https://github.com/clearcontainers/sriov.git cd sriov
用Go Mod或者Dep等工具初始化go pkg依赖,配置docker/libnetwork项目到较新版本(旧版本会导致编译失败)。
$ go mod init $ go mod vendor $ cat go.mod module github.com/clearcontainers/sriov go 1.13 require ( github.com/01org/ciao v0.0.0-20180108144400-194264adc583 github.com/boltdb/bolt v1.3.1 github.com/docker/libnetwork v0.7.2-rc.1 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/gorilla/mux v1.7.3 github.com/vishvananda/netlink v1.1.0 )
编译源码。
go build
在host上安装SR-IOV插件,并在后台启动插件。
sudo cp sriov.json /etc/docker/plugins sudo ./sriov &
以Docker默认的runc容器运行时来测试插件功能。
# Create a virtual network on physical network b2b with vlanid 100 sudo docker network create -d sriov --internal --opt pf_iface=eth0 --opt vlanid vfnet # Create container on the network vfnet docker run --net=vfnet -itd busybox top # Check that container is running docker ps # Cleanup docker stop $(docker ps -a -q) docker rm $(docker ps -a -q) sudo docker network rm vfnet
Kata with SR-IOV
Kata是一个使用轻量虚拟机(VM)来替代传统的容器运行时(runc等),以达到容器间或Pod间的内核隔离的安全容器方案。其技术上的设计细节可以参考Architecture - kata-containers/documentation。
由于Kata默认采用QEMU作为hypervisor,而QEMU不支持veth,所以网络上的默认方案是采用TAP来为VM内外打通网络。前面我们了解到SR-IOV是不需要采用传统的veth的,在CPU、内核功能上支持SR-IOV的前提下,就可以采用SR-IOV来提升Kata容器的网络性能。
Setup to Use SR-IOV with Kata
遵循Setup to use SR-IOV with Kata Containers and Docker一文,首先准备好clearcontainer的SR-IOV Docker Plugin。
Requirement
- Host支持Intel VT-d
- NIC支持SR-IOV
- Host的内核支持IOMMU和VFIO
更新以下配置前,请最好确保你已经有当前host的足够新的备份。
检查你的NIC是否支持SR-IOV:
# get the pci class code of your NIC $ lspci | fgrep -i ethernet 01:00.0 Ethernet controller: Intel Corporation Ethernet Controller 10-Gigabit X540-AT2 (rev 03) ... #sudo required below to read the card capabilities $ sudo lspci -s 01:00.0 -v | grep SR-IOV Capabilities: [160] Single Root I/O Virtualization (SR-IOV)
检查host上的IOMMU groups设置
find /sys/kernel/iommu_groups/ -type l
如果没有输出,就说明你需要按下面步骤更新内核的配置。
Update Host Kernel Configuration
开启intel-iommu,手动编辑/etc/default/grub
或者下面的sed
命令都应该可以;编辑好配置文件后,使用update-grub
更新grub配置并重启:
# edit /etc/default/grub and add intel_iommu=on to cmdline: $ sudo sed -i -e 's/^kernel_params = "\(.*\)"/GRUB_CMDLINE_LINUX="\1 intel_iommu=on"/g' /etc/default/grub $ sudo update-grub $ sudo reboot -f
重启后,检查启动命令行(也就是上面更改的CMDLINE_LINUX
)是否更新成功:
$ cat /proc/cmdline BOOT_IMAGE=/boot/vmlinuz-4... root=UUID=4b... ... intel_iommu=on
使用dmesg
命令查看系统启动信息,以检查Intel VT-d是否已初始化:
$ dmesg | grep --color "Virtualization" # output can be DMAR: Intel(R) Virtualization Technology for Directed I/O # or PCI-DMA: Intel(R) Virtualization Technology for Directed I/O
加入vfio-pci
模块:
sudo modprobe vfio-pci
再次检查iommu group,确保你的NIC支持PCIE-ACS,即它的class id出现在以下find指令输出中,并且其对应的group id不包括其他设备:
$ find /sys/kernel/iommu_groups/ -type l | grep "01:00" /sys/kernel/iommu_groups/35/devices/0000:01:00.1 /sys/kernel/iommu_groups/34/devices/0000:01:00.0 $ find /sys/kernel/iommu_groups/ -type l | grep "35" /sys/kernel/iommu_groups/35/devices/0000:01:00.1
Set Up the SR-IOV Device
Remark: 前面小节的操作都是为了让host支持SR-IOV做的,只需要设置一次就行。下面的操作需要在每次系统启动后执行,以促进物理设备的VFs的设置。
加入vfio-pci
设备驱动(上一节加过的可以不再做),该驱动将用于VF的保留:
sudo modprobe vfio-pci
查看你的NICs可以创建多少个VFs:
# use the pci class id of your NICs $ cat /sys/bus/pci/devices/0000\:01\:00.0/sriov_totalvfs 63 $ cat /sys/bus/pci/devices/0000\:01\:00.1/sriov_totalvfs 63
修改sriov_numvfs
的值来创建VFs;注意每个NIC都会对应这样一个文件,每个sriov_numvfs
的值默认为0,修改为2或者更多,恰好对应每个PF创建的VF,最多不超过上面的totalvfs值:
vim /sys/bus/pci/devices/0000\:01\:00.0/sriov_numvfs
这里我们修改第一张NIC的numvfs为2,确认VF信息:
$ sudo lspci | grep Ethernet | grep Virtual 02:10.0 Ethernet controller: Intel Corporation X540 Ethernet Controller Virtual Function (rev 01) 02:10.1 Ethernet controller: Intel Corporation X540 Ethernet Controller Virtual Function (rev 01) $ ls /sys/class/net/eth0/device/virtfn0/net enp59s0f2 $ ls /sys/class/net/eth0/device/virtfn1/net enp59s0f3 # the path for other NICs' VF can be "/sys/class/net/eth2/device/virtfn1/net"
目前的VF是没有MAC地址的,为它们设置MAC地址来保证host和VM中的地址一致性,下面以第一个VF为例:
$ ip link show dev eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1550 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether b8:59:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff vf 0 MAC 00:00:00:00:00:00, spoof checking off, link-state auto, trust off, query_rss off vf 1 MAC 00:00:00:00:00:00, spoof checking off, link-state auto, trust off, query_rss off $ ip link show dev enp59s0f2 13: enp59s0f2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 4a:b3:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff $ ip link set eth0 vf 0 mac 4a:b3:xx:xx:xx:xx # also set MAC for other VFs $ ip link show dev eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1550 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether b8:59:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff vf 0 MAC 4a:b3:xx:xx:xx:xx, spoof checking off, link-state auto, trust off, query_rss off vf 1 MAC ee:20:xx:xx:xx:xx, spoof checking off, link-state auto, trust off, query_rss off
Launch a Kata Container using SR-IOV
首先,按照Install Clear Containers SRIOV Docker Plugin中的步骤,启用sriov的Docker Plugin。
然后,创建docker network:
$ docker network create -d sriov --internal --opt pf_iface=eth0 --opt vlanid=100 --subnet=192.168.0.0/24 vfnet E0212 15:07:55.427057 1187089 plugin.go:298] Numvfs and Totalvfs are not same on the PF - Initialize numvfs to totalvfs ee2e5a594f9e4d3796eda972f3b46e52342aea04cbae8e5eac9b2dd6ff37b067
使用新建的network来创建kata容器:
$ docker run --net=vfnet --runtime=kata-runtime --cap-add SYS_ADMIN --ip=192.168.0.10 -it busybox ls / bin dev etc home proc root sys tmp usr var
清除动作与使用runc的方式类似。
参考资料
- SR-IOV是什么?性能能好到什么程度?. 钱乎. 知乎专栏
- SR-IOV简介 - 编写驱动程序. Oracle
- Setup to use SR-IOV with Kata Containers and Docker. kata-containers. GitHub