针对网卡名字这个问题,其实之前也讨论过一次,主要是如何利用udev去重命名网卡,里面提到了新的一致性命名规则,但是没有细说。
当然肯定是遇到问题了,所以针对网卡命名的细节,需要再探讨一下。
其实目前大家还是更熟悉老的那种eth0,eth1…那种命名,目前我们大部分生产环境里也是这么用的。但是随着网卡数量越来越多(在我们使用的SR-IOV场景,加上VF虚拟网卡,机器上已经有超过16个网卡)这种命名规则已经不适应现代的硬件和操作系统了,所以最近我们也老的命名方式,切换到操作系统默认的一致网络设备命名。
不过因此也带来了一些问题,发现不同厂商,或者不同机器会有命名不一致的情况,举个例子:
这是某台机器的网卡名字:
[root@ ~]# ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp59s0f0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000 link/ether 0c:42:a1:b3:12:b8 brd ff:ff:ff:ff:ff:ff 3: enp59s0f1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000 link/ether 0c:42:a1:b3:12:b8 brd ff:ff:ff:ff:ff:ff
一块网卡的两个网口,被分别命名为enp59s0f0
和enp59s0f1
,这个还是挺容易理解的,通过网卡的PCIE地址进行命名,我们看下这块网卡的PCIE信息:
[root@ ~]# lspci |grep Mellanox 3b:00.0 Ethernet controller: Mellanox Technologies MT27800 Family [ConnectX-5] 3b:00.1 Ethernet controller: Mellanox Technologies MT27800 Family [ConnectX-5]
针对lspci
的输出,之前的文章lspci命令输出的一些解释也讨论过了:PCIE地址中总线编号3b
对应到十进制就是59,后面的00.0和00.1分别对应设备编号0的第0和第1个Function,对应到网卡的名字,就是插在59总线的第0个插槽的第0个网口和第1个网口,因为网卡的插槽是不会变的,而且是个物理状态,自然网卡的名字就稳定了。
那么,按正常的理解,不同厂商之间,应该只是插槽不一样吧?然而却被事实打脸,换了一台机器,又是另外一个样子了:
[root@bx-10-13-207-38.chinaunicom-north-1.node.kubernetes ~]# ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens2f0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000 link/ether 04:3f:72:ac:f6:0e brd ff:ff:ff:ff:ff:ff 3: ens2f1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000 link/ether 04:3f:72:ac:f6:0e brd ff:ff:ff:ff:ff:ff
如果还像上面那样理解这个名称的话,那应该网卡插在00总线的第2个插槽?因为总线是0所以省略了?然而继续被打脸:
[root@ ~]# lspci |grep Mellanox 86:00.0 Ethernet controller: Mellanox Technologies MT27800 Family [ConnectX-5] 86:00.1 Ethernet controller: Mellanox Technologies MT27800 Family [ConnectX-5]
发现网卡插在0x86
这个总线上,那换算成十进制应该是134啊?网卡名字不应该是enp134s0f0
和enp134s0f1
么,为啥变成ens2f0
和ens2f1
了?
于是乎,又翻开红帽的文档,再仔细阅读一下:
设备命名过程如下:
1. /usr/lib/udev/rules.d/60-net.rules文件中的规则会让 udev 帮助工具/lib/udev/rename_device查看所有/etc/sysconfig/network-scripts/ifcfg-suffix文件。如果发现包含HWADDR条目的ifcfg文件与某个接口的MAC地址匹配,它会将该接口重命名为ifcfg文件中由DEVICE指令给出的名称。
2. /usr/lib/udev/rules.d/71-biosdevname.rules中的规则让biosdevname根据其命名策略重命名该接口,即在上一步中没有重命名该接口、已安装biosdevname、且未在boot命令行中将biosdevname=0作为内核命令给出。
3. /lib/udev/rules.d/75-net-description.rules中的规则让udev通过检查网络接口设备,填写内部udev设备属性值ID_NET_NAME_ONBOARD、ID_NET_NAME_SLOT、ID_NET_NAME_PATH。注:有些设备属性可能处于未定义状态。
4. /usr/lib/udev/rules.d/80-net-name-slot.rules中的规则让udev重命名该接口,优先顺序如下:ID_NET_NAME_ONBOARD、ID_NET_NAME_SLOT、ID_NET_NAME_PATH。并提供如下信息:没有在步骤 1 或 2 中重命名该接口,同时未给出内核参数net.ifnames=0。如果一个参数未设定,则会按列表的顺序设定下一个。如果没有设定任何参数,则不会重命名该接口。
再理理这个命名过程:
第1步先看看/etc/sysconfig/network-scripts/
文件夹下有没有MAC地址匹配的配置文件,如果有,就按配置文件里的命名来。这里我们的系统不会命中。
第2步看是否使用biosdevname
进行命名,这个是Dell的机器专属,不过我们统一给内核加上了biosdevname=0
参数来统一Dell和其他厂商,所以这条应该也不会命中。
第3步是通过udev规则填一些env,暂时不涉及命名过程。
第4步是根据env进行重命名,并且有个优先顺序:ID_NET_NAME_ONBOARD、ID_NET_NAME_SLOT、ID_NET_NAME_PATH。
所以仔细点会发现,enp59s0f0
这种命名规则是命中了ID_NET_NAME_PATH
,是以PCI物理位置为规则命名;而ens2f0
这种则是命中了ID_NET_NAME_SLOT
,是以热插拔插槽索引号命名。
但是为什么会有这样的差异,依然不明朗,继续追踪,使用udevadm info
命令看看能不能找到两者不同:
以ID_NET_NAME_PATH
命名的机器:
[root@ ~]# udevadm info /sys/class/net/enp59s0f0 P: /devices/pci0000:3a/0000:3a:00.0/0000:3b:00.0/net/enp59s0f0 E: DEVPATH=/devices/pci0000:3a/0000:3a:00.0/0000:3b:00.0/net/enp59s0f0 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=MT27800 Family [ConnectX-5] (ConnectX®-5 EN network interface card, 10/25GbE dual-port SFP28, PCIe3.0 x8, tall bracket ; MCX512A-ACAT) E: ID_MODEL_ID=0x1017 E: ID_NET_DRIVER=mlx5_core E: ID_NET_NAME_MAC=enxb8599fbaa608 E: ID_NET_NAME_PATH=enp59s0f0 E: ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. E: ID_PATH=pci-0000:3b:00.0 E: ID_PATH_TAG=pci-0000_3b_00_0 E: ID_PCI_CLASS_FROM_DATABASE=Network controller E: ID_PCI_SUBCLASS_FROM_DATABASE=Ethernet controller E: ID_VENDOR_FROM_DATABASE=Mellanox Technologies E: ID_VENDOR_ID=0x15b3 E: IFINDEX=6 E: INTERFACE=enp59s0f0 E: MAJOR=0 E: MINOR=0 E: SUBSYSTEM=net E: SYSTEMD_ALIAS=/sys/subsystem/net/devices/enp59s0f0 /sys/subsystem/net/devices/enp59s0f0 E: TAGS=:systemd: E: UDEV_BIOSDEVNAME=1 E: USEC_INITIALIZED=54242 E: biosdevname=0 E: net.ifnames=1 |
以ID_NET_NAME_SLOT
命名的机器:
[root@ ~]# udevadm info /sys/class/net/ens2f0 P: /devices/pci0000:85/0000:85:00.0/0000:86:00.0/net/ens2f0 E: DEVPATH=/devices/pci0000:85/0000:85:00.0/0000:86:00.0/net/ens2f0 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=MT27800 Family [ConnectX-5] (ConnectX®-5 EN network interface card, 10/25GbE dual-port SFP28, PCIe3.0 x8, tall bracket ; MCX512A-ACAT) E: ID_MODEL_ID=0x1017 E: ID_NET_DRIVER=mlx5_core E: ID_NET_NAME_MAC=enx043f72acf60e E: ID_NET_NAME_PATH=enp134s0f0 E: ID_NET_NAME_SLOT=ens2f0 E: ID_PATH=pci-0000:86:00.0 E: ID_PATH_TAG=pci-0000_86_00_0 E: ID_PCI_CLASS_FROM_DATABASE=Network controller E: ID_PCI_SUBCLASS_FROM_DATABASE=Ethernet controller E: ID_VENDOR_FROM_DATABASE=Mellanox Technologies E: ID_VENDOR_ID=0x15b3 E: IFINDEX=2 E: INTERFACE=ens2f0 E: SUBSYSTEM=net E: SYSTEMD_ALIAS=/sys/subsystem/net/devices/ens2f0 E: TAGS=:systemd: E: UDEV_BIOSDEVNAME=0 E: USEC_INITIALIZED=34864 E: biosdevname=0 E: net.ifnames=1 |
大部分都是一样的,但是以ID_NET_NAME_SLOT
命名的机器,ID_NET_NAME_SLOT这个ENV不为空,而以ID_NET_NAME_PATH
命名的机器直接就没有ID_NET_NAME_SLOT
这个ENV。那么按照/usr/lib/udev/rules.d/80-net-name-slot.rules
里的udev规则:
[root@ ~]# cat /usr/lib/udev/rules.d/80-net-name-slot.rules # do not edit this file, it will be overwritten on update ACTION!="add", GOTO="net_name_slot_end" # 如果不是设备添加操作,直接跳过 SUBSYSTEM!="net", GOTO="net_name_slot_end" # 如果不是一个网络设备,直接跳过 NAME!="", GOTO="net_name_slot_end" # 如果已经有名字了,直接跳过 IMPORT{cmdline}="net.ifnames" ENV{net.ifnames}=="0", GOTO="net_name_slot_end" # 如果内核参数里有net.ifnames=0,直接跳过 NAME=="", ENV{ID_NET_NAME_ONBOARD}!="", NAME="$env{ID_NET_NAME_ONBOARD}" # 没有名字,ID_NET_NAME_ONBOARD这个ENV存在,用ID_NET_NAME_ONBOARD作为名字 NAME=="", ENV{ID_NET_NAME_SLOT}!="", NAME="$env{ID_NET_NAME_SLOT}" # 没有名字,ID_NET_NAME_SLOT这个ENV存在,用ID_NET_NAME_SLOT作为名字 NAME=="", ENV{ID_NET_NAME_PATH}!="", NAME="$env{ID_NET_NAME_PATH}" # 没有名字,ID_NET_NAME_PATH这个ENV存在,用ID_NET_NAME_PATH作为名字 LABEL="net_name_slot_end" |
和文档说的一样,如果ENV{ID_NET_NAME_SLOT}
有值的话,那就直接用它了,就不会再用ENV{ID_NET_NAME_PATH}
了。
那么问题来了,这些ENV又是咋来的?文档里说是在/lib/udev/rules.d/75-net-description.rules
中设置的。那就看看这个文件:
[root@ ~]# cat /usr/lib/udev/rules.d/75-net-description.rules # do not edit this file, it will be overwritten on update ACTION=="remove", GOTO="net_end" SUBSYSTEM!="net", GOTO="net_end" IMPORT{builtin}="net_id" # 这些ENV应该是在这里设置的,因为没有其他显式赋值的地方了。 SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" SUBSYSTEMS=="usb", GOTO="net_end" SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}" SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci" LABEL="net_end" |
这里的IMPORT{builtin}="net_id"
是udev内部的实现。我们需要找到实现对应的代码了。
找了一下,代码在udev-builtin.c
static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { #if HAVE_BLKID [UDEV_BUILTIN_BLKID] = &udev_builtin_blkid, #endif [UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs, [UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb, [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id, [UDEV_BUILTIN_KEYBOARD] = &udev_builtin_keyboard, #if HAVE_KMOD [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, #endif [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, # 就是这里 [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif }; |
而udev_builtin_net_id
的实现在udev-builtin-net_id.c,这里不仔细分析了,就看看ID_NET_NAME_SLOT
这个ENV:
static int dev_pci_slot(sd_device *dev, struct netnames *names) { // ... r = sd_device_get_sysname(names->pcidev, &sysname); if (r < 0) return r; r = sd_device_get_syspath(pci, &syspath); if (r < 0) return r; if (!snprintf_ok(slots, sizeof slots, "%s/slots", syspath)) return -ENAMETOOLONG; // 这里的slots对应'/sys/bus/pci/slots/'这个目录 dir = opendir(slots); if (!dir) return -errno; hotplug_slot_dev = names->pcidev; while (hotplug_slot_dev) { if (sd_device_get_sysname(hotplug_slot_dev, &sysname) < 0) continue; // 遍历'/sys/bus/pci/slots/'目录 FOREACH_DIRENT_ALL(dent, dir, break) { unsigned i; char str[PATH_MAX]; _cleanup_free_ char *address = NULL; if (dot_or_dot_dot(dent->d_name)) continue; r = safe_atou_full(dent->d_name, 10, &i); if (r < 0 || i <= 0) continue; /* match slot address with device by stripping the function */ // 如果有任何目录中'/sys/bus/pci/slots/{xx}/address'文件里的值和网卡的设备号对应的话, // 说明这块网卡是插在可热拔插插槽的,插槽号是{xx} if (snprintf_ok(str, sizeof str, "%s/%s/address", slots, dent->d_name) && read_one_line_file(str, &address) >= 0 && startswith(sysname, address)) { hotplug_slot = i; break; } } if (hotplug_slot > 0) break; if (sd_device_get_parent_with_subsystem_devtype(hotplug_slot_dev, "pci", NULL, &hotplug_slot_dev) < 0) break; rewinddir(dir); } if (hotplug_slot > 0) { s = names->pci_slot; l = sizeof(names->pci_slot); if (domain > 0) l = strpcpyf(&s, l, "P%d", domain); l = strpcpyf(&s, l, "s%d", hotplug_slot); // 如果是热拔插网卡,那名字就是s+插槽号 if (func > 0 || is_pci_multifunction(names->pcidev)) l = strpcpyf(&s, l, "f%d", func); if (port_name) l = strpcpyf(&s, l, "n%s", port_name); else if (dev_port > 0) l = strpcpyf(&s, l, "d%lu", dev_port); if (l == 0) names->pci_slot[0] = '\0'; } return 0; } |
噢,发现区别了,ens2f0
的网卡是插在热拔插2插槽的。再确认一下,看看机器上是不是有对应文件:
[root@ ~]# cat /sys/bus/pci/slots/2/address 0000:86:00 |
发现地址确实是吻合的,而且名字和插槽号确实能对应上。
好吧,到这里问题算是解决了一半。至少知道了为啥网卡名字会不同了,但是也没办法去控制不同厂商去规定网卡地址必须一样了。所以呢,业务层还是需要针对这种场景去做些适配。或者,我们再自定义一个udev文件,另起门户,单独做一个我们自己的命名规则,将所有厂商给统一起来了。
评论前必须登录!
注册