最近在帮一台生产服务器做内核升级,环境比较特殊:CentOS 7.9.2009,纯内网环境,不连外网,不能挂代理,没有本地 yum 源,一切都得手动来。折腾了一整天,踩了几个坑,最后顺利从 3.10 内核升到了 5.x。写这篇文章,把整个离线手动升级流程、版本选型思路、依赖处理方法和坑位排查经验完整记录下来,给同样被困在内网机房里的兄弟们一块垫脚石。
先交代一下背景,这台机器装的是 CentOS 7.9.2009,也就是 7 系列最后一个版本,内核停在 3.10.0-1160,这是 RHEL/CentOS 7 的老传统——内核版本极度保守。为什么非升不可?因为要在这台机器上跑一套新的容器调度平台,3.10 内核在 cgroup v2、网络命名空间、overlayfs 的稳定性上确实有点跟不上,加上新硬件的驱动也要新内核支持。所以升级内核不是赶时髦,而是业务驱动。
整个过程可以分成四大块:版本选型与准备工作、离线安装包的获取与校验、核心升级操作(含 GRUB2 调整)、升级后的验证与问题排查。下面按这个顺序展开。
1. 离线升级前的版本选型与准备工作
1.1 为什么要先想清楚升到哪个版本
很多人一上来就yum update,或者随便找一个内核 rpm 就往系统里装,这在离线环境下是很容易翻车的。离线环境跟在线环境最大的区别是:装错了或者装到一半缺依赖,你是没法临时再拉包的。每一步都得提前想清楚。
内核选型主要考虑三个因素:
第一,业务兼容性。如果你的机器是普通 web 服务器、数据库服务器,不需要太激进的内核特性,那选长期支持版本更稳。如果是要跑 Docker/K8s,或者上新的存储方案,那就得选相对新、功能全的内核分支。
第二,硬件驱动支持。新内核对新网卡、新 RAID 卡、NVMe 设备的支持肯定更好,但反过来,如果你的机器是老旧服务器,某些厂商的闭源驱动跟不上新内核版本,升完内核网卡反而掉线。这个一定要先在测试环境验证。
第三,虚拟化环境特殊要求。比如跑在云平台、虚拟化平台上的机器,升级内核后必须确认虚拟化相关驱动(比如 virtio 系列)正常加载,不然重启直接失联。
以我这次为例,业务上需要稳定优先,同时又要支持容器特性,所以我选了长期支持版本系列(对应 ELRepo 的 kernel-lt),而不是最新主线版本(kernel-ml)。主线和 LT 差异很大:主线版本更新、功能激进,但生命周期短,可能几周就更新一个版本;LT 版本更稳、维护周期长,适合生产环境长期跑。
注意:CentOS 7 默认仓库里的内核是不升级的,需要用第三方仓库(如 ELRepo)提供的内核包。离线环境下就先把 rpm 包准备好再说。
1.2 查当前内核版本和系统发行版信息
在动手之前,先把底细摸清楚,执行下面几条命令:
cat /etc/centos-release uname -r uname -m我的环境输出是:
CentOS Linux release 7.9.2009 (Core) 3.10.0-1160.el7.x86_64 x86_64这三个信息缺一不可:系统大版本决定 rpm 包要选 el7 的;当前内核版本用于升级前后对比;架构确定你下载 x86_64 还是 aarch64 的包。
另外建议同步检查一下/boot分区空间:
df -h /boot内核 rpm 安装时会往 /boot 写入 vmlinuz、initramfs 等文件,每个内核大约占 30~60MB。如果 /boot 空间太小,多个内核并存会直接撑爆分区,这也是老 CentOS 7 机器很容易踩的雷。
1.3 确认升级目标与回滚策略
在离线环境下建议采用“保留旧内核、新增新内核”的策略,不要先卸载旧内核再装新的。这样就算新内核启动失败,重启后还能从 GRUB 菜单里选回旧内核,安全兜底。
我在实际操作中对 /boot 里的内核文件做了一个完整的备份清单:
ls -l /boot/vmlinuz-* ls -l /boot/initramfs-*并确认当前使用的内核模块目录存在:
ls /lib/modules/3.10.0-1160.el7.x86_64/这一步看似多余,但在后面删旧内核时非常有用,至少知道原始环境长什么样。
2. 离线安装包的获取与完整性校验
2.1 在联网机器上下载全部内核 rpm 包
离线环境不是没有电脑,而是服务器不能联网。我们可以找一台能联网的同架构机器(比如办公电脑、跳板机)先把调和好依赖的内核 rpm 包全部下载下来,再拷贝到内网服务器上安装。
这里推荐用 yumdownloader 的--resolve参数,它会把指定包及其所有依赖一次性拉下来。命令如下(在联网机器上执行):
# 先安装 yum-utils yum install -y yum-utils # 启用 ELRepo 源 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 # 下载 kernel-lt 及其所有依赖到指定目录 mkdir -p /data/kernel-rpms cd /data/kernel-rpms yumdownloader --resolve kernel-lt kernel-lt-devel kernel-lt-headers这里有个细节:kernel-lt-devel是内核头文件和开发包,如果你只是单纯跑步业务,其实可以不装;但后面如果要编译第三方内核模块(比如某个网卡驱动、安全 agent 的 kernel module),就必须装。我这次装了,防止后续临时要用却没有包。
如果你选择主线版本,把包名换成 kernel-ml 就行:
yumdownloader --resolve kernel-ml kernel-ml-devel kernel-ml-headers下载完成后,检查目录内容:
ls -lh /data/kernel-rpms/正常会看到类似这样的文件:
kernel-lt-5.4.275-1.el7.elrepo.x86_64.rpm kernel-lt-devel-5.4.275-1.el7.elrepo.x86_64.rpm kernel-lt-headers-5.4.275-1.el7.elrepo.x86_64.rpm如果某些依赖需要单独补(比如 abrt、dracut 版本太老),yumdownloader 也会一并拉下来,这一步不要跳过--resolve。
2.2 校验包完整性与签名
把 rpm 包传到内网机器之前,建议至少做一次完整性校验。在联网机器上还没装包之前,可以用 rpm 命令检查签名:
cd /data/kernel-rpms rpm -K *.rpm理论上每个包会显示:
digests signatures OK如果以前导入过 ELRepo 的公钥,签名校验会直接通过;如果显示MISSING KEY,那只是没导入公钥,不影响包本身完整性,但建议先在联网机器上导入公钥再校验,避免内网机器上缺少信任链导致后续安装异常。
对于纯离线环境,内网机器上一般也不会有 ELRepo 公钥,那安装时加上--nogpgcheck即可:
rpm -Uvh --nogpgcheck kernel-lt-*.rpm这个参数在离线装内核时非常常用,内部系统不要求强制验签的情况下基本都会用到。但要注意:如果你们内部对 rpm 审计有强制要求,还是要提前把 GPG 公钥文件一并拷贝进去统一管理,不能直接跳过。
2.3 上传到内网机器
这一步用 U 盘、内网共享目录或者直接scp都行,但要注意路径尽量简单。我放到/root/kernel-upgrade/下:
scp /data/kernel-rpms/*.rpm root@内网机器IP:/root/kernel-upgrade/到内网机器上确认文件完整:
cd /root/kernel-upgrade/ ls -lh *.rpm对比一下文件大小,跟联网机器上一致就说明传输没丢数据。有条件的话再跑一遍 md5sum 比对,更稳妥。
3. 离线安装内核与 GRUB2 调整实操
3.1 安装前的最后检查
内网机器上,先做一次试安装检测依赖,这一步非常重要。rpm 提供了--test参数,可以预演安装过程而不真正写入系统:
cd /root/kernel-upgrade/ rpm -Uvh --test --nogpgcheck kernel-lt-*.rpm如果输出类似:
error: Failed dependencies: xxx is needed by kernel-lt-5.4.275-1.el7.x86_64那就说明还有依赖没补齐。这时候去联网机器上把缺失的依赖包也拉下来,再传上来。这一步在离线环境下务必执行,不要为了省事跳过,不然装到一半报错再补包,麻烦的是自己。
正常情况--test会安静通过,没有输出或者只显示准备信息。这时候才能开始正式安装。
3.2 执行内核安装
正式安装命令:
cd /root/kernel-upgrade/ rpm -Uvh --nogpgcheck kernel-lt-*.rpm这里用-U(upgrade)而不是-i(install),因为在有旧内核的情况下,-U会保留旧内核的同时安装新内核,并自动生成新内核的 initramfs。观察输出,关键部分是:
Installing: kernel-lt-5.4.275-1.el7.x86_64 ... Generating grub configuration file ... ... Installing: kernel-lt-devel-5.4.275-1.el7.x86_64 kernel-lt-headers-5.4.275-1.el7.x86_64看到 “Generating grub configuration file” 说明安装脚本已经自动执行了 grub2-mkconfig。但装完不要高兴太早,还要手动确认默认引导项是否指向了新内核。
安装完成后验证 /boot 目录:
ls -l /boot/vmlinuz-*正常情况下会同时存在老内核(3.10.0-1160)和新内核(5.4.275),两个内核文件都在,说明回滚能力还保留着。
3.3 调整 GRUB2 默认启动项
CentOS 7 使用 GRUB2 引导,默认启动项由/boot/grub2/grubenv文件控制。新内核安装后,默认项未必是新内核,必须手动检查设置。
查看当前菜单中所有内核条目:
grep menuentry /boot/grub2/grub.cfg输出示例:
menuentry 'CentOS Linux (5.4.275-1.el7.x86_64) 7 (Core)' --class centos ... menuentry 'CentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core)' --class centos ... menuentry 'CentOS Linux (0-rescue-...) 7 (Core)' --class centos ...新内核的菜单条目默认会排在第一位(因为 rpm 安装脚本生成 grub.cfg 时把最新的放最前),但为了保险,还是手动执行:
grub2-set-default 0 grub2-mkconfig -o /boot/grub2/grub.cfggrub2-set-default 0的意思是设置默认启动第一条菜单项。如果新内核不在第一位,可以先记录菜单条目的编号(从0开始数),再grub2-set-default 对应编号。
随后检查 grubenv:
cat /boot/grub2/grubenv看到saved_entry=0就已经生效了。这里补充一句:CentOS 7 的 grub2-mkconfig 输出信息里可能会有一行警告,比如 “GRUB_DISABLE_OS_PROBER is not set”,只要你不是多系统共存,一般不影响。
3.4 重启进入新内核
重启前再做一次必要的落盘检查:
sync然后执行:
reboot重启后第一时间确认内核版本:
uname -r如果输出是新内核版本号(如 5.4.275-1.el7.x86_64),说明升级成功。如果输出还是旧内核,不要慌,很可能是没重启成功或者 GRUB 默认项没生效,再reboot一次,或者检查 grubenv。
另外强烈建议重启后立刻检查负载和 dmesg:
uptime dmesg | grep -i error至少观察几分钟,确认网络、磁盘、容器服务都恢复正常再继续下一步。
4. 升级后的清理、验证与常见问题速查
4.1 确认新内核模块加载并清理旧内核
进入新内核后,执行下面几条验证命令:
# 查看当前加载的内核模块数量 lsmod | wc -l # 检查网络模块是否能正常加载 ip a # 检查容器相关功能 docker info 2>/dev/null || systemctl status docker 2>/dev/null如果网络不通,大概率是新内核缺少对应网卡驱动,常见场景是某些老服务器的闭源网卡驱动没有适配新内核。这时候先别急着清理旧内核,用旧内核回滚再研究模块。
确认一切正常后,可以用 package-cleanup(yum-utils 提供)清理旧内核:
package-cleanup --oldkernels --count=2这条命令会保留最近 2 个内核,删除更旧的。我建议保留 2 个(新内核 + 上一个旧内核),不要直接--count=1,给自己留一条退路。
如果没有 yum-utils,也可以手动删除:
rpm -e kernel-3.10.0-1160.el7.x86_64手动删除要注意:必须连带删掉/lib/modules/3.10.0-1160.el7.x86_64/和 /boot 下对应的 vmlinuz、initramfs,并且删除后重新生成 grub.cfg:
grub2-mkconfig -o /boot/grub2/grub.cfg4.2 常见问题排查实录
按真实踩坑概率排序,整理几个高频问题:
问题一:重启后仍进入旧内核
这个我遇到过,原因是 grub2-set-default 生效了,但 BIOS 里默认从另一块硬盘启动,或者 GRUB 菜单里 rescue 条目排在前面。处理办法:重启时进入 GRUB 菜单,手动选择新内核条目;进入系统后重新grub2-set-default并确认 grubenv。
问题二:系统启动进入 dracut 紧急模式
典型表现是开机卡住,出现Entering emergency mode的提示。多数情况是 initramfs 没有正确包含根文件系统所在磁盘的驱动。处理办法:在 dracut 模式下用旧内核启动,然后重新生成 initramfs:
dracut -f /boot/initramfs-5.4.275-1.el7.x86_64.img 5.4.275-1.el7.x86_64-f是强制覆盖。但频繁手动重建 initramfs 不是好习惯,出现这种情况一般是根分区用的文件系统模块没进 initramfs,更稳妥的方式是在旧内核启动后检查/etc/dracut.conf.d/里有没有特殊配置,或者确认 grub 的root=内核参数是否正确。
问题三:升级后磁盘挂载异常
CentOS 7 默认文件系统是 xfs。旧内核 3.10 对应的 xfs 特性较老,新内核的 xfs 直接挂载没问题,反过来就不行——如果升完级再降回去,新内核写入的数据旧内核可能读不了。所以升级后如果发现挂载报错,先dmesg | grep xfs,再确认日志里的具体报错,一般进入旧内核能解决。总之,新内核稳定运行几天后,就不要轻易回滚到旧内核了,xfs 文件系统对新内核生成的元数据可能无法在旧内核下兼容。
问题四:docker/容器服务启动失败
由于 cgroup v2 和 iptables 规则的变化,docker 在老内核配置下能跑,切到新内核可能遇到iptables -t nat报错或者 cgroup 挂载失败。这个问题我在测试环境也遇到过,一般升级 docker 或者重启 docker 服务能让它重新适配新内核。离线环境下 docker 升级可能比较麻烦,升级内核前先确认 docker 版本是否兼容目标内核。
问题五:第三方内核模块缺失
最常见的就是杀毒 agent、监控 agent、某些虚拟化平台的 guest tools。这些工具在旧内核下编译的内核模块,到新内核里 100% 不能加载。解决办法是先去厂商官网/内网补丁包下载区找适配新内核的版本,装好 agent 再升级内核。顺序千万不能反,不然新内核启动后 agent 直接拉不起来,监控和安全体系同时失效。
4.3 离线环境特有的坑位总结
离线环境最大的特点是:出了问题,你不可能临时拉包、临时看文档。所以整个升级过程里,我反复强调的其实就两件事:备份和保留旧内核。
我这次的实际操作顺序是:
- 在联网机器下载所有 rpm 包
- 上传到内网服务器后,先
rpm --test验证依赖 - 安装新内核,不删除旧的
- 修改 GRUB2 默认启动项
- 验证新内核稳定性
- 等稳定运行一周后,清理旧内核
整个过程最花时间的不是装内核本身,而是依赖匹配和包传输。rpm 安装两分钟搞定,传包可能花半小时,找依赖包又来回折腾了一个多小时。如果你们内网有 Nginx 代理或者内部 yum 仓库,可以把 rpms 放进本地仓库,下次装其他机器就快多了。
最后分享一个小技巧:离线环境升级内核前,可以先把当前内核的 modules 目录完整打包备份:
tar czf /root/modules-backup-3.10.tar.gz /lib/modules/3.10.0-1160.el7.x86_64/万一新内核装坏了、旧内核也出了问题,这套 modules 是你最完整的底牌。我个人建议每个运维兄弟在动手前都做一次这个备份,成本极低,价值无限。