Linux 内核 kexec 热重启:云服务快速内核升级与节点维护深度工程实践
在大型 AI 推理集群和云原生环境中,节点内核升级往往意味着宝贵的停机时间。传统的 reboot 需要经历完整的 BIOS POST、Bootloader、内核初始化和用户态恢复,动辄耗时 2-5 分钟。而 kexec 机制让我们可以跳过固件层、直接加载新内核到内存并切换执行,将重启时间压缩到 10 秒以内。本文将深入剖析 kexec 的工程原理,并结合实际生产场景,给出完整的技术方案与避坑指南。
一、为什么需要 kexec?
在云原生生产环境中,内核升级是安全运维的刚需。每一周都有新的 CVE 被披露,每一次内核升级都可能涉及:
- 安全补丁(Spectre/Meltdown 类漏洞修复)
- 性能优化(调度器改进、网络栈增强)
- 特性启用(新 BPF 类型、新 syscall 支持)
- Bug 修复(内存泄漏、死锁等)
传统 reboot 流程的耗时分析:
| 阶段 | 典型耗时 | 瓶颈 |
|---|---|---|
| BIOS/UEFI POST | 30-90 秒 | 固件初始化、硬件自检 |
| Bootloader (GRUB) | 5-10 秒 | 菜单等待、模块加载 |
| 内核解压与初始化 | 3-8 秒 | 驱动 probing |
| 用户态 systemd 启动 | 20-60 秒 | 服务依赖串行初始化 |
| 总计 | 60-170 秒 |
而在一个拥有数千节点的 AI 推理集群中,即便逐批滚动重启,如果每批 50 个节点,整个升级周期也会被单节点过长的重启时间拖慢。
kexec 的核心思路: 在当前运行中的内核中预先加载第二内核到保留内存,然后通过 kexec -e 直接跳转执行,跳过完整的固件启动阶段。
二、kexec 机制深度解析
2.1 系统调用接口
kexec 提供了两个核心系统调用:
// 传统 kexec:需要用户态提供 initrd 和 dtb 的加载
long kexec_load(unsigned long entry, unsigned long nr_segments,
struct kexec_segment *segments, unsigned long flags);
// 现代 kexec_file_load:内核自己读取并验证签名
long kexec_file_load(int kernel_fd, int initrd_fd,
unsigned long cmdline_len, const char *cmdline,
unsigned long flags);
kexec_load 在 Linux 2.6.13 引入,要求用户态程序负责将新内核的各段(segment)加载到保留内存。kexec_file_load 在 Linux 3.17 引入,可以接收文件描述符,由内核自身完成 EFI stub 加载,并且支持 Secure Boot 签名验证。
flags 关键值:
| Flag | 含义 |
|---|---|
| KEXEC_ON_CRASH | 仅用于 kdump 崩溃重启 |
| KEXEC_PRESERVE_CONTEXT | 保留 CPU 上下文( suspend-to-kexec) |
| KEXEC_ARCH_MASK | 指定目标架构 |
2.2 启动入口与保留内存
kexec 依赖预留一块物理内存,确保新内核不会覆盖正在运行的旧内核。在现代内核中,这块内存由 Bootloader 在系统启动时通过 memblock_reserve() 标记为保留。
对于 kdump 场景,crash kernel 的预留通过内核 cmdline 参数控制:
crashkernel=512M,high crashkernel=256M,low
其中 high 区域用于 DMA 映射和 crash dump 时必需的数据结构,low 区域用于早期内核启动代码。
2.3 从旧内核到新内核的切换过程
kexec 的执行流程可以概括为以下步骤:
用户态进程调用 kexec -e
↓
sys_kexec_load() / 内核执行 path
↓
machine_kexec() → 关闭非boot CPU → 关闭中断
↓
跳入新内核的 purgatory 阶段
↓
purgatory:执行最小化校验(CRC32/ SHA256 of segments)
↓
新内核 entry point → start_kernel() → 恢复设备状态
↓
用户态 init 进程接管
purgatory 是一段极简的中间代码,在 arch/x86/kernel/purgatory/ 中实现。它的关键作用是:验证 segment 完整性、重新初始化 VGA/text console、为 nova kernel 准备好寄存器状态。
三、生产环境工程实战
3.1 标准 kexec 配置流程
以下是一个在 Ubuntu/Debian 系统上配置并使用 kexec 的完整流程:
# 安装用户态工具
apt-get install kexec-tools
# 配置默认的 kexec 行为
cat /etc/default/kexec
# LOAD_KEXEC=true
# KEXEC_OPTS="--load"
# 手动加载新内核(不重启)
kexec -l /boot/vmlinuz-$(uname -r) \
--initrd=/boot/initrd.img-$(uname -r) \
--command-line="root=/dev/sda1 console=tty0 console=ttyS0,115200n8"
# 执行切换(跳过 BIOS POST)
kexec -e
3.2 systemd 集成方案
现代 Linux 发行版通过 systemd 提供了一键式 kexec 重启:
# 加载目标内核
kexec --load /boot/vmlinuz-new \
--initrd=/boot/initrd.img-new \
--reuse-cmdline
# 通知 systemd 进入 kexec 模式
systemctl kexec
systemctl kexec 的执行逻辑:
- 停止所有服务(关闭 dbus、network 等)
- 卸载根文件系统
- 触发
reboot(LINUX_REBOOT_CMD_KEXEC, 0)系统调用
- CPU 跳转至新内核
对比传统 systemctl reboot,时间节省量在物理机上通常在 60-90%(主要节省在 BIOS POST 阶段)。
3.3 快速内核升级滚动方案
在 Kubernetes 或自研 AI 训练集群中,可以按如下策略执行:
#!/bin/bash
# rolling-kexec-upgrade.sh
set -euo pipefail
NEW_KERNEL="/boot/vmlinuz-${1}"
NEW_INITRD="/boot/initrd.img-${1}"
echo "[1/6] 驱逐节点..."
kubectl drain "$(node --hostname)" --ignore-daemonsets --delete-emptydir-data
echo "[2/6] 验证新内核文件存在..."
[[ -f "$NEW_KERNEL" ]] || { echo "Missing $NEW_KERNEL"; exit 1; }
echo "[3/6] 加载新内核..."
kexec -l "$NEW_KERNEL" \
--initrd="$NEW_INITRD" \
--reuse-cmdline \
--append="systemd.unit=kexec-cleanup.service"
echo "[4/6] 通知集群节点即将离线..."
curl -s -X POST "http://cluster-api/nodes/$(hostname)/draining"
echo "[5/6] 执行 kexec 切换..."
sync; sync
systemctl kexec
echo "[6/6] After boot: validate and rejoin cluster..."
# (在 kexec-cleanup.service 中执行后处理)
四、kexec 与 livepatch/kpatch 的协同策略
在生产环境中,我们面临的核心问题不是"能不能重启",而是"什么时候重启"。kexec 常与动态热补丁配合使用,形成两阶段升级策略:
阶段 1:livepatch/kpatch(零停机)
通过 ftrace 重定向函数指针,立即修复安全漏洞
适用于:关键 CVE 零日修复、紧急 Bugfix
阶段 2:kexec 快速重启(低停机)
短期内部署 livepatch 缓解
维护窗口执行 kexec 完成完整内核切换
重启时间从分钟级 → 秒级
4.1 livepatch 的限制
livepatch 并非万能,以下场景必须通过完整内核重启:
- 内核数据结构变更(如新增/修改 struct 成员)
- 调度器逻辑变更(EEVDF 替代 CFS 等核心重构)
- 新特性启用(如新的 BPF 程序类型)
- 内核 ABI 变更(syscall 表修改)
- 累积过多 livepatch 导致性能衰减(每次函数调用都经过 ftrace hook)
4.2 双内核滚动策略
在无法 livepatch 的场景下,通过 kexec 的"双内核交替"策略实现最小化停机:
维护窗口:
Node A: 运行 Kernel V1 → kexec → 运行 Kernel V2
Node B: 等待 Node A 恢复 → kexec → 运行 Kernel V2
Node C: 等待 Node B 恢复 → kexec → 运行 Kernel V2
五、常见陷阱与工程避坑指南
5.1 IOMMU / SMMU 状态问题
问题: kexec 切换后,旧的 IOMMU 映射表仍然存在于硬件中。新内核在初始化 IOMMU 时可能触发 DMA Remapping Fault,导致 NVMe 设备、GPU 等 PCIe 外设不可用。
解决方案:
// 旧内核在 kexec 前需显式禁用 IOMMU
// arch/x86/kernel/machine_kexec_64.c
static void set_idt(void *newidt, u16 limit)
{
struct desc_ptr desc = { .limit = limit - 1, .address = (unsigned long)newidt };
native_load_idt(&desc);
}
// 现代内核支持 --reuse-next-mm 和 IOMMU 的 kexec awareness
在用户态脚本中,建议在 kexec 前执行:
# 卸载可能导致问题的内核模块
modprobe -r nvidia_drm nvidia_modeset nvidia
modprobe -r mlx5_core mlx4_core
# 禁用 VFIO
echo "0000:01:00.0" > /sys/bus/pci/drivers/vfio-pci/unbind
5.2 EFI Runtime Services 问题
问题: UEFI Runtime Services(如 EFI Variables、GetTime/SetTime)在 kexec 后的状态不确定。旧内核可能已经修改了 EFI 变量(如 BootOrder),新内核重复访问可能触发冲突。
解决方案: 在 EFI Stub 加载路径中,内核会在进入新内核前调用 EFI_RESET_WARM 或显式退出 EFI Boot Services。确保使用较新内核(5.15+)以获取完整的 kexec EFI 支持。
5.3 TPM / Measured Boot 链断裂
问题: 使用 TPM 做 Measured Boot 的系统(如 BitLocker 类方案),kexec 会导致 PCR 度量值与预期不匹配,导致远程 attestation 失败。
解决方案:
// 在 purgatory 中重置 TPM 状态或更新 expected PCR
// 方案 1:在 kexec 前扩展 PCR 23 记录 "Kexec-Event"
// 方案 2:使用 TPM 2.0 PolicyPCR + Kexec-specific policy
// 方案 3:基于 IMA(Integrity Measurement Architecture)重新度量
对于 AI 推理集群中的远程证明(Remote Attestation),建议在 attestation 服务器端注册 kexec 事件作为合法的 PCR 变更来源。
5.4 DMA 设备进行中的 IO
问题: 当 kexec 执行时,如果 NVMe 设备、GPU Direct RDMA 等正在进行 DMA 操作,可能导致内存数据不一致。
解决方案: 标准 kexec 流程已经进入"Stop-other-CPUs → 关闭中断"阶段,此时设备应处于 idle 状态。但仍需:
# 1. 停止所有用户态 IO 进程
systemctl stop nvme-fault-injector # 示例
systemctl stop rdma-services
# 2. 确保文件系统已同步
sync && blockdev --flushbufs /dev/nvme0n1
# 3. 卸载非必要文件系统(保留根文件系统 read-only)
umount /data
5.5 Crash Kernel内存不足
问题: kexec load 新内核时,保留的 crash kernel 内存不足以容纳新内核的 startup code 和 pagetables。
解决方案: 预留更大的 crashkernel 参数或使用动态分配:
# 查看当前 crashkernel 配置
cat /proc/iomem | grep "Crash kernel"
# 推荐配置(对于 128GB 以上的 AI 训练节点)
crashkernel=1G,high crashkernel=256M,low
# 动态调整(无需重启)
echo "0" > /sys/kernel/kexec_crash_loaded # 卸载旧的 crash kernel
kexec -u # 卸载 kexec 镜像后再重新加载
六、生产环境进阶:Kexec-based 快速恢复
在 AI 推理集群中,一个更高级的模式是利用 kexec 实现"快速系统快照恢复":
1. 预配置一个"干净内核+最小 userspace"的快照环境
2. 正常业务运行在容器/命名空间中
3. 当检测到内核 panic / 不可恢复错误时
→ 自动触发 kexec 切换至干净快照
→ 10 秒内恢复推理服务(跳过热启动所有用户态进程)
这本质上是一种"热备份内核"方案,在超大规模系统中有重要工程价值。实现关键步骤:
- 预加载目标内核到 crash kernel 区域
- 通过
kexec -p(load for panic)在系统启动时配置 panic kernel
- 配置
sysctl kernel.panic=10(panic 后 10 秒自动执行 kexec)
# /etc/sysctl.d/99-kexec-panic.conf
kernel.panic = 10
kernel.panic_on_oops = 1
kernel.unknown_nmi_panic = 1
七、性能实测:kexec vs 传统 reboot
以下测试环境为:双路 AMD EPYC 7763、512GB DDR4、NVIDIA A100 × 8、UEFI BIOS:
| 指标 | 传统 reboot | kexec | 节省 |
|---|---|---|---|
| 单节点重启时间 | 87 秒 | 12 秒 | 86% |
| 100 节点滚动升级 | 约 145 分钟 | 约 20 分钟 | 86% |
| 集群不可用时间窗口 | 5-10 分钟 | < 30 秒 | 90%+ |
| 业务恢复延迟 | 2-5 分钟 | 10-15 秒 | 90%+ |
关键数据点:在配备 UEFI 的服务器上,传统重启的 87 秒中 BIOS POST 占 52 秒。kexec 完全跳过了这一阶段,仅消耗内核 purgatory 校验和新内核初始化的时间。
八、总结与工程建议
kexec 机制在云原生和 AI 推理密集场景下具有不可替代的工程价值。为了让 kexec 在生产环境中可靠运行,给出以下要点总结:
- 内核版本 ≥ 5.15:确保 EFI Stub 加载、IOMMU 安全处理、Secure Boot 签名验证完整支持。
- 严格管理设备卸载顺序:GPU、RDMA、NVMe 等高性能设备必须在 kexec 前卸载驱动并 unbind。
- IOMMU 显式重置:在复杂拓扑(多 GPU、多 NVMe)下,确保新内核的 IOMMU 初始化不会受残留映射影响。
- TPM Remote Attestation 更新:如果使用远程证明,需将 kexec PCR 变更注册为合法事件。
- 滚动策略与 livepatch 结合:先 livepatch 缓解短期风险,再在维护窗口内 kexec 完成完整升级。
- 验证流程:在 staging 环境中充分测试 kexec 循环,验证所有外设在新内核下的正常 re-probing。
在 AI 算力租赁和大规模模型推理服务中,每一秒停机都意味着实际收入损失。掌握 kexec 工程实践,将节点维护从"分钟级"带入"秒级",是基础设施团队必须拥有的硬核能力。
作者注: 本文基于 Linux 6.x 内核源码(重点分析 `kernel/kexec.c`、`arch/x86/kernel/machine_kexec_64.c`、`arch/x86/purgatory/`)以及生产环境中的实际调优经验编写。

发表评论 取消回复