一、内核升级这件事,风险到底在哪
Linux 服务器跑久了,迟早会遇到内核升级这件事。它和新装一个软件完全不是一回事:软件升级失败最多是那个服务起不来,内核升级失败可能让整台机器再也开不了机,而你又没有物理控制台可用。
先明确一件事:内核升级的风险不在「新内核有 bug」,而在「新内核和你的硬件/驱动/模块不匹配」,以及「升级后没有人第一时间发现」。前者概率不高但后果严重,后者才是真正普遍的翻车原因——很多人的机器在升级后重启,其实已经正常启动了,但某个依赖 DKMS 的模块没编出来,站点跑到一半才发现文件系统是只读的。
还有一个常见的误解:以为「升级内核」和「重启」是一件事。实际上在 Debian/Ubuntu 上,apt upgrade 装上新内核之后,系统仍然运行着旧内核,直到你重启。这个空窗期可以是一周也可以是一年。所以真正要规划的不是升级本身,而是什么时候重启、重启前后怎么验证。
本文按「准备 → 升级 → 验证 → 回滚」的顺序讲,重点放在回滚和验证上,因为这两步是大多数教程里缺失的部分,也是事故真正造成损失的地方。
二、先看清楚现状:现在跑的是什么内核
动手之前,先花一分钟把现状记录下来。这几条命令的输出建议直接存成文件,回滚的时候会用上。
uname -r # 当前运行的内核版本
dpkg -l | grep -E 'linux-image|linux-headers' | awk '{print $2, $3}'
ls -l /boot/vmlinuz-* # 已安装的内核
grep -E 'GRUB_DEFAULT|GRUB_TIMEOUT' /etc/default/grub要关注的几个点:/boot 上现在有几个内核?GRUB_DEFAULT 是数值还是 saved?如果 /boot 只有一个内核,说明这个系统一直是「装新的就删旧的」,回滚窗口为零——这种情况下升级前务必先把当前内核和 initrd 备份出来。
另外必须确认 /boot 的剩余空间。内核升级最常见的一个失败是 /boot 太小(很多云镜像默认只给 200MB),新内核 + initramfs 装不下,apt 报 No space left on device,而且此时 dpkg 处于半完成状态,如果重启就直接进不了系统。
df -h /boot
du -sh /boot 2>/dev/null如果 /boot 已经用了 80% 以上,先清理旧内核再升级,而不是先升级再清理。
查看当前内核的安装来源也很重要。Ubuntu 上的内核来自多个包集合(linux-image-generic、linux-image-generic-hwe-22.04、或者云厂商定制的 linux-image-*-azure / -aws / -gcp)。云主机一定要装厂商元包,装成通用内核会导致网络驱动或磁盘驱动缺失,重启后失联。
apt list --installed 2>/dev/null | grep -E 'linux-(image|modules)-[a-z]+'三、风险清单:五类会真的出事的情况
不是所有内核升级都一样危险。下面这五类情况,需要额外的小心。
第一类:使用了 DKMS 编译的第三方模块。比如 ZFS、VirtualBox 的 guest additions、某些 RAID 卡的驱动、WireGuard(旧版本)、NVIDIA 驱动。这些模块是「针对每个内核版本现场编译」的,新内核装上来时 DKMS 会自动编一次;如果编译失败,模块就不存在。判断方法:
dkms status如果有输出,升级前先在测试机上验证一次,或者至少准备好「万一编不出来就回滚」的方案。特别注意 ZFS:ZFS 内核模块和内核版本是强绑定的,ZFS 版本太老配太新的内核会直接编不过。
第二类:自己编译过内核或用 make modules_install 装过模块。这些模块不在包管理里,换内核后全部失效,而且 dpkg 不会告诉你。检查方法:ls /lib/modules/ 下有几个版本,和已安装的内核包对一下,多出来的就是手工装的。
第三类:自定义的 initramfs 或加密根分区。用了 LUKS 全盘加密的机器,升级后如果 initramfs 生成有问题,开机会卡在密码提示前或者提示找不到根设备。这类机器必须先在本地虚拟机或云上临时机验证。
第四类:云主机的定制内核。前面提到过,AWS/GCP/Azure/阿里云的官方镜像往往带一个深度定制的内核(网络、存储、监控都靠它)。不要用 apt full-upgrade 盲目升级到上游内核,尤其在云主机上。先用 apt list --upgradable 看清是哪几个包要升,确认它们在厂商的仓库里。
第五类:内核参数(sysctl)与老内核默认行为依赖。这类最隐蔽。比如某些老系统依赖 net.ipv4.tcp_tw_recycle,这个参数在新内核里已被移除,写在 /etc/sysctl.conf 里会导致 sysctl --system 报错;更糟的是某些参数名保留但语义变了。升级前先跑一遍 sysctl -p 看有没有报错的项,把无效项先清掉。
四、升级前的四项准备
准备工作花二十分钟,能省掉一次事故。四项必须做。
准备一:备份 /boot 与当前内核包名。
KVER=$(uname -r)
mkdir -p /root/kernel-backup
cp /boot/vmlinuz-$KVER /boot/initrd.img-$KVER /root/kernel-backup/
cp /boot/config-$KVER /root/kernel-backup/ 2>/dev/null
dpkg -l | grep -E 'linux-image|linux-headers' > /root/kernel-backup/pkgs.txt
dkms status > /root/kernel-backup/dkms.txt 2>&1
lsmod > /root/kernel-backup/lsmod.txt不要把 /boot 直接 cp 到 /root 下同一个磁盘就完事,如果整个盘坏了这些都白搭。真正重要的备份是「整机快照」或者「异地备份」,内核包本身其实能从仓库重新装回来。所以最关键的不是备份文件,而是记下当前内核的确切版本号,这句话决定你能不能回滚。
准备二:确认你有重启后能连上的通道。云主机要看厂商是否提供 VNC 控制台(阿里云叫「远程连接」,腾讯云叫「VNC 登录」)。如果只有 SSH,重启后网络没起来就彻底失联,只能提交工单。升级前先在控制台点一次进去看能打开,这一步不能省。
准备三:确认开机自启的服务清单。重启之后要验证的不只是内核版本,还有所有服务。把当前跑着的服务列表存下来:
systemctl list-units --type=service --state=running --no-pager \
| awk '{print $1}' | grep -v '^$' > /root/kernel-backup/services.txt
systemctl list-unit-files --state=enabled --no-pager \
| awk '{print $1}' > /root/kernel-backup/enabled.txt重启后逐条比对。很多人的事故就是这样发现的:机器起来了、网站能开,但 certbot 的 timer 没有 enable,两个月后证书过期。
准备四:安排一个能承受的时间窗。内核升级不是「一分钟的事」,如果新内核起不来,你需要在控制台里手动选旧内核,这个操作可能花掉二十分钟。放在凌晨流量谷底,并且提前告诉用户(如果有的话)。同时对搜索引擎来说,短暂几十秒的不可用影响很小,不必过度紧张。
五、升级:选对包、看清会删什么
Debian/Ubuntu 上最常见的升级路径是 apt upgrade。但在跑之前,先用 apt list --upgradable 看一眼,尤其注意有没有 linux-image-* 的「remove」动作。
apt update
apt list --upgradable 2>/dev/null | grep -E 'linux-(image|headers|modules)'如果看到要新增内核,就直接升:
DEBIAN_FRONTEND=noninteractive apt upgrade -y为什么要用 upgrade 而不是 full-upgrade(老叫法 dist-upgrade):upgrade 不会为了满足依赖而删除包,更保守;full-upgrade 可能会卸载旧内核、改动关键组件。内核升级这种场景,保守一点没错。要装新一轮的 HWE 内核,再单独 apt install linux-generic-hwe-xx.xx,别用 full-upgrade 一把梭。
升级过程中如果看到这样的提示,说明 apt 在自动清理旧内核:
The following packages will be REMOVED:
linux-image-5.10.0-20-amd64看到这一行就要停下来。这意味着升级完成后回滚目标没了。如果这是你不想要的,先 apt install 一个新的旧版本内核把回滚窗口留住,或者调整 /etc/apt/apt.conf.d/01autoremove-kernels 相关配置保留的份数(Ubuntu 上是 /etc/kernel/postinst.d/apt-auto-removal 的生成逻辑,一般是至少保留当前运行的 + 最新的一个)。
Debian 的 apt 会在装新内核时自动更新 GRUB。如果自动更新失败(比如 GRUB 配置被改过、或者 /boot 空间不够),会看到 grub-probe: error 或者 update-grub 报错。这类错误必须处理,不能忽略——它意味着重启后可能根本选不到新内核,或者 GRUB 菜单是坏的。手动跑一次并看输出:
update-grub
grub-install /dev/sda # 传统 BIOS,磁盘设备按实际改
# UEFI 系统一般是
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian还有一个经常出问题的点:initramfs 生成失败会打出 W: initramfs-tools configuration sets update-initramfs to not run 或者 mkinitramfs: failed。前者是有人把自动生成关了(update-initramfs=no 在 /etc/initramfs-tools/update-initramfs.conf),这时候新内核没有配套的 initrd,重启必然失败。必须手动生成:
update-initramfs -u -k all这一条是内核升级里最值得单独检查的一项,因为它失败时不会阻止升级,只会让重启失败。
六、重启前的最后检查单
重启是把不可逆的动作,按下之前过一遍这个单子,五分钟。
# 1. 新内核是否已安装并出现在 /boot
ls -l /boot/vmlinuz-*
# 2. 新内核是否有对应的 initrd
ls -l /boot/initrd.img-*
# 3. GRUB 菜单里能看到它
grep -E '^menuentry' /boot/grub/grub.cfg | head -20
# 4. 没有半完成的 dpkg 状态
dpkg --audit
# 5. /boot 还有空间
df -h /boot
# 6. sysctl 配置无报错
sysctl --system 2>&1 | grep -i 'cannot stat\|unknown key'六项全过再重启。dpkg --audit 输出为空是关键,如果有输出说明上一次 apt 中途被打断,必须先 dpkg --configure -a 修完。
顺便说一下 GRUB_DEFAULT 的取值,它决定了默认启动哪个内核。0 是菜单第一项(通常是最新内核),saved 是「上次成功启动的那个」。做内核升级时,建议临时改成 GRUB_DEFAULT=saved 并配合 GRUB_SAVEDEFAULT=true,这样如果新内核起不来,你在控制台里手工选了旧内核之后,下次重启会自动用旧内核,不会每次都卡在同一个坑里。改完记得 update-grub。
GRUB_DEFAULT=saved
GRUB_SAVEDEFAULT=true
GRUB_TIMEOUT=5GRUB_TIMEOUT 也要给足时间,改成 5 秒。默认的 0 秒会让 GRUB 直接启动,你想手工选的时候根本没机会。
七、重启后的十分钟验证
重启完成后,如果 SSH 能上去,说明最坏的情况已经过去了。但这时候不能掉以轻心,按下面的顺序逐项验证。
第一步:确认跑的是新内核,且没有 taint。
uname -r
cat /proc/sys/kernel/taintedtainted 为 0 是最干净的。非零说明加载了树外模块或者出现过内核告警,值是位掩码,具体含义查内核文档。加载了 ZFS 之类的树外模块会显示非零,那是正常的;如果是别的原因导致非零,要留意。
第二步:看启动日志有没有错误。
journalctl -b -p err --no-pager | head -40
dmesg --level=err,warn --no-pager | head -40重点关注:磁盘相关(EXT4-fs error、XFS 警告、blk_update_request: I/O error)、网络(link is not ready)、内存(Out of memory)。磁盘只读是最危险的一种状态——文件系统检测到错误会自动 remount 为只读,此时站点可能还能响应缓存页,但任何写操作都会失败。立刻检查 mount | grep ' ro,'。
第三步:确认关键模块都加载了。
lsmod | head -30
dkms status
modprobe -c | grep -c .如果之前 dkms status 有输出,这里要看到新内核对应的一行也是 installed。
第四步:逐个比对服务列表。
systemctl list-units --type=service --state=running --no-pager \
| awk '{print $1}' > /tmp/svc_now.txt
diff /root/kernel-backup/services.txt /tmp/svc_now.txt有差异不一定是问题(有些服务是临时的),但少了的那些要逐个人工确认。
第五步:从外部做一次真实请求验证。
curl -s -o /dev/null -w 'code=%{http_code} ttfb=%{time_starttransfer} total=%{time_total} ' "https://你的域名/"
curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total} ' "https://你的域名/某个动态页面"两条命令分别测静态与动态,前者验证 Nginx,后者验证 PHP 与数据库。只看「网站能打开」是不够的,很多人重启后发现图片能加载但登录不了,原因是 PHP 的某个扩展没编出来。
第六步:跑一次磁盘与内存的真实写操作。
dd if=/dev/zero of=/tmp/wtest bs=1M count=100 oflag=direct
rm -f /tmp/wtest
free -m
df -hoflag=direct 绕过页缓存,能暴露真实的磁盘问题。这一步如果慢得离谱(超过 10 秒),说明磁盘或驱动有问题。
八、回滚:在控制台里选旧内核
如果新内核起不来,表现通常是:SSH 连不上、ping 通但端口不通、或者控制台里能看到内核 panic。这时候唯一的办法是通过云厂商的 VNC/串口控制台。
操作流程:
第一步,进 VNC 控制台,重启机器,在 GRUB 菜单出现时按上下键停住。如果 GRUB_TIMEOUT=0,菜单会一闪而过,此时可以狂按 Shift(BIOS)或 Esc(UEFI)尝试唤出菜单,但成功率不高——所以前面把 timeout 改成 5 秒值得。
第二步,选「Advanced options for」下的旧内核条目启动。进去之后系统能起来,你就有了操作窗口。
第三步,把旧内核设为默认。最稳的方式是临时用 grub-reboot:
grep -E "^menuentry|^submenu" /boot/grub/grub.cfg
grub-reboot "1>2" # 含义:submenu 1 里的第 2 项,按实际菜单调整
update-grubgrub-reboot 只影响下一次启动,适合「先确认能起来」的场景。确认系统在旧内核上稳定之后,再做永久切换:
# 永久:把 GRUB_DEFAULT 改成 "1>2" 或者具体的内核版本串
vim /etc/default/grub
update-grub第四步,卸载问题内核,防止下次自动进入。
apt purge linux-image-新版本号 linux-headers-新版本号
apt-mark hold linux-image-旧版本号 linux-headers-旧版本号apt-mark hold 很重要:它防止下一次 apt upgrade 又把你有问题的那个内核装回来。等确认新版本修复了问题,再 apt-mark unhold。
还有一个提效手段:如果机器还能起来但网络有问题,可以先从控制台进去看 dmesg,找到具体的错误再决定是回滚还是修。
九、长期策略:别做「攒一年再一次升」
很多人对内核升级的态度是「能不动就不动」,结果攒了一年的更新一次性升级,跨度太大、变数太多,出问题的概率远高于每月升一次。
更合理的节奏是:
- 安全更新每月一次,用
unattended-upgrades自动装,但不要自动重启。自动装完内核后,用/var/run/reboot-required这个文件判断——它在需要重启时会出现。 - 重启窗口固定,比如每月第一个周日的凌晨,人工触发,走一遍本文第六、七节的检查单。
- 记录每次重启的内核版本,写在一个简单的文本文件里。半年后你会发现它能救你一次:某次性能下降,回头看刚好是内核版本变了的那一周。
关于 unattended-upgrades 有个具体配置要点:Linux 内核包默认是被「延迟更新」的(Unattended-Upgrade::Automatic-Reboot、Package-Blacklist 里的 linux-image),这既有好处(避免自动换内核)也有风险(安全问题没修)。如果你选择手动升内核,记得每月固定做一次,别让它一直挂着。
cat /var/run/reboot-required 2>/dev/null
cat /var/run/reboot-required.pkgs 2>/dev/null
apt list --upgradable 2>/dev/null | grep linux-image这三条命令可以放进每天的管理巡检里,一旦有内核待重启、或者有安全更新待装,就提醒自己安排窗口。
十、小结
内核升级的技术难度其实不高,几条 apt 命令而已。它之所以被很多人视为禁区,是因为失败的代价大、而且失败后没办法远程补救。所以整个流程的重心应该放在「让失败可恢复」上,而不是「让升级不出错」上——后者做不到,前者可以设计。
记住五件事:升级前确认 initramfs 是新内核的、GRUB timeout 给 5 秒且 GRUB_DEFAULT=saved、确认云控制台能登录、把当前内核版本号写下来、重启后先看 dmesg 和 mount 有没有只读。这五条做齐,即使新内核起不来,你也能在半小时内回到一个能用的系统上。
还有一条经验值得记:内核升级后一周内不要紧接着做其它大改动。一旦出问题,你能立刻分清是内核引起的还是新改动引起的。这种「一次只改一个变量」的纪律,比任何工具都更能防止事故。