编写Udev规则文件开机设置网卡SR-IOV

在虚拟化场景下,SR-IOVSingle Root I/O Virtualization)是一个很常用的功能,通过SR-IOV,一个物理的设备(Physical Function),可以派生出很多虚拟设备(Virtual Function),这些虚拟设备具有简单的PCIe功能。以网卡为例,通过SR-IOV,我们可以将一块网卡,虚拟化成很多块网卡,这些虚拟出来的网卡,有自己独立的PCIe地址,中断,配置空间等,这些虚拟出来的网卡,可以作为单独的PCIe设备被attach到虚拟机中,实现网络功能,当然,场景并不局限于VM。

这里先不关注SR-IOV的应用场景,或者其实现原理,而是关心一个简单的问题:如何设置SR-IOV,并且能稳定的实现开机启动时就设置好呢?

这里以网卡为例,假设要开启SR-IOV的网卡名字为eth0,文档里会提示你echo 7 > /sys/class/net/eth0/device/sriov_numvfs。在系统启动完成之后,这么做肯定是没有问题。问题是,在系统启动阶段,这么做是不是可以呢?红帽的文档第 8 章 配置 SR-IOV 网络通过rc.local来实现:

# chmod +x /etc/rc.d/rc.local
# echo "echo 7 > /sys/class/net/enp4s0f1/device/sriov_numvfs" >> /etc/rc.local

但是呢,文档里也提示了:

注意
因为额外的 systemd,Red Hat Enterprise Linux 将会并行启动服务,而不是依次启动它们。这意味着,rc.local 在引导过程中被执行的位置是不固定的。因此,一些不可预见的情况可能会出现,我们不推荐使用这个方法。

也就是说,因为systemd的并行特性,可能这么做不一定能获得预期的结果。

同样是这篇文档,还提供了驱动options的方式:

[root@compute ~]# echo "options igb max_vfs=7" >>/etc/modprobe.d/igb.conf

然而不巧的是,并不是所有的网卡驱动都支持这个option,在例子里使用的igb驱动,这个驱动对应的主要是Intel的千兆网卡芯片比如I350,而我们使用的是X700系列的网卡,加载的驱动是i40e,这个驱动并没有对应的max_vfs参数,只提供了一个debug参数:

# modinfo i40e
filename:       /lib/modules/3.10.0-957.21.3.el7.x86_64/updates/drivers/net/ethernet/intel/i40e/i40e.ko
version:        2.8.43
license:        GPL
description:    Intel(R) 40-10 Gigabit Ethernet Connection Network Driver
author:         Intel Corporation, <e1000-devel@lists.sourceforge.net>
retpoline:      Y
rhelversion:    7.6
srcversion:     F8774F317E76D8D6B699043
...
depends:        ptp
vermagic:       3.10.0-957.21.3.el7.x86_64 SMP mod_unload modversions
parm:           debug:Debug level (0=none,...,16=all) (int)

那怎么办呢?经历了一番折腾,算是找到了一个比较稳定的办法,利用Udev,通过编写Udev Rule文件,实现开机自动打开SR-IOV功能,这么做还额外带来另一个好处,还记得之前写的文章使用udev重命名网卡么,这里顺便也把网卡名字改成统一的了,既实现了SR-IOV的开机配置,又统一了网卡名字,一举两得!

既然是Udev,那就需要一些匹配规则了,针对网卡来说,最稳定的匹配规则之一,就是网卡的Mac地址,所以,可以确定的是,肯定会根据网卡Mac地址去匹配:

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:11:22:33:44:01", NAME="eth0"

然后呢,这里仅仅是设置了网卡名字,SR-IOV怎么开呢?我们先进到网卡的目录看一眼:

[root@compute ~]# ls /sys/class/net/eth0/
addr_assign_type  carrier          dev_port  gro_flush_timeout  link_mode     phys_port_id    proto_down  subsystem
address           carrier_changes  dormant   ifalias            mtu           phys_port_name  queues      tx_queue_len
addr_len          device           duplex    ifindex            netdev_group  phys_switch_id  speed       type
broadcast         dev_id           flags     iflink             operstate     power           statistics  uevent

可以这么理解:规则里的ATTR{address},相当于/sys/class/net/eth0/address文件,那么也就是说ATTR所代表的目录就是/sys/class/net/eth0/,那/sys/class/net/eth0/device/sriov_numvfs这个配置文件不就是ATTR{device/sriov_numvfs}么?是这样么?是的,没错,那在规则文件里,使用=号赋值就行了吧:

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:11:22:33:44:01", ATTR{device/sriov_numvfs}="7", NAME="eth0"

是不是真的可以呢?可以测试一下,把上面的内容保存到/etc/udev/rules.d/70-persistent-net.rules:执行udevadm test /sys/class/net/eth0,从一大堆输出里找到:

[root@compute ~]# udevadm test /sys/class/net/eth0
...
ATTR '/sys/devices/pci0000:17/0000:17:02.0/0000:19:00.1/net/eth0/device/sriov_numvfs' writing '7' 70-persistent-net.rules:1
NAME 'eth0' /etc/udev/rules.d/70-persistent-net.rules:1
...

确实可以!重启试试,依然能工作。问题解决。

当然,这自然不是唯一的办法,目前呢,还能想到的一个办法,就是写个Oneshot的Systemd Service,在network-pre.target这个阶段执行,这样可以比较稳定的确定执行的时间,不至于像rc.local那样不知道什么时候被执行了。

最后感慨一下:Udev真的是个强大的工具!