去年有一批内网服务器被安全扫描标记,原因是内核版本过旧,某个已知漏洞需要升级内核才能封堵。问题在于这批机器都在隔离网段,不能连外网yum源,也不允许为了升级内核就顺手做一次全量系统更新——那会把生产环境改到不可控。当时网上查了很多资料,大部分教程只写了yum update kernel或者源码编译,搬过来根本走不通。最后我选的路子,就是标题里写的这种:提前把内核rpm包准备好,拷进内网,用rpm本地更新的方式升级CentOS内核。这篇就把整个过程,包括选型理由、操作细节和踩坑教训,完整记录下来,给同样在离线环境里折腾内核的运维一个可落地的参考。
我默认看这篇文章的读者已经有一台CentOS机器需要动内核,知道rpm、yum的基本用法,但可能没系统梳理过"本地更新内核"这条路到底怎么走、哪些地方容易翻车。下面所有步骤都是我在实际环境里验证过的,按顺序来就行。
1. 为什么偏要选rpm本地更新这条路
1.1 三种常见内核升级方式的取舍
拿到"升级内核"这个需求的时候,大多数人的第一反应是直接yum update kernel。这种方式本身没有错,它会自动解析依赖、自动生成引导项、自动更新grub菜单,体验确实顺畅。但它有个硬前提:机器必须能访问可用的yum源。而且yum update kernel在操作时往往会把与kernel关联的其他软件包一并带起来,比如kernel-tools、systemd的某些组件,升级面比预期大很多。对于"我只想换内核、其余一律不动"的运维场景,这条路径的副作用偏大。
源码编译是另一条路。下载内核源码、make menuconfig配置、make -j编译、make modules_install、make install,一套下来时间成本很高,对机器上的编译工具链也有要求。更麻烦的是回退逻辑:源码安装的新内核会覆盖到/boot和/lib/modules,一旦新内核出了问题,你没有一个rpm记录可以快速删除,只能手动清理文件、手动修grub,生产环境里这是很大的负担。
rpm本地更新恰好站在两者中间。它不要求目标机器有外网,只要求你手里有对应的内核rpm包;它不会自动把无关软件一起升级,变更面非常小;它由rpm包管理器统一记录,装了什么、删了什么一清二楚,出了问题可以像卸普通软件一样把内核卸掉,回退路径做得比别人干净得多。
1.2 本地方案的适用边界:什么情况下该选它
从我自己的实践看,rpm本地更新并不适合所有场景,它有明确的适用区间。最典型的情况就是内网隔离环境——机器上不了外网,yum源不可用,但安全通告又催着必须换内核。这个时候,提前在外网机器上把内核rpm包下载下来,拷贝到目标机器上用rpm安装,几乎是唯一兼顾效率和可控性的方式。
还有一种场景是版本锁定。有些业务软件对内核版本有隐式依赖,比如某个数据库或者某个硬件驱动只在本版本内核上验证过,贸然升级到最新内核会带来不必要的兼容性风险。rpm本地更新允许你精确指定某个内核版本安装,而不是被动接受源里的最新版。
如果你的机器明明有完整外网源、也没有版本锁定要求,那直接用yum update kernel就好,没必要绕一圈做本地安装。本地方案最大的价值在于"离线"和"精确"两个词,缺一个它的优势就不明显了。
2. 动手前先摸清楚现状:环境确认与内核包准备
2.1 确认系统引导模式和分区空间
很多人刚拿到机器就急着下载内核包,结果装到一半才发现/boot分区满了或者引导模式判断错误。我的习惯是先花五分钟把机器现状摸一遍,命令不多,但每一条都是在为后面的操作排雷。
# 当前运行的内核版本 uname -r # 系统发行版版本 cat /etc/redhat-release # 已安装的内核包一览 rpm -qa kernel # /boot 分区剩余空间 df -h /boot # 判断是UEFI还是BIOS启动 [ -d /sys/firmware/efi ] && echo UEFI || echo BIOSuname -r告诉你现在跑在哪个内核上,rpm -qa kernel告诉你机器上一共装了哪些内核。后面做回退方案时,这两条信息直接决定了你要保留什么、可以删除什么。df -h /boot这条很多人容易漏,后面我会专门讲为什么它那么重要,这里先记住一个原则:/boot里每多装一个内核,大约要占用 100MB 到 200MB 的空间,分区太小的话第二个内核还没装完就没地方了。
UEFI和BIOS的差异主要体现在grub配置文件的路径上。BIOS机器grub配置文件在/boot/grub2/grub.cfg,UEFI机器一般在/boot/efi/EFI/centos/grub.cfg(发行版不同目录名会有差异)。这个差异不影响rpm包的安装,但影响你后面运行grub2-mkconfig时输出的目标路径,所以现在确认清楚能省不少事。
2.2 内核rpm包从哪里获取
明确要装的内核版本之后,下一步是准备rpm包。最稳妥的做法是在一台能联网、且CentOS版本和目标机一致的机器上,用yumdownloader把内核包连同依赖一起拉下来。
# 安装yum-utils工具包 yum install -y yum-utils # 建立存放目录 mkdir -p /tmp/kernel-rpms # 下载内核及其配套包(--resolve会自动拉取依赖) yumdownloader --resolve --destdir=/tmp/kernel-rpms kernel kernel-devel kernel-headers如果打算从社区维护的第三方内核仓库获取新版本内核,逻辑也差不多:先在联网机器上安装该仓库rpm包,再用yumdownloader下载对应内核包。这类仓库通常会提供"长期维护版"和"主线最新版"两个系列,生产环境我一般偏向长期维护版本,它稳定性更好、补丁周期长,不至于装上之后没多久就要再次升级。
需要注意一点:kernel、kernel-devel、kernel-headers三个rpm包的版本号必须一致,否则后续编译第三方模块时头文件版本对不上,会报各种奇怪错误。如果你只是想单纯换内核、不打算在新内核上编译任何外部模块,kernel-devel可以不要;但我仍然建议把它一起拷过去,多一个文件不影响什么,真到要用的时候你会发现它无比重要。
2.3 离线依赖梳理:别只搬一个kernel文件回家
rpm工具的设计哲学和yum不同:yum会自动解析并补齐依赖,rpm -ivh遇到缺依赖直接报错退出。这意味着离线环境下,你必须在源机器上就把依赖带全。这里有一个我在实践中碰到的教训:最初我以为内核包只需要kernel一个rpm文件,结果在目标机器上rpm -ivh时报出缺elfutils-libelf的依赖,整个安装流程卡住。后来回到联网机器上重新用yumdownloader --resolve拉完整套文件,才把事情理顺。
# 查看下载目录里的文件清单 ls -lh /tmp/kernel-rpms/拷贝进内网机器的手段就因地制宜了,能走跳板机scp最方便,不能走就U盘。本质上这就是"本地安装"的字面含义——所有安装动作都基于机器上已经存在的rpm文件,不依赖任何网络通道。如果你需要批量更新多台机器,同一个目录的rpm文件可以直接复制到每台机器上反复使用,这也是本地方案在批量场景下的隐藏红利。
3. 安装与引导变更的完整操作:每一步都讲清楚为什么
3.1 包安装顺序与依赖处理
把所有rpm包拷贝到目标机器,进入存放目录,执行安装。我通常在CentOS 7上这样操作:
cd /tmp/kernel-rpms # 一次传入多个rpm,让rpm一次性解析它们的共同依赖 rpm -ivh kernel-*.rpm这里的kernel-*.rpm会匹配到kernel、kernel-devel、kernel-headers等所有以kernel开头的包。一次传入多个rpm和逐条执行的区别在于:rpm会在安装前统一分析所有这些包的依赖关系,而不是装完一个再单独看下一个,能减少一部分"装完A才意识到B互相冲突"的情况。
如果执行过程中报依赖缺失,不要拿到--nodeps就硬上。--nodeps会让rpm跳过依赖检查,但缺失的依赖会在系统里埋雷——轻则某个工具运行异常,重则以后每次执行yum操作都会报同样的依赖错误。正确做法是记录下报错信息里的缺失包名,回到联网机器上用yumdownloader --resolve下载对应包,再拷贝过来安装。
在某些离线环境里,还有一个更顺滑的变体:把准备好的所有rpm文件放到一个目录,用createrepo把这个目录做成一个本地文件源,然后写一个repo文件指过去,再用yum localinstall -y kernel-*.rpm完成安装。这种方式的好处是yum仍然在工作,依赖解析和安装日志都由yum统一管理,比裸rpm -ivh的可视化更好。缺点是你需要额外安装createrepo工具并且多几步配置。如果机器数量少、只是临时装一次内核,直接用rpm -ivh就够了,没必要为一次性操作搭建本地源。
安装完成后,立刻检查内核文件是否落位:
# 查看/boot下的新内核文件 ls -lh /boot/vmlinuz-* # 确认新内核出现在rpm列表中 rpm -qa kernel正常情况下,/boot目录下会多出一整套新版本文件:vmlinuz-新版本、initramfs-新版本.img、System.map-新版本、config-新版本。其中initramfs是安装rpm包时自动生成的,它负责在启动阶段加载根文件系统所需的模块,不需要我们手动干预。如果你发现新内核文件都在但缺了initramfs,那就要警觉,说明安装过程中某一步没走完整,后面我会讲怎么补救。
3.2 引导加载器配置:不改默认启动项等于白装
这是整个升级流程里最容易翻车的地方。原因是很多人想当然地以为"内核rpm装好了,重启自然就进新内核了"。实际完全不是这样。CentOS的多内核机制里,新内核只是被加入到了grub菜单中,系统当前的默认启动项仍然是旧内核。不改默认项,重启一百次都还是老内核。
首先要让grub识别到新内核的菜单项:
# BIOS引导的机器 grub2-mkconfig -o /boot/grub2/grub.cfg # UEFI引导的机器(输出路径根据具体发行版目录调整) grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg这条命令会扫描/boot里的所有内核文件,重新生成完整的grub启动菜单。执行完之后,新内核的菜单项已经存在了,但系统默认项还没变。接下来用grubby设置新内核为默认启动项:
# 查看当前默认内核 grubby --default-kernel # 查看所有启动菜单项 grubby --info=ALL # 将默认启动项设置为新内核 grubby --set-default /boot/vmlinuz-新内核版本号 # 再次确认默认项是否切换成功 grubby --default-kernel在CentOS 7上,grub2-set-default也是可用的方案,但grubby在CentOS 7、8、9上都能通用,用一套命令就能覆盖不同版本的环境,我自己更习惯用它。关键判断点是最后一步:grubby --default-kernel输出的路径必须是你刚安装的新内核,否则说明这一步操作没有生效,需要回头检查内核路径是否写对。
这里补充一句,为什么要手动改默认项、为什么发行版不帮我们自动切:因为有相当多运维场景要求"装好内核但暂时不重启",开发者会把默认项留在旧内核上,等业务低峰期再手动切过去。这种设计本身是给运维留余地的,但也意味着如果不主动修改默认项,升级就是无效的。
3.3 重启与归属验证:确保你已经跑在新内核上
确认默认启动项已经指向新内核之后,就可以安排重启了。重启前我习惯再做一次快速检查:grubby --default-kernel的输出、/boot里的新内核文件都在、磁盘空间足够,三个条件满足就执行reboot。
重启回来后,验证动作不多但必须做:
# 确认实际运行的内核版本 uname -r # 查看本次启动的时间 uptime -s # 检查已加载的内核模块数量是否正常 lsmod | wc -l # 查看启动日志里有没有明显报错 dmesg | grep -iE "error|fail" | head -20uname -r是最直接的验证,输出必须等于你安装的新内核版本号。如果发现重启后还在旧内核上,基本可以断定是上一步默认启动项没设置成功,不用慌,重新执行grubby --set-default再重启一次就好。
这里我要多提醒一句:如果操作的是远程服务器,重启之前务必确认机器有带外管理通道或者可以进机房。因为新内核可能导致某个驱动没有正常加载,网络接口甚至可能起不来,一旦 ping 不通,你唯一能做的就是去物理控制台处理。这个场景我经历过,结论是在远程机器上动内核之前,评估"如果网络掉了怎么办"比评估"新内核有多快"重要一百倍。
4. 回退兜底设计:给生产环境留一条保命的后路
4.1 为什么同时保留两个内核
rpm包安装新内核并不会覆盖旧内核,系统会同时保留新旧两套内核文件,grub菜单里也会同时出现两个入口。这不是bug,是Linux发行版的刻意设计——多内核共存是系统稳定性的最后底线。
我在处理生产机器时,从不在升级当天删除旧内核。至少要等新内核跑几天、确认业务无明显异常、驱动加载无告警之后,再考虑清理。保留多个内核的成本不过是/boot里几百MB空间和grub菜单里多一行项,这点代价换一次失败的保险,太划算了。
你可以用下面命令确认当前系统和已安装内核的状态:
# 列出已安装的内核 rpm -qa kernel # 查看当前运行的内核 uname -r # 查看当前默认启动项 grubby --default-kernel对比这三个输出,你能清楚看到"当前运行的"和"默认启动项"分别指向哪个版本。正常情况它们应该一致;如果发现不一致,说明之前设置默认项时操作有误,要及时修正。
4.2 新内核启动异常时的回退操作
新内核出问题分不同严重程度,回退方式也分三种情况。
第一种:系统能进、命令行能敲、但业务跑不起来,或者某些驱动报错。这种情况最友好,直接把默认启动项切回旧内核重启即可:
grubby --set-default /boot/vmlinuz-旧内核版本号 # 确认已切回 grubby --default-kernel # 重启进入旧内核 reboot第二种:新内核启动过程中直接卡住,根本进不了系统。这时重启机器,在grub菜单出现时用方向键选择旧内核的菜单项回车启动。进入系统后,按第一种情况里的方式把默认项改回旧内核,避免下一次开机又自动往新内核里撞。
第三种:grub菜单本身都异常了,比如升级过程中grub配置写坏、启动项全丢。这种情况最麻烦,常见思路是进入故障救援模式,或者用启动介质引导后再修复grub配置。处理逻辑是先恢复一个可用的系统入口,再重新生成grub菜单。这种场景不太常见,但如果你负责的机器很重要,最好提前在测试机上演练一遍救援介质的使用流程,真到用的时候不至于手忙脚乱。
4.3 稳定运行后的旧内核清理时机与操作
新内核稳定运行几天后,清理就提上日程了。清理的目的是释放/boot空间、减少grub菜单里的杂质项。我的习惯是保留当前运行的新内核和一个最旧的备用内核,中间那些历史版本一次性清掉。
# 列出所有已安装的内核 rpm -qa kernel # 以rpm命令删除指定旧内核 rpm -e kernel-旧内核版本号 # 用yum删除也可以 yum remove -y kernel-旧内核版本号删除时务必仔细核对版本号,我见过有人手滑把当前运行的内核也删了,导致系统虽然还在运行,但rpm库里已没有对应内核记录,下次更新或重装组件时各种连环依赖错误。稳妥起见,删除前先跑一次uname -r确认当前内核,然后和rpm -qa kernel的输出比对,只删那些既不是当前运行、也不是默认启动项的旧版本。
清理完旧内核后,重新执行一次grub2-mkconfig让菜单跟实际文件保持一致。这一步可以避免grub菜单里残留指向已删除内核的灰色启动项。
5. 实操中真正容易踩的坑与自救思路
5.1 /boot分区爆满:最容易翻车的环境问题
前面我反复提到/boot空间不是无限的,因为这是我在实际环境里被绊倒过的点。某台机器原本已经装了三个内核,我下载好新内核包后直接rpm -ivh,结果在写入阶段报no space left on device。一看df -h /boot,分区已经占满。
处理这类问题没有捷径,就是先腾空间再装。把最旧最没用的内核删掉一两个,释放出空间,然后重新执行安装命令。这个坑虽然不难解决,但它发生的时机非常尴尬——通常是在你满心以为马上要完成的时候,所以我把检查/boot空间提到流程最开始,就是希望你不要在最后一步被它卡住。
5.2 驱动模块与新内核匹配:网络掉线的检查思路
一个让人记忆深刻的案例是:升级内核后某台机器的网卡驱动直接失效,网络掉线,因为是远程操作,我只能让现场同事去物理控制台处理。事后排查发现,那台机器用的是某硬件厂商提供的闭源网卡驱动模块,它并没有随新内核自动编译适配。
这个问题的预防思路其实不难:升级前先用lspci -k看看网卡、存储卡这些关键硬件当前用的是哪家驱动,确认是内核自带模块还是厂商闭源模块。升级后进入新内核,再用lsmod检查对应模块是否加载成功:
# 查看内核模块是否加载 lsmod | grep 模块名 # 查看具体硬件走的驱动 lspci -k | grep -A2 -i ethernet如果发现模块确实没有加载,就要考虑找厂商要适配新内核版本的驱动包重新安装,或者通过dkms机制在新内核上重新编译模块。这块工作最好在升级前就评估好,而不是先升级再发现问题。
5.3 依赖冲突与本地源绕行方案
离线环境下rpm -ivh报依赖错误是家常便饭。常见的是某个.so动态库文件缺失,或者是某个包对版本有硬性要求。这时候不要硬着头皮忽略依赖,最体面的解法是回到联网机器上,用yumdownloader --resolve把报错里提示的依赖包拉下来,按依赖链逐个装好,再回来装内核。
如果机器数量多、以后还要反复装包,更推荐做一个本地文件源:把所有rpm放进一个目录,用createrepo生成索引,写一个repo文件指向该目录,之后所有离线安装都可以走yum localinstall。这个方案的前期工作量稍大,但后期省心不是一点半点。
还有一个容易忽略的坑是版本锁。有些系统管理员为了防止意外升级,在yum里配置了版本锁定,把kernel包固定在某个版本号上。这种情况下即使rpm包就摆在你面前,yum localinstall也可能被版本锁拦截。排查时可以用yum versionlock list看一眼,有锁就先解除,装完再重新锁上。
5.4 特殊引导环境带来的隐藏问题
最后说两个相对低频、但遇到了会很头疼的问题。
第一个是UEFI环境下开启了Secure Boot。如果新内核rpm包没有在机器信任密钥里注册,启动时会被固件拒绝。处理办法是用mokutil导入对应公钥,或者在确认安全的前提下暂时关闭Secure Boot。这方面不同的主板、不同的发行版表现差异很大,处理时建议以硬件厂商的文档为准,不要照搬通用步骤。
第二个是根文件系统使用了LVM或磁盘加密的机器。正常情况下rpm包安装内核时会自动生成对应的initramfs,但特殊存储配置偶尔会让initramfs不包含必要的持久化模块,导致新内核启动时找不到根设备。如果你怀疑是这种情况,可以手动重新生成当前运行内核的initramfs:
dracut -f /boot/initramfs-$(uname -r).img $(uname -r)我处理这类问题时的经验是:先用文档记录、再用测试机复现,最后才动生产机器。尤其是涉及Secure Boot、磁盘加密这类和环境强相关的特性,生产环境和测试环境不一致,往往就是踩坑的根源。
这批机器后来都顺利完成了内核切换。我个人的习惯是,内网环境升级内核,心思花在"留后路"上比花在"装得快"上有价值得多。每次动内核前,我都会确认三件事:rpm包版本清晰、默认启动项改对了、旧内核还留着。只要能随时回退,剩下的故障都是可接受的。如果你也正好在离线环境里折腾CentOS内核,希望这篇能让你少走几个弯路。