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 的执行逻辑:

  1. 停止所有服务(关闭 dbus、network 等)
  1. 卸载根文件系统
  1. 触发 reboot(LINUX_REBOOT_CMD_KEXEC, 0) 系统调用
  1. 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 在生产环境中可靠运行,给出以下要点总结:

  1. 内核版本 ≥ 5.15:确保 EFI Stub 加载、IOMMU 安全处理、Secure Boot 签名验证完整支持。
  1. 严格管理设备卸载顺序:GPU、RDMA、NVMe 等高性能设备必须在 kexec 前卸载驱动并 unbind。
  1. IOMMU 显式重置:在复杂拓扑(多 GPU、多 NVMe)下,确保新内核的 IOMMU 初始化不会受残留映射影响。
  1. TPM Remote Attestation 更新:如果使用远程证明,需将 kexec PCR 变更注册为合法事件。
  1. 滚动策略与 livepatch 结合:先 livepatch 缓解短期风险,再在维护窗口内 kexec 完成完整升级。
  1. 验证流程:在 staging 环境中充分测试 kexec 循环,验证所有外设在新内核下的正常 re-probing。

在 AI 算力租赁和大规模模型推理服务中,每一秒停机都意味着实际收入损失。掌握 kexec 工程实践,将节点维护从"分钟级"带入"秒级",是基础设施团队必须拥有的硬核能力。


作者注: 本文基于 Linux 6.x 内核源码(重点分析 `kernel/kexec.c`、`arch/x86/kernel/machine_kexec_64.c`、`arch/x86/purgatory/`)以及生产环境中的实际调优经验编写。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部