1. 网卡名漂移这件事到底坑过多少人机房里的服务器装完系统第一件事往往是看网卡叫什么。同型号同批次的两台机器一台是eth0/eth1另一台可能是ens33/ens37再换一台变成enp3s0/enp4s0。如果只是看一眼倒还好真正要命的是那些写死在脚本、配置模板、Ansible playbook 和监控采集项里的接口名——一旦名字漂移自动化流程就会在某个深夜静悄悄地挂掉。用用户定义的 udev 规则给网卡做重命名本质就是把这层不确定性按死让物理上第几个插槽的那块卡永远对应一个你自己定的名字。1.1 三台机器同一份配置只有一台起不来这个场景我遇到过不止一次一批机器用同一份 kickstart 或 cloud-init 模板装机模板里写着DEVICEeth0、BOOTPROTOstatic、IPADDR10.0.0.10。部署的时候一切正常因为装机时网卡的枚举顺序碰巧是对的。等到某次机房维护、拔插了一次网线、或者主板 BIOS 里关掉了一个没用的板载网口再打开枚举顺序就变了原来排第一的那块卡变成了第二个注册于是eth0和eth1的身份互换。结果是两台机器的管理 IP 全反了监控告警炸成一片登录进去一看配置文件还在只是它认领的那块卡已经换人了。这类问题的麻烦之处在于它不报错。系统启动正常网卡也有 linkip a输出里eth0当然存在——只不过它背后的那块物理芯片换了一个。你以为你在配 A 网口实际上配的是 B 网口。排查的时候如果不知道名字跟硬件没有强绑定这个前提很容易在防火墙、交换机侧、路由表上绕一大圈。1.2 内核给网卡排队的依据谁先注册谁排前面要理解为什么会漂移得先看清楚内核是怎么命名的。传统的eth0、eth1、eth2完全是按驱动探测和注册的先后顺序递增分配的内核本身并不关心这块卡插在哪个槽位它的 MAC 是多少。谁先被驱动 probe 到谁就先拿到eth0。而驱动 probe 的顺序受 PCI 总线枚举顺序、驱动加载顺序、内核参数里模块的加载时机影响这些因素在冷启动、热插拔、内核升级、BIOS 版本变更之后都可能变化。也就是说传统命名是一套动态序号它描述的只是这是第几个被发现的以太网口而不是这是哪一个物理口。序号型命名在多网口机器上稳定性极差尤其是刀片服务器、多口网卡一张卡四个口、外加 PCIe 网卡混插的机型。真正稳定的标识必须是硬件自带的、不会变的属性MAC 地址、PCI 设备路径总线号设备号功能号、或者固件提供的板载插槽编号。注意MAC 地址虽然写在网卡上但它并不是绝对不可变。板载网卡的 MAC 可以刷写虚拟机克隆时 VMware、KVM 会把 MAC 一起复制某些网卡的 MAC 在高可用切换、SR-IOV 虚拟功能场景下还会被上层改写。选匹配键的时候要把这些情况考虑进去。1.3 什么场景值得专门写规则改名不是所有机器都需要折腾这件事。单网口的笔记本、随手开的测试虚拟机叫什么名字都无所谓。但下面几类场景把名字固定下来收益非常明显多网口服务器做角色区分管理口、业务口、存储口、专线口如果分别叫lan0、wan0、san0、dci0看配置文件一眼就懂不需要再对着 IP 反查是哪个物理口。Bond 和 Bridge 做聚合bond0下面挂哪几块网卡如果成员名字会漂移一次重启就可能把聚合的两条链路挂到错误的物理交换机端口上轻则丢包重则断网。防火墙、软路由、网关设备内外网口绝对不能搞反。很多小主机用的是第三方多口网卡槽位顺序和系统里的枚举顺序天生不一致。批量装机与自动化运维Ansible、SaltStack、Puppet 里如果接口名是变量能省掉大量条件判断配置文件的 diff 也干净。DPDK、VPP 这类用户态转发程序它们按 PCI 地址绑定网卡但配套的配置脚本、日志、运维文档里通常还是用接口名做对照名字混乱会显著提高沟通成本。我个人的判断标准很简单只要这台机器上哪个口做什么是有语义的就应该把名字固定下来。花二十分钟写一条规则能省掉未来几年里若干次深夜排查。2. 命名权之争内核、systemd、udev 各管哪一段很多人写 udev 规则改不动网卡名根本原因是不清楚这条链路上有三个角色在抢命名权而它们生效的时机、优先级、可覆盖范围各不相同。把顺序理清楚规则一次就能写对。2.1 内核只负责注册名字最长 15 个字符内核在注册网络设备时会调用dev_get_valid_name()做校验这里有个硬性限制接口名最大长度是IFNAMSIZ - 1也就是15 个字符。超过长度的名字会被直接拒绝改名失败但不会给你一个特别醒目的报错通常只在dmesg里留一行。名字里也不能出现/和空白字符不建议用.、:开头因为有些工具会把这几个字符当成别名分隔符来解析。另一个容易被忽略的点是改名必须在接口 up 之前完成。内核的dev_change_name()在接口已经运行时调用会有各种限制而 udev 处理网卡add事件的时机恰好就在接口刚注册、还没被用户态配置程序接管的那一小段窗口里。这也解释了为什么机器跑起来之后手动改个名和用 udev 规则改个名完全是两码事前者是运行期的临时操作重启就没了后者发生在上电初期名字从诞生那一刻就是对的所有后续程序看到的都是最终名字。2.2 systemd 的 predictable names 是怎么插队的从 udev 升级为 systemd-udevd 之后新的发行版默认启用了一套可预测命名机制规则文件位于/usr/lib/udev/rules.d/核心是75-net-description.rules负责给设备打上ID_NET_NAME_*属性和80-net-setup-link.rules负责依据.link文件真正执行改名。这套机制生成的名字就是大家熟悉的几种形式名字形式含义示例enoNonboard固件给出的板载网卡索引eno1、eno2ensNslotPCI 热插拔槽位索引ens33、ens192enpBsFPCI 总线号 B、功能号 Fenp3s0、enp4s0f1enxMAC基于 MAC 地址生成十六进制无分隔符enx005056a1b2c3这套机制比eth0稳定得多但它有两个问题。一是名字不直观enp3s0和enp4s0哪个是外网口不看文档根本不知道。二是它并不保证跨机器一致换一台主板 PCI 拓扑变了名字跟着变。所以即便在默认启用可预测命名的系统上用自定义规则覆盖掉它依然是很常见的做法。常见的覆盖手段有两种在内核启动参数里加net.ifnames0 biosdevname0把整套机制关掉回到eth0风格然后用 udev 规则改名或者保留机制用优先级更高的规则或.link文件把它替换成你要的名字。前者简单粗暴后者更文明具体选哪个后面细说。2.3 udev 处理 net 设备的两个关键时机udev 处理网络设备主要在两个动作上add设备首次出现包括开机枚举和热插拔和change属性变化比如 link up/down、MTU 变更。改名规则必须匹配add动作因为只有这个时刻接口还没被用户态程序引用改名是安全的。匹配change去改名基本上不会成功而且会拖慢整个事件处理链。规则文件的执行顺序是按文件名的字典序来的。目录有三个层级优先级从高到低是/etc/udev/rules.d/、/run/udev/rules.d/、/usr/lib/udev/rules.d/老系统上是/lib/udev/rules.d/。同名文件只有优先级最高的那一个生效这一点很重要如果你在/etc/udev/rules.d/里放一个跟系统自带同名的文件系统那份就被完全遮蔽了而不是合并。/etc/udev/rules.d/70-persistent-net.rules ← 你写的优先 /run/udev/rules.d/ ← 临时规则次优先 /usr/lib/udev/rules.d/80-net-setup-link.rules ← 系统自带文件名的数字前缀决定了同一目录内的执行顺序。数字越小越早执行所以想抢在系统规则之前把名字定下来前缀要小于 80。实践中最常用的是70-这个区间既有足够余量插在前面又不会早到系统还没准备好相关属性。我见过有人用10-前缀大多数情况下也能工作但如果你的规则里引用了ID_NET_NAME_*这类由75-net-description.rules计算出来的属性前缀太小反而拿不到值规则就静默失效了。匹配键决定了你的前缀能取多小这一点比很多人想象的重要。3. 动手写规则之前先把网卡的身份证摸清楚写规则最大的时间成本不在写而在确认匹配键。凭感觉抄一个 MAC 地址填进去多半会踩到虚拟机克隆后 MAC 重复或者MAC 被固件改写的坑。3.1 udevadm info 输出的 ID_NET_NAME 系列属性udevadm info是摸清设备属性的主力工具。看单块网卡的完整属性udevadm info -q all -p /sys/class/net/ens33 # 或者简写直接给设备路径 udevadm info /sys/class/net/ens33输出里重点看这几类属性ID_NET_NAME_MAC基于永久 MAC 生成的稳定名字形如enx005056a1b2c3。ID_NET_NAME_ONBOARD只有当固件SMBIOS type 41提供了板载网卡插槽编号时才有值形如eno1。有这个属性的设备是板载网卡稳定性最好。ID_NET_NAME_PATH基于 PCI 物理路径生成形如enp3s0只要卡不换槽位就不变。ID_NET_NAME_SLOT基于热插拔槽位索引形如ens33虚拟机里最常见。ID_PATH形如pci-0000:03:00.0这是 PCI 路径的原始表达非常适合作为匹配键。ID_NET_NAME以上几种按优先级挑选出来的最终结果就是系统实际会用的名字。ID_NET_DRIVER驱动名比如e1000e、ixgbe、virtio_net。如果udevadm info的输出里这几个ID_NET_NAME_*一个都没有通常说明该设备路径不是/sys/class/net/下的接口比如你给的是/sys/class/net/eth0/../../...这种混乱路径或者当前系统的 udev 规则被裁剪过容器、精简镜像里很常见。这时候用-a参数看属性链或者干脆用KERNELS直接匹配 PCI 地址。3.2 MAC 地址、PCI 路径、设备槽位三种匹配键怎么选这是整件事里最需要判断力的一步。三种匹配键各有适用边界选错了在特定场景下就是定时炸弹。匹配键写法适合场景风险点MAC 地址ATTR{address}00:25:90:aa:bb:cc物理服务器、网卡 MAC 不会被改的环境虚拟机克隆 MAC 重复MAC 被人工刷写SR-IOV VF 的 MAC 由上层分配PCI 路径ENV{ID_PATH}pci-0000:03:00.0追求槽位固定的物理机、按槽位规划角色的机器换槽位名字跟着变虚拟机热插拔槽位号可能变化固件槽位ENV{ID_NET_SLOT}1或ATTRS{index}1板载多口网卡固件编号稳定依赖固件提供老机器可能没有我的经验法则可以概括成三句话。第一物理机优先用 PCI 路径ID_PATH。因为物理机的 PCI 拓扑是焊死在主板上的网卡不挪位置名字就不变而且它天然表达这台机器第几个槽位符合运维按槽位规划角色的习惯。MAC 也能用但一旦遇到网卡返修更换、MAC 被刷改规则就失效了。第二虚拟机优先用 MAC 地址或者虚拟机平台侧的网卡插槽号。VMware 的 VMX 配置里网卡本身就有固定的序号ethernet0、ethernet1KVM/QEMU 里可以给virtio-net设备指定固定的 PCI 槽位。虚拟机里 MAC 一般是平台按 OUI 规则生成的只要不克隆就不用担心重复反而比ID_PATH可能随热插拔变化更稳。第三板载多口网卡可以两者结合。用ID_PATH做主匹配同时用 MAC 做一次交叉校验写注释记录两个值将来排查问题时一目了然。3.3 名字的命名规范与冲突规避名字本身也有一些约定俗成的约束提前定好能省很多事。长度控制在 8 个字符以内虽然上限是 15但太长的名字在ip a输出里会折行在监控图表里会挤在一起运维体验很差。不要用纯数字开头某些工具解析时会有歧义。常见做法是加一个前缀比如内网口lan0、lan1外网口wan0存储口san0带外管理口ipmi0。避开系统的保留前缀。eth、en、enp、ens、eno这些前缀在 systemd 的可预测命名里是有语义的你自己定一个en开头的名字很容易跟内置规则生成的候选名撞车导致改名行为随版本变化。用lan、wan、nic、pub这类自造前缀最安全。all、default、bond0之类的特殊名不要占用。all在某些工具里是通配bond、br、vlan、veth、virbr这些前缀跟虚拟网络设备有关系混用会让排查变得很烦。4. 两条落地路线udev 的 NAME 与 systemd 的 .link 文件改名的规则写法有两代。老一代是纯 udev 规则里的NAME新一代是 systemd 网络设备配置的.link文件。两种都有人用理解它们的区别和各自的问题才能选对。4.1 传统派70-persistent-net.rules 的写法与数字前缀在 RHEL 6 时代/etc/udev/rules.d/70-persistent-net.rules是由系统自动生成的每次发现新网卡就追加一行。那套机制早就废弃了但手写一份70-persistent-net.rules这个做法流传了下来很多运维习惯用它。基本写法是这样# /etc/udev/rules.d/70-persistent-net.rules # 按 MAC 匹配 SUBSYSTEMnet, ACTIONadd, ATTR{address}00:25:90:aa:bb:01, NAMElan0 SUBSYSTEMnet, ACTIONadd, ATTR{address}00:25:90:aa:bb:02, NAMEwan0 # 按 PCI 路径匹配 SUBSYSTEMnet, ACTIONadd, ENV{ID_PATH}pci-0000:03:00.0, NAMElan1 SUBSYSTEMnet, ACTIONadd, ENV{ID_PATH}pci-0000:04:00.0, NAMEwan1几个必须写全的元素SUBSYSTEMnet限定设备类型ACTIONadd限定时机然后才是匹配键和NAME。少写ACTION会让规则在change事件上反复触发少写SUBSYSTEM则可能误伤其他类型的设备。NAME路线最大的问题是跟 systemd 的内置规则打架。systemd 官方文档里已经明确表达了态度不建议再用 udev 的NAME来改网络接口名理由是改名时机与用户态引用之间存在竞态改到一半的名字可能已经被其他组件拿到。实测中这个冲突表现出来的现象是有时候名字生效有时候生效的是enp3s0升级一次系统之后行为又变了。想让NAME稳定工作通常需要在内核启动参数里加上net.ifnames0 biosdevname0把系统那套可预测命名整体关掉让 udev 成为唯一的命名来源。这个方案在物理服务器上非常成熟RHEL 7/8/9、Rocky、openEuler 都适用改法是在/etc/default/grub的GRUB_CMDLINE_LINUX里追加参数然后重新生成 grub 配置并重启。4.2 现代派.link 文件的 Match/Link 段systemd 提供的.link文件是官方推荐的做法放在/etc/systemd/network/目录下注意不是systemd-networkd的.network两者用途完全不同。文件名同样按字典序生效习惯用10-到99-之间的前缀。一个典型的例子# /etc/systemd/network/10-lan0.link [Match] MACAddress00:25:90:aa:bb:01 [Link] Namelan0按 PCI 路径匹配的写法# /etc/systemd/network/10-wan0.link [Match] Pathpci-0000:04:00.0 [Link] Namewan0.link文件的好处是它由 systemd 的net_setup_link内建统一处理不存在跟内置规则抢执行顺序的问题配置语义也更清楚[Match]段负责选中设备[Link]段负责施加属性。除了Name[Link]段还支持MTUSize、MACAddressPolicy、WakeOnLan等一堆链路级参数等于顺手把网卡的基础参数也收编了。验证.link文件是否被正确解析# 直接跑内建看它打算做什么 udevadm test-builtin net_setup_link /sys/class/net/ens33 # 重载 udev 规则并重新触发 udevadm control --reload-rules udevadm trigger --subsystem-matchnet --actionadd需要提醒的是.link里的Name同样只在设备初次出现时生效。运行期重新触发add事件对已经 up 的接口通常改不动名字这时候别怀疑规则写错了直接把接口 down 掉再触发或者重启验证。4.3 altname不动原名加个别名的折中方案如果你的内核和 systemd 版本比较新systemd 249 及以上还有一个思路值得知道不改名加别名。Linux 网络设备支持altname一个接口可以同时拥有原名和若干个替代名ip a里会显示成2: enp3s0: BROADCAST,MULTICAST,UP,LOWER_UP加一行altname lan0。# 运行期手动加重启失效 ip link property add dev enp3s0 altname lan0 # 持久化写进 .link 文件 # /etc/systemd/network/10-altname.link [Match] Pathpci-0000:03:00.0 [Link] AlternativeNamelan0这个方案的妙处在于它不破坏任何既有假设。内核自带的enp3s0还在NetworkManager 的旧连接配置不会失联你在脚本和文档里可以放心用lan0。缺点是老内核不支持ip版本太老也不认这个子命令。我一般在整个系统版本比较统一的新集群上优先考虑它在老设备或者混合版本的环境里还是老老实实改名。5. 一个真实场景三网口机器固定成 lan0/wan0/dmz0拿一台典型的三网口网关机器举例一块板载 Intel 双口一张 PCIe 插槽上的单口网卡。目标是固定成lan0板载口 1内网、wan0板载口 2外网、dmz0PCIe 卡DMZ 区。5.1 采集信息与规则生成第一步永远是采集不要在没看清属性的情况下写规则。# 列出所有网卡的接口名和 MAC for i in /sys/class/net/*; do [ -e $i/device ] || continue printf %-12s %s\n $(basename $i) $(cat $i/address) done # 逐块看 udev 属性 udevadm info /sys/class/net/enp3s0 | grep -E ID_PATH|ID_NET_NAME|ID_NET_DRIVER假设采集结果是这样接口名MACID_PATH角色eno100:25:90:aa:bb:01pci-0000:00:1f.6内网eno200:25:90:aa:bb:02pci-0000:00:1f.7外网enp3s000:25:90:aa:bb:03pci-0000:03:00.0DMZ板载两个口都有ID_NET_NAME_ONBOARD说明固件支持 SMBIOS 插槽编号稳定性最好PCIe 卡只有ID_NET_NAME_PATH。这里我选择统一用ID_PATH作为匹配键理由是它三块卡都有规则风格一致而且物理机上 PCI 路径不会变。同时把 MAC 写进注释方便日后硬件更换时对照。# /etc/systemd/network/10-netnames.link [Match] Pathpci-0000:00:1f.6 [Link] Namelan0 [Match] Pathpci-0000:00:1f.7 [Link] Namewan0 [Match] Pathpci-0000:03:00.0 [Link] Namedmz0如果一个文件里写多组[Match]/[Link]语义上要小心稳妥做法是一块卡一个文件文件名带序号比如10-lan0.link、11-wan0.link、12-dmz0.link。这样出问题时可以单独禁用某一个文件来定位不用整体回滚。5.2 生效、验证与回滚写完之后先别急着重启用测试模式验证规则的解析结果# 看内建是否认出了你的配置 udevadm test-builtin net_setup_link /sys/class/net/eno1 21 | grep -i name # 重载规则 udevadm control --reload-rules确认无误后重启。重启起来第一件事是核对ip -br link show # 看接口名和状态 ip -br addr show # 看 IP 是否落在正确的口上ip -br这种简洁输出特别适合做核对一行一个接口状态、MAC、IP 一目了然。回滚方案要提前准备好。.link文件的回滚非常干净把文件改名成.link.bak或者删掉udevadm control --reload-rules之后重启系统就回到默认命名了。关键是要知道自己原来的名字是什么所以采集阶段那三行命令的输出一定要存档我通常直接写进/root/net-inventory-$(date %F).txt。5.3 和 NetworkManager/ifcfg 的联动收尾改名只是第一步真正让机器能用还要处理上层配置工具的关联。这块是最容易漏的漏了就会出现名字对了但没 IP的情况。用NetworkManager的系统上连接配置里通常有interface-name字段。改名之后 NetworkManager 会发现一个陌生的接口然后按自己的逻辑新建一个默认连接一般是 DHCP原来的静态配置文件还在但匹配不到设备处于 inactive 状态。# 查看连接与接口的绑定关系 nmcli -f NAME,DEVICE,TYPE connection show nmcli connection modify lan0-static connection.interface-name lan0 nmcli connection up lan0-static用ifcfg 脚本RHEL 系传统方式的系统上配置文件/etc/sysconfig/network-scripts/ifcfg-名字的文件名和DEVICE字段必须同时改成新名字两者不一致就是经典的配置看起来在网卡起不来。cd /etc/sysconfig/network-scripts git init . 2/dev/null # 改之前先留个版本后悔了能 diff mv ifcfg-eno1 ifcfg-lan0 sed -i s/^DEVICE.*/DEVICElan0/ ifcfg-lan0Debian/Ubuntu 系如果用的是ifupdown/etc/network/interfaces里的段落头名也要一起改用 netplan 的话/etc/netplan/*.yaml里的ethernets:段键名和可选的match:块都要对上。顺序是先让网卡名字稳定再统一迁移配置文件最后重启网络服务倒过来做会陷入改一处坏一处的循环。6. 改完名字网卡不见了排查链路实录下面这套排查顺序是踩坑踩出来的按这个顺序走绝大多数改名失败的问题能在十分钟内定位。6.1 第一步永远是确认规则有没有被读到规则写了没生效先别怀疑语法先确认 udev 有没有读到这个文件。# 看规则文件有没有被语法解析通过 udevadm test /sys/class/net/eno1 21 | head -40 # 看 udev 是否加载了你的文件 udevadm control --reload-rules udevadm info /sys/class/net/eno1 | grep -i ID_NET_NAME用udevadm test的时候注意它只模拟不执行输出里会带上读取了哪些规则文件匹配到了哪些规则最终打算设置什么名字。如果输出里压根没出现你写的文件名问题通常在三个地方目录放错放到了/etc/udev/rules.d之外的目录、后缀不对必须是.rules或.link写成.conf不生效、文件权限不合法udev 出于安全考虑会忽略组可写或全局可写的规则文件改成644并从属 root。6.2 被内置规则抢名、名字冲突与 15 字符越界规则被读到了但结果不对多半是下面几种情况。被内置规则覆盖。你的NAME规则前缀比80大系统自带的80-net-setup-link.rules先执行完改名你的规则再执行时设备已经改过名NAME要么被忽略要么触发二次改名失败。解决办法是把前缀改小或者换成.link文件路线。名字冲突。你想把两块卡都叫lan0或者新名字跟系统里已存在的某个接口包括虚拟网桥、veth、tun重名内核会拒绝改名。ip -br link show挨个看一遍确认没有重名。超出长度限制。名字超过 15 个字符会被内核直接拒绝错误信息藏在dmesg里dmesg | grep -i net\|rename | tail -20匹配键写错了。ATTR{address}和ATTRS{address}是两个不同的东西前者匹配设备本身的属性后者匹配设备父链上的属性。MAC 地址属于设备本身用ATTR{address}如果你要匹配 PCI 控制器上的属性比如ATTRS{vendor}0x8086才用ATTRS。这个区别坑过的人非常多因为写错了不一定报错只是规则永远不匹配表现就是什么都没发生。MAC 地址大小写和分隔符。sysfs 里读出来的 MAC 是小写十六进制、冒号分隔。你从别的文档里抄来的00:25:90:AA:BB:01是大写或者写成了002590aabb01那种无分隔符格式规则都不会匹配。统一用小写加冒号最保险。6.3 网卡有了但没 IP配置文件的 DEVICE 与 interface-name这是另一种典型症状接口名改成功了ip a里能看到lan0但它是DOWN状态没有 IP。这时候排查方向要立刻转到上层配置工具我一般按这个表逐项过一遍。检查项命令期望结果接口是否 upip -br link show lan0状态含UP是否有 IPip -br addr show lan0有预期地址NetworkManager 是否接管nmcli device statusconnected不是unmanaged连接是否激活nmcli connection show --active有对应连接ifcfg 的 DEVICE 字段grep DEVICE /etc/sysconfig/network-scripts/ifcfg-lan0等于lan0netplan 的键名grep -A3 ethernets /etc/netplan/*.yaml键名等于新接口名是否有残留的旧配置ls /etc/sysconfig/network-scripts/ifcfg-eno*空的或已删还有一种隐蔽情况NetworkManager 自动新建了一个同名连接。你之前手动建过一个叫lan0的连接改名之后 NetworkManager 又检测到新接口自动生成一个lan0两个同名连接打架激活的是错的那个。用nmcli connection show看清楚 UUID把多余的那个删掉。7. 批量部署与边界情况把改名沉进镜像和自动化单机改完不算完这套东西真正体现价值是在几十上百台机器上批量落地的时候。7.1 按槽位生成规则的小脚本手写规则在批量场景下不可维护因为每台机器的 MAC 都不一样。正确的思路是按 PCI 路径生成规则而不是按 MAC因为同型号机器的槽位布局完全一致一台机器上写好脚本其他机器直接复用。下面这个脚本从 sysfs 里枚举所有物理网卡按 PCI 路径排序后生成.link文件#!/bin/bash # gen-netnames.sh —— 按 PCI 槽位顺序生成 udev .link 命名规则 set -euo pipefail OUTDIR/etc/systemd/network PREFIX10 INDEX0 [ -d $OUTDIR ] || mkdir -p $OUTDIR rm -f $OUTDIR/${PREFIX}-*.link # 收集有 device 目录的接口即真实物理网卡排除 veth/bridge/tun for dev in /sys/class/net/*; do name$(basename $dev) [ -e $dev/device ] || continue path$(udevadm info $dev 2/dev/null | sed -n s/^E: ID_PATH//p) [ -n $path ] || continue printf %s %s\n $path $name done | sort | while read -r path name; do target$(printf nic%d $INDEX) cat $OUTDIR/${PREFIX}-${target}.link EOF # generated from ${name}, path${path} [Match] Path${path} [Link] Name${target} EOF echo generated: ${target} - ${name} (${path}) INDEX$((INDEX1)) done udevadm control --reload-rules脚本里几个细节值得说明。[ -e $dev/device ]这个判断用来过滤虚拟接口——真实物理网卡在/sys/class/net/*/下一定有device符号链接指向 PCI 设备而veth、docker0、virbr0这些软设备没有这样就避免了脚本给容器网桥也生成规则。sort保证生成的序号在同类机器上是稳定的因为glob展开的顺序不保证。rm -f先清空旧规则避免数字前缀冲突。最后重新加载规则让改动生效名字本身还是要重启才变。如果想更严谨一点可以在生成前做一次重复路径检测awk seen[$1] {print dup:, $1}防止某些奇怪的机器上出现两个接口报同一个ID_PATH。7.2 虚拟化、USB 网卡与热插拔设备的特殊处理虚拟机场景。VMware 的虚拟网卡在客户机里看到的 PCI 路径会随虚拟机配置文件里的设备顺序变化如果中途加过网卡、调整过顺序ID_PATH就不稳。KVM/QEMU 里可以显式指定addr0x3之类的 PCI 地址让它固定下来配好之后再按路径匹配。虚拟机里我更推荐直接用 MAC只要不做模板克隆克隆时记得选重新生成 MACMAC 就是最省事的匹配键。USB 网卡。USB 网卡的ID_PATH形如pci-0000:00:14.0-usb-0:2:1.0包含 USB 端口号。插到不同 USB 口上路径就变了所以这类设备只能用 MAC 匹配而且要注意 USB 网卡在开机时可能还没枚举出来udev 的add事件会晚很多启动脚本里如果依赖这个接口要加等待逻辑。热插拔与多路径。万兆、25G 网卡在有些平台上会出现同一个物理口有两套 sysfs 路径的情况多路径或者 SR-IOV PF/VF 并存。这时候匹配键要加上ATTR{type}1之类的限定或者用KERNELS直接锁 PCI 地址避免把 VF 也匹配进去改了名。容器与网络命名空间。容器里也有/sys/class/net/但里面的接口是宿主机命名空间里挪过来的容器内改名的规则对宿主机无效反之亦然。要改容器里的接口名得在对应的网络命名空间里操作用nsenter -t pid -n ip link set ...这种方式。7.3 集群/批量装机时值得注意的几点第一把规则文件提前放进装机镜像或 kickstart 的%post段而不是装完再手动改。装完再改意味着中间有一段时间机器用的是不稳定名字如果这时候自动化平台已经把监控、配置管理跑了一遍后面改名会触发一轮全量重配。顺序上改名必须在配置管理工具第一次运行之前完成。第二内核参数和规则文件要一起标准化。如果决定用net.ifnames0 biosdevname0关掉可预测命名那么所有机器的 GRUB 配置都要统一包括后来重装内核时grub2-mkconfig生成的配置。我见过一台机器因为内核升级后 GRUB 配置被覆盖启动参数丢了重启后名字全变。第三留一份物理口到逻辑名的映射表。这份表不要只存在某个人脑子里写进 CMDB、写进机柜标签、写进/etc/motd。将来现场拔插网线的人不看文档也知道哪个是wan0。第四升级前做一次验证。内核大版本升级、systemd 大版本升级之后.link文件和NAME规则的行为都可能有细微变化。升级前先在一台机器上验证udevadm test-builtin net_setup_link的输出比升级后发现名字变了再从备份里找配置要轻松得多。我个人在几年维护下来最深的体会是网卡改名的价值不在于名字本身好看而在于它把物理拓扑这个事实固化进了系统。系统里名字稳定了配置才能稳定脚本才能稳定人的判断才能稳定。反过来只要名字还会漂移你的所有自动化都建立在流沙上早晚会塌一次——而且往往是在你最不希望它塌的时候。最后再分享一个小习惯每次做完改名我都会把那三行采集命令的输出和生成的规则文件一起打包存到这台机器之外的备份点上。真出事的时候回滚到默认命名只需要删文件加一次重启前提是你知道原来的名字是什么。