这个需求如果是在内网环境里处理,那确实是个高频问题。很多跑着旧系统的机器,因为安全合规或者新硬件兼容性的原因,必须升级内核,但偏偏连不上外网源,不能直接yum update。我把自己在一台 CentOS 7.9.2009 上做离线升级的完整过程、踩过的坑和原理都整理在下面了,从选包、传包、安装到处理启动项和驱动问题,一条线走完。
1. 为什么这台 CentOS 7.9.2009 必须手动离线升级内核
先说背景。CentOS 7.x 系列默认自带的 3.10.0 内核,年代确实比较久了。它虽然稳定,但遇到三类情况就明显吃力:一是新出的网卡、NVMe 固态、GPU 直通这类硬件,2.6/3.10 里没有驱动;二是跑容器和 Kubernetes 时,像 overlay2 存储驱动、防火墙规则、网络性能这些内核特性跟不上;三是安全扫描总报内核漏洞,生产环境又改不了,只能靠升级内核打补丁。
我这次处理的机器就在某公司内网,系统版本是CentOS Linux release 7.9.2009 (Core),内核还是3.10.0-1160.el7.x86_64,需要升级到 5.x 系列。这台机器没有外网访问权限,内网也没有配置本地 Yum 源镜像,所以完全不能走在线安装那条路。更麻烦的是,这台机器上还跑着业务,不能随便重启,也不能因为装了一半缺依赖就回滚不了。所以必须做到“包一次传全”“一次装干净”“启动项可回退”。
升级之前先自我确认一下需求:到底是不是必须升级?如果只是为了解决某个特定模块的 bug,先看看能不能通过更新kernel-3.10.0-1160.el7的补丁版本解决,而不是直接跳到 5.x。但这次的目标很明确:新硬件驱动不识别,老内核解决不了,升级是唯一路径。
2. 离线环境下的内核包获取与版本选型逻辑
离线环境最尴尬的不是怎么装,而是怎么拿到正确的包。网上很多教程直接让你yum install kernel-ml,在内网机器上回车就是Could not resolve host,这条路直接断掉。所以核心思路是:在一台可以联网、且同样是 CentOS 7.9 的机器上把 rpm 包全部下载好,再通过内网传输手段拷贝到目标机器。
2.1 ELRepo 源的配置与包列表解析
CentOS 7 默认的 base 源里内核版本只到 3.10.0,想要 5.x 主线内核,最靠谱的是用 ELRepo 源。ELRepo 是专门为 RHEL/CentOS 提供较新内核和驱动模块的第三方仓库,它的 kernel 包分两种:
kernel-ml:Mainline Stable,主线稳定版,更新频繁,内核大版本迭代快,比如 5.15、6.1 这类。kernel-lt:Long Term Support,长期维护版,更新节奏慢,更偏向稳定生产环境使用。
实操中我优先选kernel-ml,因为这台机器需要识别较新的硬件,lt 版本在某些最新硬件的支持上会滞后。如果对稳定性要求极其严苛、不想频繁跳大版本,可以用kernel-lt,逻辑完全一样。
在联网机器上先装 ELRepo 的 GPG key 和仓库配置:
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-4.el7.elrepo.noarch.rpm装好之后先查看可用的内核版本,确认我们要下载的包名和依赖关系:
yum --enablerepo=elrepo-kernel list available | grep kernel-ml输出里通常会有这样几个包:
kernel-ml.x86_64:主内核包kernel-ml-devel.x86_64:内核头文件和开发库,编译第三方驱动模块时必需kernel-ml-tools.x86_64:内核工具集kernel-ml-tools-libs.x86_64:工具依赖库
有些教程只让你下载一个kernel-ml就完事,这对纯跑业务的普通场景也许够用,但一旦你后面要编译网卡驱动或装某个需要内核头文件的软件,就会发现kernel-ml-devel缺了根本进行不下去。所以这次我把主包和 devel 包一次性全下载。
2.2 用 yumdownloader 完整拉取 rpm 包及其依赖
直接下载 rpm 用yumdownloader或者repotrack都行。区别在于,yumdownloader默认只下载你指定名字的包,依赖需要手动再用--resolve参数一起拉;repotrack会把所有依赖关系自动分析并下载完整闭环,更适合离线安装场景。我这次用的是yumdownloader:
yum install -y yum-utils mkdir -p /root/kernel-rpms cd /root/kernel-rpms yumdownloader --enablerepo=elrepo-kernel --resolve kernel-ml kernel-ml-devel--resolve参数很关键,它会自动把依赖包也下载进来。内核包常见的依赖有kernel-ml-tools-libs、elfutils-libelf等,这些在目标机器上不一定有,缺一个 rpm 安装就会报错。如果你不放心,可以再执行一次:
repotrack --enablerepo=elrepo-kernel -a x86_64 kernel-ml这个命令会把kernel-ml相关的所有架构、所有依赖包全部拉下来,放在当前目录里。两者可以结合使用:先用repotrack拉完整列表,再用yumdownloader查缺补漏。最终目录里应该是干净的几个 rpm 文件,不要混入大量无关包。
下载完顺便做一下完整性校验:
rpm -K *.rpm这一步会显示每个包的 GPG 签名是否有效,防止 rpm 包被破坏或来源不可信。我看到全部是OK才进入下一步。
2.3 跨机器传输的三种方式
包拿到了,怎么传到目标机器?内网环境千差万别,我这边提供三种方式,按实际情况选:
- 如果目标机器有内网 IP,且和操作机在同一网段,直接用
scp传最快。 - 如果中间隔着跳板机或者安全域,先传到跳板机指定目录,再用内网文件摆渡工具(比如某公司的文件安全交换平台)推过去。
- 如果机器数量多、路径统一,把 rpm 放在内网共享目录或 FTP 服务器上,目标机器自行拉取,避免一台一台操作。
我自己习惯把包名、版本号写进一个文本清单里,顺便记录 SHA256 值。这样传到内网后如果出现大小不一致、rpm 损坏的情况,第一时间就能比对,不用靠猜。
3. 离线安装与依赖冲突的完整处理链路
这一步是整篇文章的重头戏。rpm 包的安装方式看着简单,实际执行时经常遇到依赖不足、版本冲突、架构不对这几类问题,每一样都可能导致升级中断。
3.1 目标机器安装前的环境检查与快照准备
动手之前,目标机器一定要做好三件事:
第一,确认当前磁盘空间。内核包解压后会在/boot下写入vmlinuz、initramfs文件,/usr/src/kernels下会生成内核源码目录,加起来可能占用 1-2GB。检查一下:
df -h /boot /usr/src第二,查看当前内核版本和启动项顺序,方便后面核对:
uname -r grub2-editenv list第三,如果机器是虚拟机,强烈建议先在虚拟化平台上做一次快照;如果是物理机,确认备份工具可用。内核升级本来就可以回滚,但回滚的前提是旧内核还在,如果安装过程异常导致/boot损坏,没有备份就会很被动。我之前遇到过一次停电导致/boot分区文件损坏的情况,最后靠备份恢复,从那之后再也不敢跳过这一步。
如果机器上有第三方内核模块(比如 NVIDIA 驱动、某厂商的多路径软件),需要先记录它们的版本,因为内核升级后这些模块通常要重新编译。检查命令:
lsmod | grep -E "nvidia|drbd|multipath"3.2 rpm -ivh 还是 rpm -Uvh?这个选择影响回滚策略
安装命令有讲究。大多数教程用rpm -Uvh kernel-ml*.rpm,U表示升级,会替换旧内核并安装新内核。但在 CentOS 7 上,内核 rpm 的机制比较特殊:即使你用-U,它默认也会保留旧内核的引导项,不会卸载旧内核。这是因为内核包的%post脚本会调用grub2-mkconfig重新生成引导配置,把新旧内核都写进去。
不过我还是更推荐用-ivh安装新内核,而不是-Uvh:
cd /root/kernel-rpms rpm -ivh kernel-ml-*.rpm kernel-ml-devel-*.rpm原因很简单:-i是纯安装,不触发“升级”语义,不会对既有内核做任何替换动作,回滚逻辑更干净。-U也不是不行,但如果你同时传了多个内核版本包,它可能会按顺序处理,处理完的旧包状态不好预估。分开装、按需装,是离线手动升级的基本素养。
如果遇到依赖缺失报错,比如提示libxxx.so.1()(64bit) is needed,不要急着加--nodeps。先记录缺什么,在之前的 rpm 下载目录里找对应包,单独安装。只有在确定缺失的只是一些无足轻重的辅助库、且手动验证过不影响内核启动时,才可以用:
rpm -ivh --nodeps kernel-ml-*.rpm--nodeps是一把双刃剑。内核主包本身依赖很少,跳过依赖大概率能装上,但若缺少的是elfutils-libelf这类与模块压缩有关的库,后续用dracut生成 initramfs 时就会出现诡异问题。所以尽量别用。
3.3 安装完成后的引导文件核对与内核模块确认
安装完成后,不要急着重启。先确认两件事:
第一,/boot下是否生成了对应 5.x 版本的vmlinuz和initramfs文件:
ls -l /boot/vmlinuz-* /boot/initramfs-*第二,内核模块目录是否存在:
ls /usr/lib/modules/正常会看到旧的3.10.0-1160.el7.x86_64和新的5.15.x-xxx.el7.x86_64两个目录。只有模块目录完整生成,新内核启动时才能加载各类驱动,否则就算引导成功了,起不来网络、识别不了磁盘,照样凉凉。
还有一点很容易漏:kernel-ml-devel安装后会在/usr/src/kernels下生成一个源码树。如果你后面需要编译第三方模块,这个目录必须留着。别为了省空间去删它,删了就后悔。
4. 引导顺序调整与旧内核回滚机制
这一步没做好,重启后机器可能直接起不来,或者起来还是旧内核,白忙活。CentOS 7 用 GRUB2 引导,内核选择逻辑由/boot/grub2/grub.cfg和grub2-editenv共同决定。
4.1 默认启动内核的切换命令与细节
安装完成后,查看当前默认启动项:
grub2-editenv list输出里会出现一行类似:
saved_entry=CentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core)这说明默认启动的还是旧内核。把它切换成新内核:
grub2-set-default 00表示 grub.cfg 中第一个 menuentry,但这里有一个坑:新装的内核通常排在第一个,但如果你机器上有多套系统或之前手动改过 grub,顺序不固定。最稳妥的做法是先列出所有启动项及顺序:
awk -F\' '/menuentry / {print $2}' /boot/grub2/grub.cfg找到形如CentOS Linux (5.15.x-xxx.el7.x86_64) 7 (Core)的那一行,记下它前面的编号,然后:
grub2-set-default "CentOS Linux (5.15.x-xxx.el7.x86_64) 7 (Core)"可以用名称精确指定,比序号靠谱。执行完再确认:
grub2-editenv list注意:grub2-set-default修改的是saved_entry,它会成为下次启动时的默认项。但我遇到过一种情况:执行后saved_entry改了,重启后居然还是旧内核。原因是/etc/default/grub里如果设置了GRUB_DEFAULT=saved,且/boot/grub2/grub.cfg里没有正确包含新内核的 menuentry,grub 会退回到第一个可用项。所以重启前一定要检查 grub.cfg 里是否真的写入了新内核项:
grep "5.15" /boot/grub2/grub.cfg如果没有,需要手动执行一次:
grub2-mkconfig -o /boot/grub2/grub.cfg这一步会把新内核、新 initramfs 完整写进引导配置。不执行的话,前面全白搭。
4.2 重启验证流程与意外中断时的应急处理
切换到新内核后重启:
sync reboot重启过程中注意观察 GRUB 界面,看默认选中的是不是新内核,是否出现类似error: file not found的提示。如果一切正常,系统会依次加载内核、解压 initramfs、挂载根文件系统,最后进入登录界面。等系统起来后立刻验证:
uname -r看到5.15.x-xxx.el7.x86_64就说明升级成功。但这只是第一步,接着要验证关键服务是否正常:
systemctl status network systemctl status sshd以及磁盘和网络是否正常识别:
lsblk ip addr如果重启后直接卡在 GRUB 界面或者内核 panic,不用慌。重启机器,在 GRUB 菜单里快速按方向键,选择旧内核那个 menuentry,回车即可回到原来的 3.10.0 内核。旧内核并没有被移除,所以理论上不会丢失系统。
如果连 GRUB 菜单都看不到,最可能的原因是/boot/grub2/grub.cfg损坏。这种情况物理机需要用系统安装光盘或救援模式修复,虚拟机则可以直接挂载 ISO 进 rescue。这里也再次说明为什么安装前备份和确认 grub.cfg 如此重要。
5. 升级后必踩的驱动与性能适配坑
内核版本跳了接近两个大版本,硬件驱动和系统性能参数的兼容性一定会受影响。这一节我专门讲升级后碰到的实际问题,提前给大家打预防针。
5.1 第三方内核模块重新编译流程
如果你的机器装过 NVIDIA GPU 驱动、某厂商的存储多路径软件、或者专门的网卡驱动,升级内核后大概率会失效。因为这类驱动通常是 out-of-tree 模块,依赖内核源码树编译,内核版本一变,模块就得重编。
以最常见的 NVIDIA 驱动为例,升级前先把驱动安装包准备好(比如NVIDIA-Linux-x86_64-xxx.run),升级后用新内核重新安装一次:
sh NVIDIA-Linux-x86_64-xxx.run --kernel-source-path=/usr/src/kernels/$(uname -r)/整个过程需要gcc、make、kernel-devel配合。如果你装内核时用了--nodeps导致kernel-devel没装上,或者装的是旧版 devel,编译就会报各种找不到头文件的错误。这也是我前面反复强调一定要装kernel-ml-devel的原因。
验证模块是否加载成功:
lsmod | grep nvidia如果模块列表里没有,说明新内核没有自动加载驱动,业务依赖 GPU 的应用会直接报错。
5.2 内核参数与业务进程兼容性调整
从 3.10 升到 5.15,一些内核默认参数变了。比如:
fs.suid_dumpable的默认值net.ipv4.tcp_congestion_control默认算法vm.swappiness的行为- 部分 cgroup 版本的默认挂载方式
像我们用 Docker 的老机器,升级内核后如果发现容器起不来,查一下 cgroup 版本:
mount | grep cgroup5.x 内核默认开了 cgroup v2,而 Docker 20.10 之前的版本对 v2 支持不完整,可能需要改/etc/default/grub里的GRUB_CMDLINE_LINUX,追加systemd.unified_cgroup_hierarchy=0强制回退到 v1,重新生成 grub.cfg 并重启。这一点很关键,我在一台跑着旧版 Docker 的机器上踩过,容器全部起不来,日志全是 cgroup 相关的报错。
另外就是性能监控工具和内核版本匹配的问题。比如某些版本的perf、bpftrace对内核版本敏感,升级后需要同步更新工具,或者直接用内核源码树自带的工具。
5.3 模块缺失时的 udev 规则排查思路
如果网卡在旧内核下是eth0,新内核下变成ens160,或者干脆识别不到,多半是 udev 规则和固件缺失的问题。排查分三步:
第一,看硬件是否被识别:
lspci | grep -i ethernet第二,看对应驱动模块是否存在:
modprobe -l | grep <驱动名>第三,如果驱动模块不存在,检查kernel-ml是否包含了该硬件的驱动。Intel 和 Realtek 的主流网卡基本都内置了,但某些小众网卡只提供旧版驱动,新内核反而没有。这种情况只能去厂商官网找新版驱动源码,用dkms或手动编译安装。
另外不要忽略固件文件。内核 5.x 对部分网卡、显卡要求/lib/firmware下有对应固件,缺了会报failed to load firmware。这个包一般叫linux-firmware,在离线环境下记得也从联网机器拉下来一起装。
yumdownloader --resolve linux-firmware rpm -Uvh linux-firmware-*.rpm一个小细节:linux-firmware是独立于内核的包,装它不会影响当前内核,但会影响新内核的硬件支持范围。
6. 从这次升级到后续维护的运维建议
内核升级不是一次性的动作,它只是一个开始。升级完成后,后续的维护还需要注意几件事。
6.1 新旧内核共存期的日常操作纪律
升级后新旧内核会共存一段时间。默认启动新内核,但旧内核的 menuentry 保留,以便出现问题时能回滚。这个共存期建议至少保持一两周,等业务稳定跑过一轮高峰、验证完所有驱动和服务,再考虑清理旧内核。
清理旧内核时用yum remove而不是手动rm:
yum remove kernel-3.10.0-1160.el7.x86_64yum remove会同步更新 grub.cfg 和/boot下的文件。手动删除容易出现残留配置或 grub 指向不存在文件的问题。
不过要注意:别把旧内核全删光,至少留一个能引导的内核。我见过有人为了省磁盘空间把新旧内核都删了,重启后 GRUB 找不到内核,系统直接挂掉,最后只能拿安装盘救援。
6.2 离线环境后续内核小版本升级的路径规划
这次内核升级是从 3.10 跳到 5.15,跨度很大。以后再做小版本升级(比如 5.15.x 到 5.15.y),建议不要再用rpm -ivh,而是用rpm -Uvh,因为同大版本内的升级,新内核会完全替换旧内核,模块和头文件更容易保持一致。
同样,离线环境下小版本升级也走“联网机器下载 rpm → 传输 → 安装”这条路。下载时用repotrack拉全依赖,装完后执行:
grub2-mkconfig -o /boot/grub2/grub.cfg grub2-set-default "<新内核名称>"这两条命令是离线升级里最稳妥的组合,可以形成固定操作清单,交给其他人执行也不会出错。
6.3 如何把这次升级过程沉淀为团队可复用文档
我从这次操作里沉淀出的结论是:离线升级内核真正难的不是敲命令,而是对依赖关系的梳理、对回滚方案的预演、对驱动兼容性的测试。建议把整个流程写成可复用的检查清单,至少包含以下项目:
- 目标机器当前内核版本
- 需要升级到哪个版本(kernel-ml 还是 kernel-lt)
- 联网机器上已经准备好哪些 rpm 包,SHA256 是否校验
- 目标机器的磁盘空间、grub 启动项、是否存在第三方模块
- 安装后的引导文件核对结果
- 重启后的内核版本、服务状态、硬件识别情况
- 回滚步骤和旧内核保留策略
有了这份清单,下一次就算换一个人操作,也能按图索骥,不用重新踩一遍坑。
我在实际执行中还习惯把每一步的日志用script命令记录留存:
script /var/log/kernel-upgrade-$(date +%F).log这样升级完可以回溯每一步的操作和输出,出现问题时能快速定位到底哪一步出了问题。
这次升级最终顺利把机器跑在了 5.15 内核上,新网卡驱动识别正常,容器服务恢复,安全扫描的内核漏洞项也清零了。整个过程最深的体会是:离线环境不可怕,可怕的是准备工作不充分。把依赖想全、把回滚想好、把驱动问题留在重启前解决,升级这件事其实是个体力活,不是技术活。以后如果你们的内网机器也要做同样操作,欢迎随时交流具体细节。