1. 先想清楚:3.10 到 4.19 到底升级了什么,值不值得动
把一台跑着 CentOS 7.9 的机器从 3.10 内核换到 4.19,这件事在运维圈里属于"看起来一句话,干起来一整天"的活。很多人第一次动这个念头,往往不是因为喜欢折腾,而是被现实逼的:新采购的 NVMe 盘认不出来,新网卡插上lspci有、ip a没有,容器跑着跑着 overlayfs 出问题,或者某个业务软件在安装脚本里硬性检查uname -r的主版本号。这些问题在 3.10 上你想修都没地方下手,因为内核太老了。
CentOS 7.9 自带的是 3.10.0-1160 系列内核,基线可以追到 2013 年前后的 3.10 主线。需要注意的是,上游 3.10 和红帽维护的这个 3.10 并不是一回事——他们往里回移了大量 4.x 的特性,比如 tcp_bbr 模块、部分 eBPF 能力、新的文件系统挂载选项,所以你不能简单地拿"3.10 就是老古董"来概括它。但回移终究是回移,接口是残缺的,文档是缺失的,社区案例是搜不到的,你遇到的问题在 Google 上基本只有红帽自己的 KB 有答案。这才是真正难受的地方。
4.19 是 2018 年底发布的一个长期支持版本,它的价值不在于"版本号大",而在于它把 4.x 这一代内核的完整能力一次性给全了:完整的 eBPF 和 bpftool 工具链、overlayfs 的 metadata-only copy-up、更成熟的 cgroup v2、XFS 的在线修复与 reflink、更好的 NVMe 多队列支持、XDP 与 AF_XDP、以及一整套针对侧信道攻击的缓解机制默认开启。对一台还在服役的老机器来说,这些能力很多是用不上的,但只要你的业务沾上容器、高性能网络、新硬件,就至少会撞上其中两三条。
1.1 3.10 上那些绕不过去的具体短板
先说硬件。这是最直接的推动力。3.10 的 PCI ID 表停在 2013 到 2019 年之间,中间新增的设备只能靠回移补丁覆盖,覆盖面有限。你买一块 2020 年之后出的企业级 NVMe,插上之后dmesg里可能只有一句nvme nvme0: I/O 12 QID 0 timeout,或者干脆连nvme控制器都起不来。新网卡同理,芯片在,但驱动的 ID 表里没有,lspci -k显示 "Kernel driver in use: 无"。
再说容器和隔离。3.10 的 cgroup 只有 v1,overlayfs 在很多细节上不完善,最典型的是 copy-up 行为——3.10 的 overlayfs 在修改一个文件时会触发整个文件的数据拷贝,而 4.19 引入了 metadata-only copy-up,只拷贝元数据、数据走原文件引用。这个差异在大镜像、多层构建的场景下能差出几倍的启动时间。如果你的机器上跑着容器平台,这一条本身就值回票价。
网络方面,3.10.0-1160 确实回移了 tcp_bbr 模块,你可以modprobe tcp_bbr然后设net.ipv4.tcp_congestion_control=bbr。但这个 BBR 是早期版本,缺少后来针对浅缓冲、丢包误判的修正,也没有 4.19 里那套和 fq qdisc 配合得更好的实现。另外 3.10 的ss -i输出的信息量比 4.19 少一大截,排查拥塞问题时很不方便。
还有一处容易被忽略:3.10 的 eBPF 只能做很基础的事情,bcc工具集里一大半脚本在上面跑不起来或者跑出来数据不准。你要是想用 eBPF 做无侵入的延迟观测,3.10 基本是死路,4.19 才勉强算能用。
1.2 4.19 换来的实际收益清单
我把这些收益按"能立刻感受到"和"长期受益"分了一下,方便你判断自己的场景该不该动:
| 收益项 | 具体表现 | 感受强度 |
|---|---|---|
| 新硬件支持 | 新 NVMe、网卡、USB 控制器、CPU 微码识别 | 立刻,插上就用 |
| overlayfs 改进 | metadata-only copy-up,镜像层操作明显变快 | 立刻,容器密集场景明显 |
| 网络栈 | BBR 完整实现、fq/fq_codel 成熟、XDP 可用 | 中期,压测能看出来 |
| XFS 增强 | reflink、在线修复、更细的 quota | 中期,取决于是否用得上 |
| cgroup v2 | 统一层级、更精细的内存控制 | 长期,但受用户态限制 |
| 安全缓解 | KPTI、retpoline、init_on_alloc 等默认开启 | 长期,无感但重要 |
| 观测能力 | perf、bpftrace、ftrace 功能完整 | 中期,排障效率提升 |
需要泼一盆冷水的是 cgroup v2。4.19 内核里 cgroup v2 的控制器已经比较完整了,但 CentOS 7.9 配的 systemd 是 219 版本,它根本不认识 unified hierarchy。你就算在内核启动参数里加systemd.unified_cgroup_hierarchy=1,systemd 也会直接罢工。所以升级内核并不等于你能用上 cgroup v2,这一条基本可以划掉,除非你连用户态一起换。
1.3 代价盘点:升级前必须认的账
升级内核最怕的不是升不上去,是升上去之后三个月才发现某个东西不对。所以动手前要把代价列清楚。
第一笔账是内核 ABI。原来的 3.10 由发行方维护一套稳定的 kABI,第三方驱动厂商只要对着这套 ABI 编译,就能跨小版本使用。换成 4.19 之后,这套保证没有了,任何依赖 kABI 的闭源模块都必须按新内核重新编译。你机器上装了硬件 RAID 卡管理工具、某些监控 agent、某些安全 agent,它们带的内核模块很可能就是这一类。
第二笔账是用户态工具链的老化。CentOS 7.9 的 glibc 是 2.17,iproute2 是 3.10 时代的版本,util-linux 是 2.23。4.19 内核暴露的一些新接口,老工具根本不认识。比如你想用ip link配置某些新属性,它会报 "RTNETLINK answers: Invalid argument",原因不是内核不支持,是工具太老不会拼这个 netlink 消息。同理,bpftool需要自己编译,perf要从对应版本的源码树里编,系统自带的那个版本对不上。
第三笔账是sysctl 键的变化。3.10 时代的一堆/proc/sys/net/ipv4/conf/*/xxx参数,在 4.19 里有的被移除、有的被重命名、有的语义变了。升级前建议执行一遍sysctl -a > /tmp/sysctl-before.txt,升级后再导一份对比,把消失的键找出来,看看你的调优脚本里有没有引用它们。
第四笔账是排障经验失效。你团队里那套"看这个日志、调这个参数"的肌肉记忆,有一部分要重学。这不算技术成本,但确实是真实成本。
1.4 三条技术路线对比与选型建议
实际可走的路不止一条,我把常见的四种列出来横向比一下:
| 路线 | 工作量 | 可控性 | 可回滚性 | 长期维护成本 | 适用场景 |
|---|---|---|---|---|---|
| 换成新大版本的发行版 | 最大 | 高 | 差(要重装) | 最低 | 机器数量多、有统一编排 |
| 用 ELRepo 装现成 RPM | 小 | 中 | 好(grub 切回) | 中 | 只要 4.x 能力,不锁小版本 |
| 源码编译并打 RPM | 中 | 最高 | 好 | 高(自己跟补丁) | 必须精确锁 4.19 |
| 不动内核,改用容器/用户态方案 | 视方案 | 中 | 好 | 中 | 只是想跑新软件 |
这里有个关键事实要说明白:ELRepo 这个第三方仓库提供kernel-ml(跟随主线)和kernel-lt(跟随长期支持)两个系列。kernel-lt在不同时期对应的版本是不一样的,历史上长期是 4.4,后来切到过 5.4。也就是说,你现在去装kernel-lt,拿到的很可能不是 4.19。如果你的目标就是精确的 4.19,用yum --showduplicates list翻历史版本、或者去仓库的归档目录里找旧包,都比直接装要靠谱;实在找不到,就走源码编译那条路,可控性最高。
我个人的判断是:如果只是想要 4.x 的能力而不在乎具体小版本,走 ELRepo 最省事;如果是被某个软件硬性要求 4.19,或者需要在几十台机器上保证版本完全一致,那就老老实实自己编 RPM,一次性把 spec 和构建环境固化下来,后面批量分发反而更轻松。
2. 动手前的摸底:把风险摸透再谈变更
很多人栽跟头不是因为技术不够,是因为信息不全。升级前花两小时做摸底,能省掉后面两天的救火。这一节讲的就是怎么在动手前把机器的底细摸清楚。
2.1 主机与业务清单盘点
先建立一份"这台机器上到底跑了什么"的清单。基础信息用几条命令就能拉出来:
uname -r rpm -qa | grep -E '^kernel' | sort cat /etc/centos-release lscpu | head -20 free -g df -hT重点是/boot的空间。CentOS 7 默认给/boot分 1GB,这个大小在只有一两个 3.10 内核的时候很宽裕,但 4.19 的内核镜像加 initramfs,一套下来 100MB 到 200MB,你要是保留旧内核再加一个新内核,很容易撑爆。装到一半报 "No space left on device" 是很常见的翻车点,而且是在最不该出问题的阶段。
然后是业务侧。把这几类东西单独列出来,逐个确认它们有没有内核态组件:
- 容器运行时及其存储驱动
- 数据库和它的 IO 相关配置
- 监控 agent、日志采集 agent
- 安全 agent、入侵检测组件
- 硬件管理工具(RAID 卡、网卡管理套件)
- 加密加速、国密卡之类的专用硬件驱动
判断方法很直接,看lsmod里那些不在标准内核树里的模块:
lsmod | awk 'NR>1 {print $1}' | while read m; do f=$(modinfo -F filename "$m" 2>/dev/null) case "$f" in */extra/*|*/updates/*|*/weak-updates/*) echo "$m -> $f";; esac done凡是路径里带extra、updates、weak-updates的,基本都是树外模块,升级后大概率要重装或重编。这份名单一定要在动手前确认清楚,同时把对应的安装包名也记下来,方便升级后重新安装。
2.2 驱动与内核模块排查(这一步最容易翻车)
硬件驱动的排查要具体到每一块网卡、每一个存储控制器。用lspci -k能看到设备和它当前绑定的驱动:
lspci -k | grep -E -A3 'Ethernet|RAID|SCSI|NVMe|VGA'对每块网卡,再看一眼驱动名和版本:
ethtool -i eth0输出里的driver、version、firmware-version三项都要记下来。如果version显示的不是内核自带的版本号(比如显示一个厂商自己的编号),说明这是树外驱动,4.19 里很可能没有对应实现,需要提前找厂商要新版本,或者确认该硬件在 4.19 的内核树里有原生支持。
固件也要留意。CentOS 7.9 的固件包是linux-firmware,安装路径是/lib/firmware。这一块在内核升级时通常不用动,因为它是用户态文件,新内核照样读。但如果你的设备需要某个特定版本的固件才能在新内核下正常工作,就得单独确认。
还有一个细节:网卡命名规则。CentOS 7 用的是 systemd/udev 的 predictable network interface names,规则在/etc/udev/rules.d/里,比如70-persistent-net.rules或者基于 ifcfg 文件里的HWADDR。新内核下设备的枚举顺序可能变,导致网卡名字从ens33变成ens34。防这一手的办法是在 grub 参数里加net.ifnames=0 biosdevname=0,退回传统的eth0命名,配置文件不用改。这个参数要写进/etc/default/grub的GRUB_CMDLINE_LINUX里,确保新旧内核都生效。
2.3 备份与回滚方案设计
内核升级的回滚,本质上是 grub 引导项切换,所以只要旧内核和它的 initramfs 还在/boot里,回滚就是改一个默认项然后重启的事。但前提是你没把旧内核删掉,也没把/boot搞坏。
需要备份的东西列一下:
/boot整个目录(至少grub2/grub.cfg、vmlinuz-*、initramfs-*)/etc/default/grub/etc/fstab/etc/modprobe.d/和/etc/modules-load.d//etc/sysconfig/下的网络配置- 当前
sysctl -a的完整输出
虚拟机的话直接打快照最省事,这是成本最低的回滚手段,有条件的都该用。物理机的话,至少要保证/boot有一份 tar 备份放到别的分区,并且确认你能在救援模式下把它恢复回去。
注意:物理机升级前先确认你能进救援模式。很多机器在 grub 菜单里没有救援入口,得靠安装介质引导。这一步不验证,等于没有回滚方案。
2.4 演练环境的低成本搭法
有条件的话,先在虚拟机上把整套流程走一遍。最贴近真实环境的做法是拿virt-v2v之类的工具把生产机镜像导出来,起一台配置相同的虚拟机。更简单的做法是找一台同型号的测试机,或者在虚拟化平台上按同样的分区结构装一台最小化的 CentOS 7.9,把关键软件包装上,然后跑一遍完整流程。
演练要验证的点不只是"能不能起来",还包括:驱动是否齐全、树外模块能否加载、容器能否启动、业务探针是否正常、监控数据是否还在上报。这些都过了,才轮到生产机。
3. 实操上:4.19 内核的安装与引导切换
准备工作做完,正式进入实操。这里给两条路线,按你的实际需求选。
3.1 路线一:通过 ELRepo 拿现成的 RPM 包
ELRepo 是 CentOS 生态里最常用的第三方内核仓库,配置很简单:
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm装完先看看仓库里都有什么:
yum --disablerepo="*" --enablerepo="elrepo-kernel" list available | grep '^kernel'标准的包名是kernel-lt和kernel-ml,对应的包还有kernel-lt-devel、kernel-lt-headers、kernel-lt-tools。这几个建议一起装,尤其 devel 包,后面装树外驱动、编 DKMS 模块都要用。
安装命令:
yum --enablerepo=elrepo-kernel install -y kernel-lt kernel-lt-devel kernel-lt-headers装完rpm -qa | grep kernel-lt确认一下版本号。如果出来的不是你想要的 4.19,用yum --showduplicates list kernel-lt看历史版本,指定版本号安装:
yum --enablerepo=elrepo-kernel install -y kernel-lt-4.19.xxx-1.el7.elrepo找不到的话,就只能走第二条路。
3.2 路线二:从源码编译 4.19 并打成 RPM
这条路可控性最高,也最能保证版本精确。先装编译依赖:
yum groupinstall -y "Development Tools" yum install -y ncurses-devel openssl-devel elfutils-libelf-devel \ bc bison flex perl python3 rsync rpm-build redhat-rpm-config下载源码。选一个 4.19 系列的稳定小版本,尽量选该系列最后几个版本,补丁更全:
cd /usr/src curl -LO https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.xxx.tar.xz tar -xf linux-4.19.xxx.tar.xz cd linux-4.19.xxx配置的起点,建议直接拿当前系统的配置做基础:
cp /boot/config-$(uname -r) .config make olddefconfigolddefconfig会把新版本里多出来的选项按默认值填上。这一步很关键,但不代表可以闭眼过。3.10 到 4.19 跨越了太多个版本,新增的配置项有上千条,默认值不一定符合你的需求。要人工确认的关键项至少包括:
| 配置项 | 说明 | 建议 |
|---|---|---|
| CONFIG_EXT4_FS | 根分区如果用的是 ext4 | 必须为 y |
| CONFIG_XFS_FS | XFS 支持 | 必须为 y |
| CONFIG_OVERLAY_FS | 容器存储驱动依赖 | 容器环境必须为 y |
| CONFIG_BRIDGE / CONFIG_VXLAN | 虚拟网络 | 有虚拟化或容器网络时必须 y |
| CONFIG_VIRTIO_* | 虚拟化平台磁盘网卡 | 虚拟机必选 |
| CONFIG_NF_TABLES | 新版防火墙后端 | 建议 y,同时保留 iptables |
| CONFIG_BPF_SYSCALL / CONFIG_BPF_JIT | eBPF | 需要观测能力时必选 |
| CONFIG_MODULE_SIG | 模块签名 | 有安全基线要求时要注意 |
确认完开始编译。并行数别贪多,一般取核数,但每个编译进程大概吃 1.5 到 2GB 内存,内存不够会直接 OOM:
nproc free -g8 核 16GB 的机器,-j8是安全线;8GB 内存的机器建议-j4。全量编译 4.19 大概 20 到 40 分钟,看磁盘和 CPU。
编译并直接打成 RPM,后面批量部署就靠这个包:
make -j8 rpm-pkg产物在~/rpmbuild/RPMS/x86_64/下,会生成kernel-4.19.xxx、kernel-headers、kernel-devel三个包。这个方法的额外好处是:目标机器不需要装编译工具链,直接rpm -ivh就行,而且卸载干净。
如果想先在本机试跑,也可以用传统的安装方式:
make -j8 make modules_install cp arch/x86/boot/bzImage /boot/vmlinuz-4.19.xxx cp System.map /boot/System.map-4.19.xxx cp .config /boot/config-4.19.xxx3.3 grub2 引导项确认与默认内核设置
内核文件到位之后,要生成 initramfs 并写入 grub。CentOS 7 用的是 dracut,指定内核版本生成:
dracut -f /boot/initramfs-4.19.xxx.img 4.19.xxx这一步会读取/lib/modules/4.19.xxx/下的模块信息,所以必须保证make modules_install已经跑过。生成的 initramfs 大小通常在 20 到 60MB 之间,如果只有几 MB,说明 hostonly 模式没抓到驱动,得手动加:
dracut -f --add-drivers "megaraid_sas mpt3sas ixgbe" /boot/initramfs-4.19.xxx.img 4.19.xxx然后把这个内核注册进 grub:
grubby --add-kernel=/boot/vmlinuz-4.19.xxx \ --initrd=/boot/initramfs-4.19.xxx.img \ --title="CentOS 7.9 (4.19.xxx)" \ --copy-default--copy-default的作用是把当前默认引导项的启动参数复制过来,包括 root=、rd.lvm.lv、crashkernel 这些。不加这个参数,新内核启动时会缺参数,直接 panic。这一步是新手最容易漏的。
如果用的是 RPM 包安装,%post脚本里通常会自动完成上面这些动作,装完grub2-editenv list看一眼就知道。
用 BIOS 引导时,grub 配置文件在/boot/grub2/grub.cfg;用 UEFI 引导时在/boot/efi/EFI/centos/grub.cfg。确认新条目确实写进去了:
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg设置默认启动项:
grub2-set-default 0 grub2-editenv list如果命令输出的saved_entry和你选的不一致,说明有别的机制在覆盖,检查/etc/default/grub里的GRUB_DEFAULT。改完之后记得跑一次grub2-mkconfig -o /boot/grub2/grub.cfg让它真正生效。
3.4 重启后的验收清单
重启这一步没什么技巧,但要保证你能接触到控制台,别只靠 SSH。重启之后按这个清单逐项过:
uname -r # 确认内核版本 dmesg -T | head -60 # 看启动早期有没有报错 dmesg -T | grep -iE 'error|fail|warn' | head -30 systemctl --failed # 有没有服务起不来 journalctl -b -p err # 本次启动的所有错误级日志 lsmod | wc -l # 模块数量对比升级前 ip a # 网卡是否齐全、IP 是否正确 df -hT # 文件系统是否正常挂载 cat /proc/mdstat # 软 RAID 状态还要额外检查几个容易被忽略的点:
/proc/sys/kernel/random/boot_id的变化说明确实是新启动cat /proc/cmdline确认启动参数完整sysctl -a > /tmp/sysctl-after.txt,和升级前那份做 diff- 如果有 kdump 配置,执行
systemctl status kdump确认转储内核能正常加载
sysctl的 diff 建议这么看:
diff <(cut -d= -f1 /tmp/sysctl-before.txt | sort) \ <(cut -d= -f1 /tmp/sysctl-after.txt | sort)左边有、右边没有的键,就是你原来配置里已经失效的部分,得从调优脚本里清掉,不然每次开机都会报 "cannot stat"。
3.5 旧内核的保留与清理
新内核稳定运行一到两周之前,旧内核一定不能删。这是回滚的唯一依靠。
yum的行为由/etc/yum.conf里的installonly_limit控制,默认是 3,意味着最多保留三个内核包。如果你的 4.19 是通过 RPM 装的,注意它会参与这个计数,可能会把 3.10 挤掉。升级前把installonly_limit改大一点,或者临时用yum install --setopt=installonly_limit=5。
确认新内核稳定之后,再清理:
package-cleanup --oldkernels --count=2这条命令会保留最新的两个内核。清理之前先看一眼/boot里剩下什么,别把正在跑的那个删了。源码编译方式安装的内核不归 yum 管,需要手动删/boot/vmlinuz-*、/boot/initramfs-*、/lib/modules/*和 grub 条目,删完跑一次grub2-mkconfig。
4. 升完不是结束:关键子系统调优与验证
内核换完只是完成了 60%,剩下的 40% 在调优和验证。3.10 上那套参数直接搬到 4.19,有的还能用,有的会起反作用。
4.1 网络栈:拥塞控制与队列调度
4.19 的 BBR 是完整实现,配合 fq qdisc 效果最好:
cat >> /etc/sysctl.d/99-net-tune.conf <<'EOF' net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 EOF sysctl --system验证是否生效:
sysctl net.ipv4.tcp_congestion_control sysctl net.core.default_qdisc ss -i | head -20ss -i的输出里,bbr相关的字段会显示bbr:(bw:...,mrtt:...,pacing_rate ...)。如果没有这些字段,说明拥塞控制没生效,检查是不是模块没加载或者有其他配置覆盖。
这里有个经验:default_qdisc设成fq对 BBR 是必须的,但对高并发短连接的场景,fq_codel在降低尾延迟上表现更稳。如果你机器上跑的是大量短连接的服务,可以先试fq_codel,用压测对比p99延迟再定。
多队列网卡还要看一下队列数。4.19 对 RSS 的支持更完整,中断可以分散到更多核上:
ethtool -l eth0 ethtool -S eth0 | grep -i 'queue\|drop'如果Combined队列数偏少,可以调:
ethtool -L eth0 combined 8调完观察/proc/interrupts里的分布是否均匀。这一步在 3.10 上也做,但 4.19 的中断亲和性处理更细致,同样的配置往往能拿到更均匀的分布。
4.2 存储与文件系统:IO 调度器的变化
3.10 时代的调度器是cfq、deadline、noop三选一,4.19 换成了多队列架构下的mq-deadline、kyber、bfq、none。名字变了,/sys/block/sdX/queue/scheduler里的可选项也变了:
cat /sys/block/sda/queue/scheduler对机械盘,mq-deadline是比较稳的选择;对 SSD 和 NVMe,none(也就是 noop 在多队列架构下的对应项)通常性能最好;bfq适合桌面或者对公平性要求高的场景,服务器上反而容易成为瓶颈。
XFS 在 4.19 上支持 reflink,但这个特性要在格式化时开启,已有的文件系统不会自动获得:
mkfs.xfs -m reflink=1 /dev/sdb1已有的 XFS 可以查一下当前的特性:
xfs_info /mountpointreflink=1显示为reflink=1。如果原来是 0,除非重新格式化,否则改不了。这个特性对快照、容器镜像层、备份去重很有价值,如果机器上有独立的空盘,可以考虑按新特性重新规划。
还有一个容易忽略的变化:4.19 的discard行为。3.10 上挂载参数里加discard会在删除时同步发 TRIM,4.19 上更推荐用fstrim.timer定期批量执行,避免同步 TRIM 带来的写放大。CentOS 7 的fstrim.timer默认状态可以用systemctl status fstrim.timer查,如果没启用,手动开一下:
systemctl enable --now fstrim.timer4.3 cgroup、systemd 与容器运行时的配合
这一块是升级后问题最集中的地方。4.19 内核支持 cgroup v2,但 CentOS 7.9 的 systemd 219 只认 v1,所以默认还是走 v1 挂载:
mount | grep cgroup应该能看到一堆cgroup on /sys/fs/cgroup/xxx type cgroup (rw,...,xxx)的挂载,说明是 v1 的分离式层级。这是正常的,不用动。如果有人建议你加systemd.unified_cgroup_hierarchy=1,别听,加了 systemd 起不来。
容器运行时这边,要注意两个点。一是存储驱动,overlayfs 在 4.19 上更稳,但overlay和overlay2是两个不同的驱动名,取决于运行时版本,升完内核之后确认一下docker info或者等价命令里显示的存储驱动有没有变。二是某些运行时会在启动时检查内核版本和/proc里的特性,老版本运行时可能会误判。如果容器起不来,先看运行时日志里有没有 "kernel does not support" 之类的字样。
seccomp 也是一个坑。老版本运行时的 seccomp 白名单是按 3.10 的系统调用表写的,4.19 新增了一批系统调用,如果容器里的程序用了新调用,会被白名单拒绝,报 "Operation not permitted"。这个报错很有迷惑性,看起来像权限问题,实际是 seccomp。排查方法:
dmesg | grep -i seccomp如果有audit: type=1326之类的记录,基本可以确定。解决办法是升级运行时版本,或者临时用--security-opt seccomp=unconfined验证(只用于排查,不要长期这么跑)。
4.4 安全模块与审计的重新确认
4.19 默认开启了几个安全特性,会改变一些行为。最典型的是kernel.kptr_restrict和kernel.dmesg_restrict,很多发行版在 4.x 时代把默认值调严了,导致普通用户读dmesg会得到 "Operation not permitted"。这不是故障,是设计。如果有脚本依赖读dmesg,要么加 sudo,要么调低限制。
SELinux 的策略本身和内核版本关系不大,但 AVC 日志的格式和字段在 4.19 上有细微变化。检查一下:
sestatus ausearch -m avc -ts recent | head -20auditd 这边,CentOS 7.9 自带的 audit 2.8.5 在 4.19 上基本能用,但如果你的规则里用到了某些新字段,可能会报 "unknown field"。升级后跑一次auditctl -l确认所有规则都加载成功,然后执行service auditd restart观察日志。
还有一处值得检查:kernel.modules_disabled。有些安全基线会把它设为 1,意味着任何模块都不能再加载。升级内核后如果这个值被继承下来,你后面想modprobe任何东西都会失败,而且报错信息很不直观。升级后第一时间确认一下:
sysctl kernel.modules_disabled5. 故障现场实录:那些年我们踩过的坑
这一节把实际遇到过的问题按现象归类,前面是现象,后面是原因和排查路径。
5.1 卡在 grub 或直接 panic
最常见的三种 panic 信息,含义各不相同。
"VFS: Unable to mount root fs on unknown-block(0,0)",这个基本就是 initramfs 里缺存储驱动。原因通常是 dracut 生成时用了 hostonly 模式,而新内核的模块路径还没准备好,或者生成的时候/lib/modules/4.19.xxx里的modules.dep还没建好。解决办法是先进旧内核,重新跑一次depmod -a 4.19.xxx,再dracut -f --add-drivers "实际需要的驱动" ...重新生成。
"Kernel panic - not syncing: No working init found",这个通常是 root= 参数不对或者 initramfs 内容不完整。检查grubby --info=ALL里新条目的args是不是复制过来了。
grub 菜单里根本没有新内核,多半是/boot空间不足导致写入失败,或者grub2-mkconfig没重跑。检查df -h /boot和ls -lh /boot/vmlinuz-*。
这里补一个实操技巧:出问题的时候,grub 菜单里选中新内核,按e编辑,把rhgb quiet删掉,加上debug ignore_loglevel,能看到完整的启动日志。再不行就加rd.break进 initramfs 的 shell 里手动检查。
5.2 起来了,网卡没了
系统能起来,ip a只有 lo,这个现象背后可能是三个原因。
一是驱动不在 4.19 的内核树里。这种情况lspci -k会显示设备存在但没有 "Kernel driver in use"。需要找厂商要对应 4.19 的驱动源码重新编译,或者确认芯片型号,在 4.19 的drivers/net/ethernet/目录里找找有没有原生支持。
二是驱动在,但固件没加载。dmesg | grep -i firmware会显示 "Direct firmware load for xxx failed"。检查/lib/firmware下有没有对应的.bin文件,没有的话从linux-firmware包里找,或者从厂商那里拿。
三是驱动和固件都在,但网卡名字变了,配置文件对不上。CentOS 7 的网络配置是按接口名找/etc/sysconfig/network-scripts/ifcfg-xxx,名字一变,配置就不生效。前面提到的net.ifnames=0 biosdevname=0就是防这个的。
排查顺序建议这样:先ip a看有没有设备,再lspci -k看驱动绑没绑上,再dmesg看固件加载,最后查命名。
5.3 容器跑不起来 / 存储挂不上
容器起不来,先分清楚是运行时的问题还是存储的问题。
如果mount -t overlay overlay ...手动挂载失败,报 "invalid argument",说明内核的 overlayfs 配置没开,或者挂载参数不被支持。4.19 的 overlayfs 支持metacopy=on选项,但只有在 4.19 之后的版本才稳定,早期版本加上去可能出问题,先别加。
如果是容器网络不通,检查br_netfilter模块和net.bridge.bridge-nf-call-iptables这个 sysctl。4.19 里这些还是默认关闭的,需要手动开:
modprobe br_netfilter sysctl -w net.bridge.bridge-nf-call-iptables=1还有一种情况是存储挂载失败但日志里没有任何有用信息,这种时候看dmesg里的 SCSI 层日志,往往能看到 "rejecting I/O to offline device" 之类的话,说明存储链路本身有问题,和内核版本无关。
5.4 第三方模块编译失败
DKMS 模块在新内核上编译失败,最常见的原因是没有匹配的kernel-devel。检查/lib/modules/4.19.xxx/build这个软链接指向哪里:
ls -l /lib/modules/4.19.xxx/build如果指向一个不存在的目录,说明kernel-devel装的是别的版本,或者根本没装。源码编译的内核,这个链接是指向源码目录的,编译完之后别把源码目录删了,否则后面装驱动会失败。
编译报错"找不到某个头文件"的话,大概率是模块源码太老,用了 4.x 里被移除的 API。这种情况只能升级模块源码。常见的 API 变化包括:get_ds()被移除、create_proc_entry换成proc_create、init_timer换成timer_setup。这些在模块源码里搜一下就能定位。
5.5 常见问题速查表
| 现象 | 最可能原因 | 快速验证 | 处理 |
|---|---|---|---|
| panic 无法挂根 | initramfs 缺存储驱动 | lsinitrd /boot/initramfs-*.img | grep 驱动名 | 重新 dracut 并--add-drivers |
| 卡住不动无输出 | 启动参数缺失 | grub 编辑项看 args | grubby --copy-default重加条目 |
| 网卡消失 | 驱动为树外或命名变化 | lspci -k、dmesg | 编译驱动或加net.ifnames=0 |
| 容器异常退出 | seccomp 白名单或 cgroup 路径 | dmesg | grep seccomp | 升级运行时版本 |
| dmesg 读不了 | kptr/dmesg_restrict 收紧 | sysctl kernel.dmesg_restrict | 用 root 读取或调低限制 |
| sysctl 报 cannot stat | 键在新内核被移除 | 比对 before/after | 从配置文件中删除 |
| 模块加载失败 | 未签名或 kABI 不匹配 | modprobe -v 模块名 | 重编或关闭签名校验 |
| /boot 写满 | 分区空间不足 | df -h /boot | 清理旧内核或扩容 |
6. 生产环境的落地节奏与长期维护
单台机器升完不算什么,真正的考验是怎么在一批机器上稳定落地,以及后面几年怎么维护。
6.1 灰度批次与变更窗口怎么排
我习惯分三批。第一批 1 台,挑业务压力最小的那台,升完观察 48 小时。观察指标除了常规的 CPU、内存、负载,还要特别看:dmesg里有没有新的 call trace、内核日志里有没有 OOM、网卡有没有丢包计数增长、文件系统有没有报错。48 小时里只要出现一次无法解释的异常,就暂停后续批次,回去查。
第二批 10% 左右,观察一周。这一批的意义是覆盖不同的负载形态,比如第一批挑的是 Web 服务,第二批就要包含数据库、消息队列这类对 IO 和网络敏感的角色。
第三批全量。全量之前准备一份回滚脚本,内容就是grub2-set-default切回旧条目然后reboot,放在每台机器上都有的路径下,并且确认每个运维同学都知道它在哪。
变更窗口的选择上,我倾向选业务低峰的前半段,而不是正中间。原因是如果出问题,你还有时间在低峰期内恢复,不至于把故障拖到业务高峰期。
6.2 升级后必须盯的监控项
新增几个针对内核本身的监控项,这些在升级前是没必要看的,升完必须要看:
node_uname_info之类的指标,确认每台机器的内核版本和你预期一致dmesg里的关键字计数,比如 "Call Trace"、"Out of memory"、"hardware error"- 软中断占用的 CPU 时间,
/proc/softirqs的变化,网络密集型业务尤其要看 - 网卡层面的
rx_dropped、tx_dropped、rx_fifo_errors - TCP 重传率,
/proc/net/snmp里的RetransSegs - 文件系统的
xfssm之类健康检查指标,如果有的话
前面几项是通用的,TCP 重传率这一项值得单独说。BBR 上线之后重传率的行为和之前的 cubic 完全不一样,BBR 会把重传率压得很低,如果你还按老阈值告警,会发现告警再也不会触发,这本身不是坏事,但要知道阈值要重新标定。
6.3 长期维护:跟不跟主线,跟到什么程度
这里要给个明确态度:4.19 是一个长期支持版本,但它的支持期是有限的,而且第三方仓库提供的构建没有商业支持背书。如果你的机器要跑五年以上,最好在做这件事的时候就想清楚后续路线。
我的建议是分成两种情况。如果机器数量少、业务重要度高,就别折腾内核了,把它当成一次性动作,锁死一个版本,只在有明确安全问题时打补丁。如果机器数量多、有统一的自动化能力,那就在升级内核的同时规划用户态升级,把整个系统迁移到新的大版本上,一劳永逸。
还有一个折中方案:容器化的业务可以只升级宿主机内核,把用户态的老化问题用容器镜像来规避。这个方案在国内不少团队里跑得挺顺,代价是宿主机本身还是老系统,某些系统级组件的新特性用不上。
最后分享一个我自己踩过的小坑。有一次升完内核,一切正常,三个月后业务量涨上来,突然出现大量连接超时。查了两天才发现,是 3.10 时代留下的一条net.ipv4.tcp_max_syn_backlog调优参数在新内核上被另一个配置项覆盖了,实际生效值一直是默认的。这件事教会我一个习惯:升级内核之后,把所有关键 sysctl 的实际生效值(不是配置文件里的值)导出成一份清单,存进配置管理里,后面每次变更都跟这份清单对比。配置文件骗人,/proc/sys不骗人。