凌晨两点,一台跑着业务数据的 CentOS 7.9 服务器,云厂商刚打完内核安全补丁,网络就断了。SSH 连不上,控制台看着网卡状态是 up,但 ifconfig 一片黑——没有 eth0,没有 ens192,什么都没有。这不是测试环境。你的第一反应可能是重启网络服务、检查 ONBOOT,但都没用:内核换了,网卡模块没跟上。

那个让你摸不着头脑的“全黑了”时刻

最常见的情况就三种,而且实际中经常叠加出现。第一,新内核的 initramfs 里压根没带网卡驱动。比如你用 yum update kernel 升级了内核,但没触发 dracut 重新生成 initramfs。或者 dracut 按默认配置跑了,但没把对应的 .ko 模块打包进去。启动时内核加载了,却找不到网卡设备的驱动,直接跳过——系统起来了,网络没了。

第二,GRUB 默认引导了旧内核。你明明升级了,但 /boot/grub2/grub.cfg 里默认项还是旧内核。机器重启后,旧内核加载了旧驱动,网络暂时正常——但你以为升级成功了。等下次意外重启或者手动选错内核,新内核一启动,网络立刻掉线。这个坑比上面那个更隐蔽,因为它不立刻发作。

第三,驱动路径或符号链接错误。某些云定制内核(比如 Anolis OS 的 ANCK 内核)会把网卡模块放在非标准路径下,而 dracut 默认只扫描 /lib/modules/$(uname -r)/kernel/drivers/net,找不到就漏了。更坑的是部分厂商的内核包卸载旧内核时,把 /lib/modules/ 下旧版本的符号链接连带删了,导致模块加载时路径直接断了。之前帮朋友处理过一个阿里云 ECS 的实例:Anolis OS 8.6 从 4.19.91 升级到 5.10.134,重启后网络丢失,控制台用 VNC 进去发现连 e1000e 驱动都没加载。一查,/lib/modules/5.10.134-xxx-xxx/kernel/drivers/net/ethernet/intel/e1000e/ 这个目录压根不存在——新内核没装完整的 kernel-devel 包,驱动模块没编译进去。

别急着把服务器挂维修牌。只要还能摸到键盘,或者能通过控制台看到串口输出,你就有机会把它从“失联”拉回来。关键是先用一个还能动的内核启动,再想办法让网络先通一口气。

VNC rescue mode server console

救命流程:从黑屏到能连上线的三件事

先让机器开口说话:VNC/救援模式进系统

云厂商的控制台不只是开关机。多数公有云都提供 VNC 或带外管理,相当于直接连到显示器;有的还提供“救援模式”,会把根挂成只读,尽量让你能修。遇到升级后 SSH 彻底不见,第一件事是别在已经起不来的新内核上死磕。

  • 阿里云 ECS:控制台 → 实例详情 → 远程连接 → 使用 VNC;按 Ctrl+Alt+Del 重启,趁自检时打断 GRUB。
  • 腾讯云 CVM:实例右侧“登录”→“VNC 登录”;同样在引导界面按 e 编辑。
  • 华为云 ECS:操作列“远程登录”→“VNC 登录”;注意快捷键提示。
  • AWS EC2:实例页面 → Connect → Session Manager;没有 VNC,但能用串口终端。
  • Azure VM:支持 Boot Diagnostics 串口输出,可配合 Serial Console。

进不去桌面也别慌,目标只是拿到一行可输入的命令。你不需要鼠标,也不需要图形界面。

GRUB 里挑个能用的旧内核,先把机器跑起来

开机停在 GRUB 菜单时,别回车,按 e 进入编辑。找到 kernel 那行,确认是不是新内核导致网卡驱动缺席。如果默认就是新内核,手动改回旧版本,比如 5.10.134-xxx-xxx 这种。改完按 Ctrl+X 或 F10 启动。

ls -l /boot/vmlinuz* | grep -E '(4.19|5.10)'
uname -r
cat /etc/default/grub

能进系统后,立刻确认两件事:一是当前确实跑在旧内核,二是 network 服务还活着。

临时把网络顶起来:加载模块 + 静态 IP + 落盘

有些发行版在新内核里没把 e1000e、ixgbe 这类常见驱动打进 initramfs,结果就是启动后 lsmod | grep -i eth 空空如也。先用旧内核顶着,再把缺失的模块手工装上,顺手给一个静态 IP,确保你能重新连上。

modprobe e1000e
ip link set ens192 up
ip addr add 10.0.1.10/24 dev ens192
ip route add default via 10.0.1.1
echo 'MODPROBE_LOAD="e1000e"' > /etc/rc.local
chmod +x /etc/rc.local

RHEL/CentOS/Anolis 系还可以顺手补一条 Dracut 记录,免得下次切回新内核又抓瞎。

dracut -f --kver $(uname -r) --add "e1000e"

做完这些,reboot 一次,再用新内核启动。网络要是还在,就说明问题出在 initramfs 缺模块;要是仍旧掉线,就把排查重点放到 dmesg 和 firmware 版本上。急救完成,下一步才是怎么把坑填平。

Linux terminal dracut rebuild initramfs

重建 initramfs:dracut 与 mkinitrd 实战

旧内核跑着只是喘口气,你总得切回新内核过日子。刚才我提到补了一条 dracut -f --add "e1000e",但说实话,这条命令在 CentOS 7 / Anolis 7 上不一定能生效——因为它们的 initramfs 工具还是 mkinitrd,dracut 是新版 RHEL 8/9 才默认的。你得先确认自己的发行版在用哪个家伙。看一眼,哪个存在就用哪个。都装了?优先用 dracut,它更可控。

检查新内核到底缺了什么

别急着重建,先看看当前新内核的 initramfs 里到底有没有网卡驱动。上机器后切到新内核(如果旧内核里已经能跑网络,那直接用新内核启动,网络挂了再切回来查)。

lsinitrd /boot/initramfs-$(uname -r).img | grep -iE "e1000|ixgbe|bnx2|tg3"

没有输出?那就对了,缺失的模块就是它。如果你连新内核都进不去系统,只能在急救模式里挂载 /boot 分区后手动解包 initramfs 看:

mkdir /tmp/initramfs-check && cd /tmp/initramfs-check
xzcat /boot/initramfs-5.10.134-xxxx.img | cpio -id
find . -name "*.ko" | grep -i net

跑完之后记得把临时目录删掉,别给自己留垃圾。

dracut 重建:带参数就一次搞定

如果你用 dracut,最稳妥的写法不是裸 dracut -f,而是明确告诉它你要哪个内核版本、要包含哪些驱动模块:

dracut -f --kver 5.10.134-xxxx.an8.x86_64 --add-drivers "e1000e ixgbe tg3" /boot/initramfs-5.10.134-xxxx.an8.x86_64.img

--add-drivers 会把指定模块强制塞进 initramfs,哪怕 dracut 自动检测没发现它们。这个参数比 --add 精准,--add 是添加一个 dracut 模块(比如 network),而 --add-drivers 直接操作内核模块列表。RHEL 7 / CentOS 7 的 mkinitrd 没那么灵活,它本质是对 dracut 的封装。你可以在 /etc/dracut.conf.d/ 下新建一个文件:

add_drivers+=" e1000e ixgbe tg3 "

然后执行 mkinitrd。mkinitrd 会自动读取配置,结果和 dracut 一样。

验证:别相信感觉,看输出

重建完别急着 reboot,先拿 lsinitrd 确认模块确实进去了:

lsinitrd /boot/initramfs-5.10.134-xxxx.an8.x86_64.img | grep -iE "e1000|ixgbe"

输出类似 -rw-r--r-- 1 root root 12345 Jun 1 12:34 usr/lib/modules/5.10.134-xxxx/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko 这样才算成功。还有一个小心思:有些云厂商的定制内核把网卡驱动编译成了 built-in(ls /lib/modules/$(uname -r)/kernel/drivers/net/ethernet/ 下找不到对应 .ko 文件),这种情况你往 initramfs 里塞模块也没用。怎么确认?

cat /boot/config-$(uname -r) | grep CONFIG_E1000E

如果输出 CONFIG_E1000E=y,那驱动已经编进内核了,initramfs 不需要加。如果输出 =m,那就是模块形式,必须加。模块确认后,reboot 切到新内核,网络还在就说明坑填平了。如果还掉线,别怀疑 initramfs 了,去看 dmesg | grep firmware 或者网卡固件版本。最后提醒一句:dracut -f 不加内核版本号默认重建当前运行内核的 initramfs,如果你在旧内核里执行这条命令,它只会重建旧内核的 initramfs,新内核的照样缺模块。很多人踩过这个坑。我踩过,而且不止一次。

GRUB 引导修复:确保新内核正确加载驱动

内核和 initramfs 都准备好了,临门一脚却在 GRUB。很多人升级完一重启,发现默认还是旧内核,或者新内核里网卡驱动没被加载。别急,这里有几个实战里救过我的招。

把“不该自动加载”的驱动拉黑

有些环境下,系统会错误地把不匹配的网络驱动塞进 initramfs,结果早期启动就把设备“抢”走,后续内核自带的正确驱动反而认不到卡。遇到这种情况,在 /etc/default/grub 里加一行:

GRUB_RDLOAD_BLACKLIST=e1000e,ixgbe,tg3

改完记得重跑 grub2-mkconfig,再重启。这样 early udev 阶段就不会去加载这些模块,让内核在后期用正确的方式接管。

给 initramfs 生成器“指条明路”

如果你用的是 dracut,可以在 /etc/dracut.conf.d/ 下放一个文件,比如 01-net.conf,内容很简单:

add_drivers+=" e1000e ixgbe tg3 "

然后重建对应内核版本的 initramfs,例如:

dracut -f /boot/initramfs-$(uname -r).img $(uname -r)

要点有两个:一是路径里要有内核版本,二是确认当前运行的就是目标内核,否则容易“白忙活”。我也干过错把旧内核的 initramfs 重建一遍,结果切过去照样没网的事。

重启之后,到底谁说了算?

机器起来后,看一眼 dmesg | grep -i net,你会看到类似:

[    2.389123] e1000e: Intel(R) PRO/1000 Network Driver
[    2.389157] e1000e: Copyright(c) Intel Corporation.

如果上面还带着 firmware: 的报错,那是固件层面的问题,不在 GRUB 这章范围;如果是模块被 blacklisted 或者找不到设备,多半是 initramfs 里的驱动冲突。把这个输出留好,下次回滚或提工单都是证据。说到底,内核升级不是“装完即安”,每一步都要确认“谁在何时加载了谁”。GRUB 这一下,决定的是整套网络栈的起点。

防患未然:DKMS 与驱动备份

上一章聊的是重启后的急救,这一章咱们聊聊怎么在升级前就把坑填上。我这人最怕的不是升级过程中出问题——出问题不可怕,可怕的是你明知道它会出问题,手里却没预案。

DKMS:让驱动自己跟着内核走

第三方网卡驱动——Realtek 的 r8169、Mellanox 的 mlx4_core,你懂的——每次内核升级都得手动重编一遍模块,这活儿干过的人都在骂娘。DKMS 就是来治这个病的,全称 Dynamic Kernel Module Support。逻辑很简单:你把驱动注册进去,下次新内核装上的时候,它自动帮你重新编译一遍,完事直接丢进 /lib/modules/ 里。说白了就是个“内核级自动跟班”。拿 CentOS 或 Anolis OS 来举个例子吧:

yum install -y dkms
# 假设你已经编译过 e1000e 源码,且源码在 /usr/src/e1000e-3.8.4 下
dkms add /usr/src/e1000e-3.8.4
dkms build e1000e/3.8.4
dkms install e1000e/3.8.4

加完之后,可以用 dkms status 确认模块状态。我见过有人只加了没安装,结果升级完内核发现驱动目录下空空如也——白忙一场。dkms status 会告诉你当前内核版本下的模块是否 built 和 installed,缺一不可。

备份驱动目录:最笨但最稳的办法

DKMS 不是万能的。有些私有驱动的安装脚本根本不注册到 DKMS,甚至有的只认固定内核版本号。这时候,手动备份是最直接的。网络驱动一般躺在 /lib/modules/$(uname -r)/kernel/drivers/net 下,但这个路径下面还有 eth、bonding、team、phy 等等子目录。我建议连整个 kernel/drivers/net 一起打包:

tar czf ~/net_drivers_backup_$(uname -r).tar.gz \
    /lib/modules/$(uname -r)/kernel/drivers/net

备份完扔到 /root 或者挂载的持久化存储上。有个细节:系统里可能同时存在多个内核版本,备份时一定带版本号,否则事后你根本分不清这是哪一版的驱动。我还见过更狠的做法——有人直接 tar 了整个 /lib/modules/,然后在新内核下解压覆盖。这招风险极高,因为不同内核的模块 ABI 不兼容,硬覆盖可能导致系统 panic。别学这种。

升级前检查脚本:自动化该做的事

手动操作多了就会漏。我习惯写个小脚本,每次内核升级前跑一遍:

#!/bin/bash
KERNEL_VER=$(uname -r)
BACKUP_DIR="/root/driver_backup"
mkdir -p $BACKUP_DIR
# 备份网络驱动
tar czf $BACKUP_DIR/net_drivers_${KERNEL_VER}.tar.gz \
    /lib/modules/${KERNEL_VER}/kernel/drivers/net
# 记录当前模块加载状态
lsmod | grep -E "net|eth|bond" > $BACKUP_DIR/lsmod_${KERNEL_VER}.txt
# 重建当前内核的 initramfs(以防下次抢救要用)
dracut -f /boot/initramfs-${KERNEL_VER}.img ${KERNEL_VER}
echo "Done for ${KERNEL_VER}"

这个脚本干三件事:备份驱动、记录当前加载了哪些网络模块、重建当前内核的 initramfs。最后一步是为了防止你在旧内核上修东西时 initramfs 损坏。脚本跑完,再去执行升级。如果升级后网络挂了,你可以用这个备份在新内核下手动 insmod 对应模块,或者干脆切回旧内核先用着。有人问:备份 lsmod 有什么用?我告诉你,很有用。升级后如果 dmesg 报“module not found”,你可以对照备份文件,看看到底少了哪个模块。比瞎猜快得多。

说到底,预防不是“希望不出事”,而是“出事了我有路可退”。备份驱动 + DKMS 双管齐下,基本能挡住九成以上的网络驱动丢失问题。剩下那一成,留给下一章聊的应急方案去兜底。

前面聊了预防和脚本,但真到了生产环境,还是会遇到“人算不如天算”的时刻。我那次在 Anolis OS 上做内核升级,就是因为漏了一件事——没把当前内核的 initramfs 也备份一份——结果重启后网卡死活起不来,SSH 直接失联。

一次生产环境内核升级的教训

从“能连”到“彻底断”只差一次重启

当时升级完新内核,系统提示要重启才能生效。我习惯性地先去确认网络服务,却发现 ifconfig 只剩一个 lo 接口。dmesg 里一堆关于 e1000e 的报错,说模块找不到。那一刻我才意识到,新内核的 initramfs 里并没有包含旧的网络驱动,而我又没给旧内核留备用。

救援模式是唯一的救命稻草

先绕进云厂商的救援模式,把系统盘挂载到 /mnt,然后 chroot 进去。上来第一件事:lsmod > /mnt/tmp/lsmod.save,把原来系统里装的模块列表先倒腾出来,不然过会儿新内核一加载,你根本不知道之前 e1000e 驱动到底有没有在跑。然后手动 insmod e1000e.ko,这步不报错才算过。最后 depmod -a 重新生成依赖,还得补一记 dracut -f /boot/initramfs-$(uname -r).img。不跑这步?下次重启照样 panic,网络驱动照样找不着。整个流程就是给新内核递话:嘿,这几个 .ko 别忘了带。

别让“以为不会发生”变成事故

这次事故让我彻底明白一件事——你以为稳如老狗的“内核升级”,翻起车来连个招呼都不打。两个小时的全面停摆,追根溯源,就是忘了重建那个不起眼的 initramfs。现在每次做内核升级,我最后一步一定加一条:“手动确认网络驱动已经正常加载”,谁都别想跳过。