1. 为什么离线升级内核这件事值得单独拿出来讲
但凡在生产环境里摸过几年服务器的人,大概率都遇到过这种场景:一批机器跑在内网隔离区,没有外网出口,连个像样的软件源都连不上,但偏偏业务侧提了个需求,说某个新上线的组件要求内核版本必须到某个基线以上,否则驱动加载不了、某个特性开不了、甚至安全扫描过不了。这时候你面对的就不是“能不能升级”的问题,而是“在完全没有网络的情况下怎么把内核换掉并且保证机器还能正常起来”的问题。
Linux离线升级内核,说白了就是在目标机器无法访问任何在线软件仓库的前提下,把新版本内核的安装包、依赖包、固件包全部提前准备好,通过可移动介质或者内网文件服务传到目标机器上,再用本地包管理的方式完成安装、引导配置和重启验证。它解决的核心问题是:在封闭网络环境中,让操作系统的核心组件完成版本迭代,同时不破坏现有业务和引导链。
这件事适合谁看?我大致分三类:第一类是负责内网服务器运维的工程师,手里动辄几十上百台隔离机器,升级内核是家常便饭;第二类是做私有化交付的实施人员,客户现场往往没有外网,交付环境的内核基线又必须满足产品要求;第三类是自己折腾HomeLab或者离线开发环境的技术爱好者,想搞清楚内核升级到底动了哪些东西。不管你是哪一类,下面这套流程和踩坑经验都能直接拿去用。
我先把话说在前面:离线升级内核本身不复杂,复杂的是依赖闭环和引导回退这两件事。在线升级时包管理器帮你自动解决依赖,离线时这个责任就落到你头上了;在线升级失败了大不了重装,离线环境里引导一旦写坏,机器可能直接起不来,而你又没有网络去救。所以这篇文章的重点不在于“敲哪几条命令”,而在于“怎么把风险控制住”。
2. 升级前的整体设计与方案选型
2.1 先搞清楚你的发行版和包管理体系
离线升级内核的第一步不是去下载内核包,而是确认目标机器到底是什么发行版、用什么包管理器、当前内核是什么版本。这一步看起来废话,但我见过太多人上来就找rpm包,结果目标机器是Debian系,白忙活一场。
主流发行版大致分两个阵营:RedHat系(含CentOS、Rocky、Alma、Fedora等)用rpm/dnf/yum体系,内核包通常叫kernel、kernel-core、kernel-modules这类;Debian系(含Ubuntu、Debian等)用deb/apt/dpkg体系,内核包叫linux-image-xxx、linux-headers-xxx、linux-modules-xxx。这两套体系的离线操作方法完全不同,包格式、依赖表达、引导配置工具都不一样,所以第一步必须确认清楚。
确认命令很简单:
cat /etc/os-release uname -r第一条看发行版标识,第二条看当前运行的内核版本。注意uname -r显示的是当前正在运行的内核,不一定等于已安装的最新内核。如果之前装过新内核但没重启,这两者会不一致,用rpm -qa | grep kernel或者dpkg -l | grep linux-image能看到实际安装了哪些。
2.2 离线升级的三种典型路径
在实际操作中,离线升级内核主要有三条路,各有适用场景:
路径一:同发行版同大版本内的内核小版本升级。比如从某个5.x内核升到同发行版提供的更高5.x内核。这种最简单,依赖关系基本不变,直接拿官方仓库里的新内核包及其依赖即可。
路径二:跨大版本升级内核。比如发行版本身没换,但想用更新的主线内核或者第三方编译的内核。这种要特别注意模块依赖、固件包、以及引导配置的兼容性。
路径三:使用发行版官方提供的离线仓库镜像。把整个仓库的元数据和包同步到本地,然后在目标机器上配置本地源。这种方式最稳妥,因为依赖关系由包管理器自己算,但代价是需要同步的数据量大。
我个人的建议是:如果只是升级内核,优先走路径一,用官方仓库的包,依赖闭环最可控。路径三适合批量机器统一升级的场景,一次性把本地源搭好,后面所有机器都受益。路径二风险最高,除非有明确需求,否则不建议在生产环境轻易尝试。
2.3 为什么强烈建议保留旧内核
这是我要反复强调的一点:离线升级内核时,绝对不要卸载旧内核。在线环境下你卸载旧内核出问题了还能重装,离线环境下旧内核就是你最后的救命稻草。
Linux的引导加载器(GRUB)支持同时存在多个内核条目,启动时可以选择进入哪一个。只要旧内核还在,新内核起不来你还能回退。一旦把旧内核删了,新内核又有问题,那这台机器就只能进救援模式或者重装了——而离线环境里救援本身也是个大麻烦。
所以整个方案设计的核心原则是:新增而非替换。安装新内核,保留旧内核,验证新内核没问题之后,再考虑是否清理旧内核(而且我建议至少保留一个旧版本作为回退)。
3. 核心细节解析与实操要点
3.1 依赖闭环是怎么算出来的
离线升级最容易翻车的地方就是依赖。在线时你敲一条安装命令,包管理器自动去仓库里找依赖、算版本、下载安装。离线时这些依赖必须你提前准备好,少一个包,安装就中断。
RedHat系用rpm的话,可以用rpm -qpR查看一个包的依赖:
rpm -qpR kernel-core-xxx.rpm但rpm的依赖表达比较粗,实际安装时dnf/yum会做更复杂的解析。更靠谱的做法是找一台同发行版同版本、能联网的机器,用dnf download把内核及其所有依赖下载下来:
dnf download --resolve --alldeps kernel kernel-core kernel-modules--resolve会自动解析依赖,--alldeps会把所有相关依赖都拉下来。下载完成后你会得到一个包含所有rpm包的目录,把这个目录整体拷到离线机器上,用dnf localinstall或者rpm -ivh安装。
Debian系类似,用apt-get install --download-only把包下载到缓存目录:
apt-get install --download-only linux-image-xxx linux-headers-xxx下载的deb包在/var/cache/apt/archives/下,拷过去之后用dpkg -i安装。但要注意,dpkg不会自动解决依赖,如果依赖不全安装会失败,所以更推荐用apt-get install ./xxx.deb的方式,让apt在本地解析依赖。
提示:下载依赖的机器必须和目标机器发行版版本、架构完全一致。x86_64和aarch64的包不能混用,不同小版本的依赖关系也可能有差异。
3.2 固件包和模块包别漏了
很多人只盯着kernel主包,忽略了kernel-modules和linux-firmware这类包。结果新内核装上了,重启之后发现网卡不工作、磁盘认不出来,因为对应的驱动模块或者固件没装。
RedHat系里,kernel-modules包含大量驱动模块,kernel-modules-extra包含一些不常用的模块,linux-firmware包含硬件固件。这些包在离线升级时都要一并准备。Debian系里对应的是linux-modules-xxx和linux-firmware。
判断需不需要某个模块包,可以看目标机器的硬件情况。如果机器用的是比较新的网卡或者存储控制器,那模块包和固件包基本是必须的。稳妥起见,把kernel、kernel-core、kernel-modules、kernel-modules-extra、linux-firmware这一整套都下下来。
3.3 引导配置的自动与手动
安装新内核时,包管理器通常会自动调用引导配置工具更新GRUB配置。RedHat系安装kernel包后会触发grubby更新/boot/grub2/grub.cfg,Debian系会触发update-grub。正常情况下你不需要手动干预。
但离线环境里有两个坑:第一,如果/boot分区空间不足,新内核的vmlinuz和initramfs写不进去,安装会失败或者引导配置更新不完整;第二,如果引导配置工具依赖的某些组件缺失,自动更新可能静默失败。
所以安装完成后,一定要手动检查引导配置:
# RedHat系 grubby --info=ALL # 或者直接看配置文件 grep -E "menuentry|linux16|linux " /boot/grub2/grub.cfg # Debian系 grep -E "menuentry|linux " /boot/grub/grub.cfg确认新内核的条目确实写进去了,而且排在旧内核前面(或者至少能被选中)。
3.4 initramfs的生成
initramfs是内核启动时加载的临时根文件系统,里面包含了挂载真实根文件系统所需的驱动和脚本。新内核安装后必须生成对应的initramfs,否则启动时会因为找不到根设备而失败。
RedHat系用dracut生成,Debian系用update-initramfs。这些通常在安装内核包时自动触发,但离线环境下如果相关工具缺失或者配置有问题,可能不会自动执行。手动生成的命令:
# RedHat系,为新内核生成initramfs dracut -f /boot/initramfs-<新内核版本>.img <新内核版本> # Debian系 update-initramfs -c -k <新内核版本>注意:生成initramfs时如果提示缺少某些模块,说明对应的驱动包没装全,这时候不要强行继续,先把模块包补齐再重新生成。
4. 完整实操流程与关键环节
4.1 环境信息采集与包准备
假设我们有一台内网隔离的RedHat系服务器,当前内核版本是4.18.0-xxx,需要升级到发行版仓库里更新的4.18.0-yyy。整个流程分两大阶段:联网机器上准备包,离线机器上安装。
第一阶段:在联网机器上准备离线包。
找一台和目标机器发行版版本、架构完全一致的联网机器,执行:
# 创建存放目录 mkdir -p /tmp/kernel-offline cd /tmp/kernel-offline # 下载内核及依赖 dnf download --resolve --alldeps \ kernel kernel-core kernel-modules kernel-modules-extra linux-firmware # 查看下载了哪些包 ls -lh下载完成后,你会看到一堆rpm包。把它们打包成一个tar文件方便传输:
tar czvf kernel-offline.tar.gz *.rpm如果目标机器是Debian系,操作类似:
mkdir -p /tmp/kernel-offline cd /tmp/kernel-offline apt-get install --download-only -y \ linux-image-<版本> linux-headers-<版本> linux-modules-<版本> linux-firmware cp /var/cache/apt/archives/*.deb /tmp/kernel-offline/ tar czvf kernel-offline.tar.gz *.deb4.2 传输与校验
把打包好的文件通过可移动介质或者内网文件服务传到目标机器。传输完成后一定要校验完整性,离线环境里文件损坏是很常见的问题:
# 生成校验值(在源机器上) sha256sum kernel-offline.tar.gz > kernel-offline.sha256 # 在目标机器上校验 sha256sum -c kernel-offline.sha256校验通过后再解压:
tar xzvf kernel-offline.tar.gz -C /tmp/kernel-offline/4.3 安装前的空间检查
安装内核前必须确认/boot分区有足够空间。一个新内核的vmlinuz加initramfs通常需要100MB到200MB,如果/boot只剩几十MB,安装必然失败。
df -h /boot如果空间不足,先清理旧的initramfs文件(保留旧内核的vmlinuz,只删对应的initramfs,需要时还能重新生成),或者扩容/boot。我一般建议/boot至少留500MB以上,给多内核共存留余地。
4.4 执行安装
RedHat系用dnf本地安装:
cd /tmp/kernel-offline dnf localinstall -y *.rpm如果dnf不可用,用rpm直接装:
rpm -ivh *.rpm注意用-ivh而不是-Uvh,-i是安装新包,-U是升级替换。我们要保留旧内核,所以用-i。
Debian系:
cd /tmp/kernel-offline dpkg -i *.deb # 如果有依赖问题,用下面这条修复 apt-get install -f -y安装过程中留意输出信息,特别是关于initramfs生成和引导配置更新的部分。如果看到报错,先停下来排查,不要急着重启。
4.5 安装后的验证清单
安装完成后,重启之前,逐项检查:
| 检查项 | 命令 | 预期结果 |
|---|---|---|
| 新内核包已安装 | rpm -qa | grep kernel或dpkg -l | grep linux-image | 能看到新版本 |
| 新内核文件存在 | ls /boot/vmlinuz-* | 包含新版本文件 |
| initramfs已生成 | ls /boot/initramfs-*或ls /boot/initrd-* | 包含新版本文件 |
| 引导条目已更新 | grubby --info=ALL或grep menuentry /boot/grub/grub.cfg | 新内核条目存在 |
| 旧内核仍保留 | 同上 | 旧内核条目仍在 |
| /boot空间充足 | df -h /boot | 有剩余空间 |
这份清单每一项都要过一遍,尤其是引导条目和旧内核保留这两项。我见过有人装完新内核直接重启,结果引导配置没更新,重启还是进旧内核,白折腾。
4.6 重启与验证
确认无误后重启:
reboot重启过程中如果机器有带外管理(比如IPMI、iDRAC、iLO这类),盯着控制台看启动过程。如果没有带外管理,那就只能等它起来。启动时在GRUB菜单里确认选中的是新内核(通常新内核会排在默认位置)。
起来之后验证:
uname -r确认输出的是新内核版本。然后检查关键硬件是否正常:
# 网卡 ip link show # 磁盘 lsblk # 内核模块加载情况 lsmod | head -20 # 系统日志有无异常 dmesg | grep -i error如果一切正常,升级就算完成了。如果新内核有问题,重启时在GRUB菜单里选旧内核进入,然后排查问题。
5. 常见问题与排查技巧实录
5.1 重启后还是旧内核
这是最常见的问题,原因通常是引导配置没更新,或者GRUB默认启动项指向旧内核。排查思路:
先确认新内核的引导条目是否存在。如果不存在,说明安装时引导配置更新失败,需要手动更新:
# RedHat系 grub2-mkconfig -o /boot/grub2/grub.cfg # 或者用grubby手动添加 grubby --add-kernel=/boot/vmlinuz-<新版本> --initrd=/boot/initramfs-<新版本>.img \ --title="新内核" --copy-default # Debian系 update-grub如果条目存在但默认启动的是旧内核,调整默认启动项:
# RedHat系,设置默认启动新内核 grubby --set-default=/boot/vmlinuz-<新版本> # Debian系,修改/etc/default/grub里的GRUB_DEFAULT # 然后update-grub5.2 启动卡在initramfs或者找不到根设备
这种通常是initramfs生成不完整,缺少挂载根文件系统所需的驱动。解决办法是进救援模式或者用旧内核启动,重新生成initramfs:
# RedHat系 dracut -f --regenerate-all # 或者针对特定内核 dracut -f /boot/initramfs-<新版本>.img <新版本> # Debian系 update-initramfs -u -k <新版本>如果重新生成还是不行,检查是不是缺少存储控制器的驱动模块包,把对应的kernel-modules或者linux-modules包补上再重新生成。
5.3 网卡或其他硬件不工作
新内核起来了但网卡不工作,大概率是固件包或者模块包没装全。检查:
# 看内核有没有识别到硬件 lspci | grep -i ethernet # 看模块有没有加载 lsmod | grep <驱动名> # 看dmesg有没有固件加载失败的提示 dmesg | grep -i firmware如果提示固件加载失败,把linux-firmware包补装,然后重新生成initramfs并重启。
5.4 依赖缺失导致安装中断
离线安装时如果提示某个依赖包找不到,说明准备阶段漏了包。解决办法是回到联网机器,用dnf download --resolve把缺失的包补下来。如果反复出现依赖问题,建议改用本地仓库的方式:把整个仓库的元数据同步下来,配置本地源,让包管理器自己解析依赖。
5.5 常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 重启后仍是旧内核 | 引导配置未更新 | 手动更新GRUB配置,设置默认启动项 |
| 启动卡住找不到根设备 | initramfs不完整 | 重新生成initramfs,补齐模块包 |
| 网卡不工作 | 固件或模块缺失 | 补装linux-firmware和模块包 |
| 安装报依赖错误 | 离线包不全 | 联网机器补下依赖包 |
| /boot空间不足 | 分区太小 | 清理旧initramfs或扩容 |
| 新内核panic | 内核与硬件不兼容 | 回退旧内核,换其他版本 |
5.6 几个我踩过的坑
第一个坑是架构不匹配。有次在x86_64机器上准备包,结果目标机器是aarch64,包根本装不上。后来养成习惯,准备包之前先uname -m确认架构。
第二个坑是发行版小版本差异。同样是某个发行版,小版本不同,内核包的依赖关系可能不一样。有次在A版本机器上下的包,拿到B版本机器上装,依赖解析失败。后来统一要求准备包和装包的机器版本完全一致。
第三个坑是忘了保留旧内核。早期图省事用-U升级,把旧内核替换掉了,结果新内核有问题,机器起不来,只能进救援模式折腾了半天。从那以后一律用-i安装,保留旧内核。
第四个坑是没检查/boot空间。有次/boot只剩50MB,新内核装到一半空间满了,initramfs生成失败,引导配置也没更新完整。重启后直接进了GRUB救援提示符。后来每次升级前必查/boot空间。
6. 批量离线升级的组织方式
如果手里不是一台机器,而是几十上百台内网机器需要统一升级内核,单台手工操作就不现实了。这时候需要一套批量方案。
6.1 搭建内网本地源
最省事的方式是在内网找一台机器做本地仓库。把联网机器上下载的所有包放到这台机器的某个目录下,用createrepo生成仓库元数据:
# 安装createrepo dnf install -y createrepo_c # 在仓库目录下生成元数据 cd /path/to/repo createrepo_c .然后在目标机器上配置本地源:
# /etc/yum.repos.d/local.repo [local-kernel] name=Local Kernel Repo baseurl=file:///path/to/repo enabled=1 gpgcheck=0配置好之后,目标机器就能像用在线源一样用本地源了,依赖解析交给dnf,省心很多。
6.2 用配置管理工具批量执行
如果内网有配置管理工具(比如Ansible、SaltStack这类),可以写一个playbook批量执行升级。核心步骤就是:分发本地源配置、执行安装、检查结果、按批次重启。
批量升级的关键是分批。不要一次性把所有机器都重启,先拿几台非核心机器试,确认没问题再扩大范围。每批之间留观察时间,一旦发现问题立即停止后续批次。
6.3 升级窗口和回退预案
批量升级一定要选业务低峰期,并且提前准备好回退预案。回退预案的核心就是:旧内核还在,引导配置里旧内核条目还在,出问题能通过带外管理切换启动项。
如果机器没有带外管理,那批量升级的风险会高很多,因为一旦新内核起不来,你没法远程切换启动项。这种情况下建议先给关键机器配上带外管理,或者安排现场人员值守。
7. 一些收尾的经验之谈
离线升级内核这件事,技术难度其实不高,难的是流程的严谨性。在线环境下你可以试错,离线环境下试错成本很高。所以我的习惯是:准备阶段宁可多下几个包,也不要少下;安装阶段宁可多检查几遍,也不要急着重启;验证阶段宁可多观察一会儿,也不要草率收工。
另外,内核升级完之后,建议把这次用到的离线包和操作步骤归档保存。下次再遇到类似需求,直接复用,不用从头再来。我自己的归档里会记录:目标发行版版本、架构、原内核版本、新内核版本、用到的包列表、操作步骤、遇到的问题和解决办法。这份记录在后来几次升级里帮了大忙。
最后分享一个小技巧:如果目标机器数量不多,又不想折腾本地源,可以把所有rpm包放在一个目录里,用dnf localinstall /path/to/*.rpm一次性安装。dnf会在这个目录范围内解析依赖,比逐个rpm -ivh省事得多。前提是依赖包都在这个目录里,所以准备阶段一定要用--resolve --alldeps把依赖下全。