kdump 内核崩溃转储实战:服务器半夜重启后如何用 vmcore 与 crash 定位真凶

服务器半夜自己重启了,你拿什么查

很多个人站长都遇到过这种情况:早上打开监控,发现服务器在凌晨 3 点多重启过一次,uptime 只有几个小时,业务看起来恢复了,但没人知道当时到底发生了什么。你翻 /var/log/messages,日志在重启点戛然而止,没有任何 Oops、没有 panic 记录,唯一的线索是 last reboot 里那个突兀的时间戳。这种情况八成是内核崩溃(kernel panic)触发了自动重启,而系统默认不会把崩溃现场保留下来——内存里的证据随着重启灰飞烟灭。

kdump 就是解决这个问题的机制。它在系统启动时预先保留一块内存,专门放一个"备胎内核"(crash kernel)。当生产内核崩溃时,机器迅速切换到备胎内核,把崩溃内核的完整内存镜像(vmcore)写进磁盘,然后再重启。有了 vmcore,你就能用 crash 工具像法医一样回放现场:谁在跑、栈是什么样的、是哪个驱动或哪段代码把系统带崩的。

本文用一个真实可复现的场景,把 kdump 从内核参数、内存预留、vmcore 抓取到 crash 分析的全流程走一遍。适合已经会用 dmesg、journalctl 做基础排障,但遇到"内核级崩溃"就束手无策的站长。

第一步:确认内核有没有编译进 kdump 支持

绝大多数发行版(Debian、Ubuntu、CentOS/Rocky)的官方内核都自带 kdump 支持,但要确认 kexec 相关选项确实开启。真正决定能不能落盘的是 CONFIG_CRASH_DUMP 和 CONFIG_KEXEC,前者负责 crash kernel 的加载,后者负责"不停机切换内核"这个动作。

# 看当前内核是否带 kexec / crash dump 支持
grep -E 'CONFIG_KEXEC|CONFIG_CRASH_DUMP|CONFIG_PROC_VMCORE' /boot/config-$(uname -r)

# 期望看到:
# CONFIG_KEXEC=y
# CONFIG_KEXEC_FILE=y
# CONFIG_CRASH_DUMP=y
# CONFIG_PROC_VMCORE=y

如果 /boot/config- 文件不存在(有些精简镜像会删),可以退而检查运行时的 sysfs 节点:ls /sys/kernel/kexec_crash_loaded,这个文件存在说明内核支持 crash kernel 加载。Debian 系安装 kexec-tools 才能让 kexec -p 真正把备胎内核塞进预留内存。

第二步:给 crash kernel 预留内存

这是整个流程里最容易翻车的地方。kdump 需要在系统启动时从物理内存里划走一块给备胎内核长期占用,划多大有讲究:太小会导致崩溃时来不及写 vmcore,太大又白白浪费内存,在 1GB 内存的小机器上甚至会让系统起不来。

在 GRUB 里通过 crashkernel= 参数预留。现代内核推荐用 crashkernel=auto,它会根据物理内存总量自动算出一个合理值;但 auto 在老内核或某些 VPS 上不可靠,建议写死区间。

# 编辑 /etc/default/grub,在现有参数后追加
GRUB_CMDLINE_LINUX_DEFAULT="quiet crashkernel=256M-:128M"

# 上面这行的含义:
#  内存 < 256M   → 不预留(小机器直接放弃 kdump)
#  内存 >= 256M  → 预留 128M 给 crash kernel

# 更新 GRUB 配置
update-grub            # Debian/Ubuntu
# grub2-mkconfig -o /boot/grub2/grub.cfg   # RHEL 系

预留内存更大的机器(8GB 以上)可以给 256M 甚至 512M,因为要容纳的内存镜像更大。改完必须重启才生效。重启后验证:

# 检查预留是否成功
dmesg | grep -i crashkernel
cat /proc/cmdline | tr ' ' '\n' | grep crashkernel

# 检查 crash 内核是否已加载就绪(1 = 已加载)
cat /sys/kernel/kexec_crash_loaded

# 更直观的:确认 crash 内核占用的内存区间
cat /proc/iomem | grep -i crash

如果 kexec_crash_loaded 是 0 或者文件不存在,说明 crash kernel 没能加载,此时即使崩溃也抓不到 vmcore。最常见原因是内存预留不足,或者 VPS 的虚拟化层不支持 kdump(部分 OpenVZ/LXC 容器天然不支持,KVM 一般支持)。

第三步:配置 kdump 抓取参数与落盘位置

Debian/Ubuntu 系的配置文件是 /etc/default/kdump-tools,RHEL 系是 /etc/kdump.conf。核心要配三件事:vmcore 存哪里、要不要压缩、崩溃后是否自动重启。

# /etc/default/kdump-tools 关键项(Debian/Ubuntu)
USE_KDUMP=1
# vmcore 落盘目录,别放在内存盘(tmpfs)上,否则等于没存
KDUMP_COREDIR="/var/crash"
# 压缩保存,能省一大半空间
KDUMP_SAVEDIR="file:///var/crash"
# 崩溃后自动重启
KDUMP_IMMEDIATE_REBOOT="yes"

# 启用并启动服务
systemctl enable kdump-tools
systemctl start kdump-tools

# 查看备胎内核是否加载成功
kdump-config show

有一点必须提醒:/var/crash 所在的分区要留足空间。一个 2GB 内存的机器崩溃时 vmcore 压缩后可能还有几百 MB,如果 /var 分区本来就快满,写入会中途失败,你依然拿不到完整现场。建议单独确认 df -h /var/crash,并在 /etc/fstab 里给 crash 目录配好独立的配额或挂载点。

另外,kdump 抓取过程本身是"紧急模式",它会先把 vmcore 写进一个临时内存缓冲再慢慢往磁盘搬。如果内存预留太小,缓冲区不够,抓取会失败。这也是为什么前面强调 crashkernel= 不能划得太抠。

第四步:不要真的把服务器搞崩,用手动触发来演练

配完 kdump 最忌讳的就是"配完就等下次崩溃",因为你根本不知道它到底能不能用。内核提供了手动触发崩溃的开关,可以主动验证整条链路。这是一种会立刻重启机器的操作,务必在测试机或业务低峰期做。

# !危险操作:这条命令会让内核立即 panic 并重启
echo c > /proc/sysrq-trigger

# 如果开启了 sysrq 但想更"温柔"地测试抓取,先确认:
cat /proc/sys/kernel/sysrq      # 需要非 0

机器重启回来后,去 /var/crash 看有没有生成目录,里面通常有 vmcore 和一份 vmcore-dmesg.txt(崩溃瞬间的内核日志,非常关键,即使没有 crash 工具也能读)。如果这里空空如也,说明抓取链路断了,回头检查前面三步。

在生产中触发内核崩溃除了 sysrq,还有几种真实成因值得留意:驱动 bug(尤其是自定义的网卡/存储驱动)、硬件内存故障、内核模块与内核版本不匹配、以及 mmap 到坏页时的 Oops。日志里如果出现 BUG: unable to handle kernel NULL pointer dereference 或 kernel panic - not syncing 这类字眼,就是典型的内核级崩溃。

第五步:用 crash 工具读 vmcore,定位崩溃根因

拿到 vmcore 后,需要匹配的内核镜像和调试符号才能分析。Debian/Ubuntu 要装对应版本的 linux-image-*-dbg,RHEL 系装 kernel-debuginfo。没有符号表,crash 只能读出地址读不出函数名,等于白抓。

# 安装 crash 工具和对应内核的调试符号(版本必须与崩溃内核一致)
apt install crash
apt install linux-image-$(uname -r)-dbg

# 进入 crash 交互式分析
crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/*/vmcore

进入 crash 的交互界面后,几条命令就能把现场拼出来:

# 1. 先看崩溃摘要:最直接的结论,谁触发的、在哪一行
crash> bt

# 2. 看所有 CPU 的调用栈,定位是哪颗核崩的
crash> bt -a

# 3. 查看崩溃进程是谁
crash> ps | head
crash> sys | head

# 4. 看内核日志缓冲,等价于崩溃瞬间的 dmesg
crash> log | tail -100

# 5. 如果是 OOM 或内存问题,看内存统计
crash> kmem -i

# 6. 退出
crash> quit

实战中,bt 输出最上面那个"当前运行"的栈往往不是元凶,真正有用的是看 Oops 提示里给出的 RIP 地址对应的函数,以及调用它的上一层。比如栈里出现 ext4_file_write_iter 之后跟了一串文件系统函数栈,再往下崩在某个驱动,问题大概率在存储层。如果崩溃进程是 kswapd0、kcompactd 这类内核线程,则往往是内存压力或透明大页相关的问题。

没有 vmcore 时的降级排查:vmcore-dmesg 与 pstore

有些场景 vmcore 抓不完整,但 vmcore-dmesg.txt 一般都能留下,它记录了崩溃前最后几百行内核日志,价值极高。直接读它往往就能看出是哪个子系统先报错。此外还有 pstore(persistent storage)机制,利用 ACPI ERST 或 EFI 变量在重启间保留少量崩溃信息,即使 kdump 完全没配也能留个念想:

# 如果有 pstore,崩溃前的日志会留在这里
ls -la /sys/fs/pstore/
cat /sys/fs/pstore/dmesg-* 2>/dev/null

# 崩溃日志快照文件夹(Debian 系)
ls /var/crash/*/vmcore-dmesg.txt
tail -200 /var/crash/*/vmcore-dmesg.txt

kdump 在 VPS 上的现实与取舍

对个人站长的低配 VPS 来说,kdump 要权衡两件事。第一是内存成本:预留 128M 在 1GB 的机器上就是 12.5%,如果业务本来就吃紧,这笔开销不小。第二是虚拟化支持:KVM 架构的 VPS 通常可用,但廉价 VPS 用的容器虚拟化根本不给你内核权限,kdump 无从谈起——这种情况下你的"服务器崩溃"应该去翻宿主机的状态,而不是在容器里折腾。

务实的策略是:内存 2GB 以上、且确认是 KVM 的机器上开启 kdump,把 crashkernel 设为 128M,落盘目录留出 2GB 空间,然后手动触发一次验证。1GB 以下或容器架构的机器,放弃 kdump,转而在应用层做好健康检查和自动拉起(systemd Restart=on-failure、监控探活),把"重启"当成正常现象来设计,而不是指望事后取证。

常见故障速查

kexec_crash_loaded 一直为 0:crashkernel 预留太小或参数写错,检查 /proc/cmdline 是否真的带上了参数,改完 GRUB 后是否 update-grub 并重启。
崩溃后 /var/crash 为空:分区空间不足,或者 vmcore 落盘目录被配到了 tmpfs 上。
crash 打开 vmcore 报 "cannot find vmlinux":调试符号包没装或内核版本与崩溃时不一致,崩溃后如果升级过内核就会踩这个坑。
手动 echo c 之后没重启:/proc/sys/kernel/sysrq 为 0,或 kdump 服务未运行导致 panic 处理未挂接。

FAQ

Q:kdump 会影响服务器性能吗?
A:正常运行几乎无影响,它唯一代价是预留的那块内存平时不参与分配。真正的开销只在崩溃抓取的那几十秒。

Q:容器里能配 kdump 吗?
A:不能。容器共享宿主机内核,看不到也控制不了 kexec,这类平台的崩溃排查要交给服务商。

Q:vmcore 文件很大,能不能只存关键部分?
A:可以在 /etc/default/kdump-tools 里减去无关内存区间(KDUMP_SAVEDIR 配合过滤参数),但这属于进阶操作,个人站建议直接开压缩存全量,省心。

Last modification:October 1st, 2026 at 10:23 pm

Leave a Comment