概述

凌晨三点,你被告警吵醒,登录服务器发现屏幕上只有一行 Kernel panic - not syncing: Fatal exception,然后系统就重启了。等你好不容易连上去,崩溃现场什么都没留下——没有日志,没有 core dump,没有调用栈。你只能对着一句 systemd-logind: System is going down 发呆。

这种场景,每个干过几年运维的人都遇到过。内核崩溃本身已经够头疼了,但更头疼的是崩溃后什么都抓不到,问题根本没法定位。

kdump 就是解决这个问题的。它相当于给 Linux 服务器装了一台飞行记录仪——飞机坠毁时,黑匣子能告诉你最后几秒发生了什么;内核崩溃时,kdump 能把崩溃瞬间的完整内存状态保存下来,让你事后用 crash 工具逐帧分析。

这篇文章不讲虚的,从 kdump 的工作原理到生产环境的完整配置流程,再到 crash 工具的实际分析操作,全部覆盖。读完之后,你应该能在自己的服务器上搭一套可靠的崩溃捕获系统。

kdump 是怎么工作的

双内核机制

要理解 kdump,先搞懂一个关键概念:崩溃时主内核已经不可信了。你不能指望一个已经 panic 的内核去把自己的内存好好保存下来——它连正常执行代码都做不到。

kdump 的思路很巧妙:系统启动时,提前预留一块物理内存,在里面加载一个精简的"捕获内核"(capture kernel,也叫第二内核)。主内核正常运行时,这块内存被隔离,谁都不许动。一旦主内核崩溃,kexec 机制会直接把 CPU 控制权交给捕获内核——不走 BIOS,不重启硬件,直接在预留内存里启动。捕获内核接管后,主内核的内存内容还完好无损地躺在那里,捕获内核把它打包成 vmcore 文件,写到磁盘上。

整个过程像这样:

┌──────────────────────────────────────────────┐
              物理内存布局                       
                                              
  ┌────────────────┐  ┌───────────────────┐  
     主内核区域        预留内存区域        
    (正常运行)         (crashkernel)      
                                         
    用户进程           捕获内核 +         
    内核模块           initramfs          
    页缓存...         (待命状态)         
  └────────────────┘  └───────────────────┘  
                                              
  主内核崩溃  kexec 切换  捕获内核启动       
   读取 /proc/vmcore  写入 /var/crash/      
└──────────────────────────────────────────────┘

kexec:不开机的热切换

kexec 是 kdump 的底层支撑技术。它允许在不经过 BIOS 启动流程的情况下,直接把一个内核镜像加载到内存并跳转执行。正常重启要走 BIOS POST、GRUB 引导、内核解压这一长串流程,可能要几分钟。kexec 跳过了这些,直接从一个内核跳到另一个内核,几毫秒的事。

这对崩溃捕获至关重要:主内核崩溃后,内存里的数据如果不赶紧保存,可能随着硬件复位而丢失。kexec 的快速切换让捕获内核能在内存数据被破坏之前就把现场冻结下来。

参考 Red Hat 内核崩溃转储指南,kdump 的工作流程可以拆成几步:

  1. 系统启动时,通过 crashkernel 内核参数预留内存
  2. 主内核启动后,kdump 服务把捕获内核加载到预留内存
  3. 主内核正常运行,捕获内核处于待命状态
  4. 主内核崩溃,kexec 触发捕获内核启动
  5. 捕获内核挂载文件系统,将主内核内存转储为 vmcore
  6. 转储完成后,捕获内核重启系统

生产环境配置实战

安装必要组件

CentOS / RHEL 系:

# 安装 kexec-tools(含 kdump 服务和 kexec 工具)
sudo dnf install kexec-tools

# 安装 crash 工具(用于事后分析 vmcore)
sudo dnf install crash

# 安装内核调试信息包(crash 分析需要,包名跟内核版本对应)
sudo dnf install kernel-debuginfo-$(uname -r)

Ubuntu / Debian 系:

sudo apt install kexec-tools makedumpfile crash
sudo apt install linux-image-$(uname -r)-dbg

安装 kexec-tools 时,Ubuntu 会弹一个对话框问你要不要在系统崩溃时自动触发 kdump,选 Yes。

预留内存:crashkernel 参数

这是配置 kdump 最关键的一步,也是最容易出错的地方。

crashkernel 是内核启动参数,用来告诉内核"给我留多少内存给捕获内核用"。它在 GRUB 配置里设置。

编辑 /etc/default/grub

# 编辑 GRUB 配置
sudo vi /etc/default/grub

# 在 GRUB_CMDLINE_LINUX 行中添加 crashkernel 参数
# 常见写法:
#   crashkernel=256M              固定预留 256M
#   crashkernel=auto              自动计算(RHEL 系支持,但有时不够准)
#   crashkernel=128M@64M          预留 128M,起始地址 64M
#   crashkernel=2G-16G:256M,16G-64G:512M  按系统内存大小分级预留
GRUB_CMDLINE_LINUX="crashkernel=256M rd.lvm.lv=centos/root rhgb quiet"

预留多少内存? 这是最常被问的问题。我的经验值:

系统物理内存推荐 crashkernel说明
4G - 8G128M - 256M小内存机器,尽量少预留
8G - 16G256M常见配置,够用
16G - 64G256M - 512M中等规模,留点余量
64G - 256G512M - 768M大内存机器,vmcore 也大
256G+768M - 1G超大内存,可能需要 1G+

实际需要多少取决于内核版本、加载的模块数量、以及你用 makedumpfile 做了多少过滤。最佳做法是先用推荐值配置,然后实际触发一次崩溃测试,看捕获内核能不能正常启动并完成转储。

更新 GRUB 并重启:

# RHEL/CentOS 7 系
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

# RHEL/CentOS 8+ / Fedora 系
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# 或者(EFI 系统)
sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

# Ubuntu / Debian 系
sudo update-grub

# 重启使 crashkernel 预留生效
sudo reboot

重启后验证预留是否成功:

# 检查内核启动参数是否包含 crashkernel
cat /proc/cmdline | grep crashkernel

# 检查预留内存区域
dmesg | grep -i "crashkernel"
# 预期输出类似:
# Reserving 256MB of memory at 800MB for crashkernel

# 检查 kdump 服务状态
systemctl status kdump.service

kdump 配置文件

kdump 的主配置文件是 /etc/kdump.conf(RHEL/CentOS)或 /etc/default/kdump-tools(Ubuntu)。这里控制转储文件的存储位置、压缩方式、通知脚本等。

一个生产级配置示例:

# /etc/kdump.conf

# vmcore 存储路径(默认 /var/crash)
path /var/crash

# 核心采集器:makedumpfile
# -d 8: 过滤掉以下类型的页面(按位掩码):
#   1=zero pages, 2=cache pages, 4=cache private, 8=user data,
#   16=free pages
# -c: 压缩输出
# 用 -d 31 可以过滤掉所有可过滤的页面,显著减小 vmcore 大小
core_collector makedumpfile -l --message-level 1 -d 31

# 转储完成后执行的动作(默认 reboot)
# 可选:reboot / halt / poweroff / shell
# shell 模式在转储后进入 shell,方便手动检查
default reboot

# 转储目标覆盖(可选)
# 如果 /var/crash 在小分区上,可以指定写到其他分区
# ext4 /dev/sdb1
# nfs my.nfs.server:/export/crashdumps

# 转储失败时的行为
# failure_action 选项:continue / halt / reboot / shell
# 建议生产环境用 shell,转储失败时至少给你一个排查机会
failure_action shell

关于 makedumpfile 的过滤级别,这里展开说明一下。vmcore 如果直接把所有内存原样保存,一个 64G 内存的机器就会生成 64G 的转储文件,写入磁盘要很久,还可能撑爆 /var/crash 分区。makedumpfile 通过分析内核的页表结构,过滤掉不需要的页面:

过滤标志过滤内容节省空间
-d 1全零页面5-15%
-d 2内核页缓存10-30%
-d 4私有页缓存5-10%
-d 8用户态进程数据30-60%
-d 16空闲页面20-50%
-d 31以上全部通常减小 70-90%

生产环境推荐 -d 31,除非你需要在 vmcore 里检查某个用户态进程的内存内容。加上 -l 启用压缩,vmcore 通常能从原始内存大小的 100% 压缩到 5-15%。

配置 SSH 远程转储

如果你的服务器本地磁盘空间紧张,或者崩溃后本地文件系统可能不可用(比如根分区是 LVM 且出了问题),可以把 vmcore 直接转储到远程服务器:

# /etc/kdump.conf

# SSH 远程转储
ssh root@crash-collector.internal
sshkey /root/.ssh/id_rsa
path /data/crashdumps
core_collector makedumpfile -l --message-level 1 -d 31
default reboot

配置远程转储时,确保:

  1. 捕获内核的 initramfs 里包含了 SSH 客户端和网络驱动
  2. 目标服务器已添加到 crash-collector 的 authorized_keys
  3. 网络配置在 initramfs 环境下可用(可能需要静态 IP 或 DHCP 配置)

重新生成 initramfs 使配置生效:

# RHEL/CentOS
sudo kdumpctl restart

# Ubuntu/Debian
sudo systemctl restart kdump-tools

验证 kdump 服务

# 查看服务状态
systemctl status kdump

# 检查捕获内核是否已加载到预留内存
# 方法1:查看 kexec 加载状态
kexec -p -l /boot/vmlinuz-$(uname -r) 2>&1
# 如果已加载,会提示 "kexec_load failed: File exists" 或类似信息

# 方法2:查看 kdump 内核加载日志
dmesg | grep -i kdump
# 预期输出:
# kdump: Loaded kdump kernel at 0x...
# kdump: kexec: kdump kernel loaded

# 方法3:检查 /sys/kernel/kexec_loaded
cat /sys/kernel/kexec_loaded
# 0 = 未加载, 1 = 已加载(kdump 正常工作时应为 1)

# 检查 kexec_crash_loaded(崩溃内核是否加载)
cat /sys/kernel/kexec_crash_loaded
# 1 = 崩溃捕获内核已就位

手动触发崩溃测试

配置好了不测一下,等于没配。这一步我在每台新上线服务器上都会做。

警告:这会让你的服务器立刻崩溃并重启。请确保没有业务在跑,或者在一台测试机器上操作。

# 第一步:确认 kdump 服务正常
systemctl status kdump

# 第二步:确认 panic_on_oops 已开启
sysctl kernel.panic_on_oops
# 值应为 1,如果不是:
sudo sysctl -w kernel.panic_on_oops=1

# 第三步:确保 panic 后自动重启
sysctl kernel.panic
# 值应大于 0(表示 panic 后多少秒重启),设为 0 不会自动重启
# 建议:
sudo sysctl -w kernel.panic=10

# 第四步:同步磁盘数据,避免文件系统损坏
sync && sync && sync

# 第五步:触发内核崩溃!
# 方法A:通过 SysRq 触发(推荐)
echo c > /proc/sysrq-trigger

# 方法B:如果 SysRq 被禁用,先开启
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger

echo c > /proc/sysrq-trigger 会触发一个空指针解引用,导致内核 panic。如果你的 kdump 配置正确,系统会:

  1. 打印 panic 信息到控制台
  2. kexec 切换到捕获内核
  3. 捕获内核启动,开始采集 vmcore
  4. 采集完成后自动重启

重启后检查:

# 查看 vmcore 是否已保存
ls -lh /var/crash/
# 预期看到类似:
# drwxr-xr-x 2 root root  4096 Jul 17 09:20 127.0.0.1-2026-07-17-09:20:01
# 里面应该有 vmlinux(或 vmlinuz)和 vmcore 文件

# 检查 vmcore 文件大小
ls -lh /var/crash/*/vmcore*
# 压缩后的 vmcore 通常在几百 MB 到几 GB 之间

常见配置失败排查

如果触发崩溃后没找到 vmcore,或者 kdump 服务压根起不来,按这个清单排查:

问题1:kdump.service 启动失败

# 查看具体错误
journalctl -u kdump.service -e

# 最常见原因:crashkernel 参数没配或预留不够
# 检查:
cat /proc/cmdline | grep crashkernel
# 如果没有 crashkernel= 参数,说明 GRUB 配置没更新

# 检查预留内存是否生效
dmesg | grep -i crashkernel
# 如果看到 "crashkernel reservation failed" 说明预留失败
# 可能原因:内存太小,或预留地址与其它区域冲突

参考 CSDN 上关于 kdump.service 启动失败的排查文章,crashkernel 参数配置不当是 kdump 启动失败的"头号元凶"。

问题2:崩溃后没有生成 vmcore

# 检查是否有 kdump 日志
journalctl -b -1 | grep -i kdump
# -b -1 表示上一次启动的日志

# 检查 /var/crash 是否有空间
df -h /var/crash

# 检查控制台输出(如果有串口或 IPMI 日志)
# 常见原因:
#   1. 捕获内核的 initramfs 缺少磁盘驱动
#   2. /var/crash 分区空间不足
#   3. makedumpfile 参数错误

问题3:crashkernel=auto 不生效

crashkernel=auto 在某些环境下(特别是虚拟机或自定义内核)可能无法正确计算预留大小。我建议直接写固定值,别用 auto。虚拟机环境尤其要注意,有些 hypervisor 不支持 crashkernel 内存预留。

用 crash 工具分析 vmcore

拿到 vmcore 只是第一步,不分析等于白拿。crash 工具是分析 vmcore 的标准工具,它结合内核调试信息(debuginfo),能让你像用 GDB 调试用户态程序一样调试内核。

启动 crash

# 基本用法:crash <vmcore> <vmlinux-debuginfo>
# vmlinux-debuginfo 通常在 /usr/lib/debug/lib/modules/$(uname -r)/vmlinux

crash /var/crash/127.0.0.1-2026-07-17-09:20:01/vmcore \
      /usr/lib/debug/lib/modules/$(uname -r)/vmlinux

如果找不到 vmlinux debuginfo 文件:

# RHEL/CentOS
sudo dnf install kernel-debuginfo-$(uname -r)

# Ubuntu
sudo apt install linux-image-$(uname -r)-dbg

# 找 vmlinux 文件位置
find /usr/lib/debug -name vmlinux

常用 crash 命令

进入 crash 交互界面后,以下命令最常用:

crash> bt            # 打印崩溃时的内核调用栈(最常用)
crash> bt -a         # 打印所有 CPU 的调用栈
crash> ps            # 列出崩溃时所有进程
crash> ps | grep -i "D"  # 筛选 D 状态(不可中断睡眠)进程
crash> log           # 打印内核日志缓冲区(dmesg 内容)
crash> sys           # 显示系统信息(内核版本、CPU、内存等)
crash> files         # 列出崩溃时打开的文件
crash> vm            # 显示虚拟内存信息
crash> kmem -i       # 内核内存使用概况
crash> dev -l        # 列出已加载的设备驱动
crash> mod           # 列出已加载的内核模块
crash> mod -s <名称>  # 加载指定模块的调试信息
crash> struct <结构名> <地址>  # 查看指定地址的结构体内容
crash> rd <地址> <数量>  # 读取内存
crash> dis <地址>     # 反汇编指定地址的代码

实战分析示例

假设我们触发了一次 panic,用 crash 分析崩溃原因:

# 1. 先看调用栈,确定崩溃发生在哪里
crash> bt
PID: 0      TASK: ffffffff81c10480  CPU: 0   COMMAND: "swapper/0"
 #0 [ffff88003fc03c90] machine_kexec at ffffffff8105f7a0
 #1 [ffff88003fc03ce0] crash_kexec at ffffffff810b0a72
 #2 [ffff88003fc03db0] oops_end at ffffffff81009524
 #3 [ffff88003fc03dd0] no_context at ffffffff8104b6a5
 #4 [ffff88003fc03e20] __bad_area_nosemaphore at ffffffff8104b6e5
 #5 [ffff88003fc03e70] bad_area at ffffffff8104b810
 #6 [ffff88003fc03ea0] do_page_fault at ffffffff8104bd76
 #7 [ffff88003fc03f30] page_fault at ffffffff816012b8
    [exception RIP: my_driver_write+42]
    RIP: ffffffffa0001234  RSP: ffff88003fc03fe8  RFLAGS: 00010246
    RAX: 0000000000000000 RBX: ffff88003e8a0000 RCX: 0000000000000000
    RDX: 0000000000000100 RSI: ffff88003e8a1000 RDI: 0000000000000000
    RBP: ffff88003fc03ff0
 #8 [ffff88003fc03ff8] sys_write at ffffffff811df3a2

# 调用栈解读:
# 1. sys_write 被调用(用户态 write 系统调用)
# 2. 进入 my_driver_write(自定义驱动模块的 write 函数)
# 3. 发生 page_fault(缺页异常)
# 4. bad_area → no_context → oops_end → crash_kexec → machine_kexec
# 说明:my_driver_write 函数访问了一个无效的内存地址,触发缺页异常
# 2. 反汇编崩溃位置的代码
crash> dis ffffffffa0001234
0xffffffffa0001234 <my_driver_write+42>:  mov    %rax,(%rdi)
# RDI = 0(从上面的寄存器信息),mov %rax,(0) 就是对空指针解引用

# 3. 查看是哪个模块
crash> mod -s my_driver
     MODULE       NAME                 SIZE  OBJECT FILE
ffff88003e8a0000  my_driver           16384  /lib/modules/.../my_driver.ko

# 4. 查看模块里的函数源码(需要 debuginfo)
crash> sym ffffffffa0001234
ffffffffa0001230 (t) my_driver_write+38 /usr/src/my_driver/write.c: 42

# 定位到 write.c 第 42 行,发现是对一个未初始化的指针做写操作

这个示例展示了从 vmcore 定位到具体代码行的完整流程。实际工作中,大部分内核崩溃都能通过 btdissym 三步定位到问题函数。

分析 D 状态进程

有时候崩溃不是因为空指针,而是因为死锁——进程卡在 D 状态(不可中断睡眠),最终触发 hung task 检测导致 panic。这种情况下要换个思路:

# 查找 D 状态进程
crash> ps | grep "UN"
   PID    PPID  CPU       TASK        ST  %MEM     VSZ    RSS  COMM
  12345      1   0  ffff88003e5b8000   UN   0.2  262144   8192  mysql

# UN = Uninterruptible Sleep

# 查看这个进程的调用栈
crash> bt ffff88003e5b8000
PID: 12345  TASK: ffff88003e5b8000  CPU: 0  COMMAND: "mysql"
 #0 [ffff88003e8a3d80] __schedule at ffffffff8109a2a3
 #1 [ffff88003e8a3dd0] schedule at ffffffff8109a3a5
 #2 [ffff88003e8a3e00] schedule_timeout at ffffffff8109a6b0
 #3 [ffff88003e8a3e50] wait_for_completion at ffffffff8109a8c0
 #4 [ffff88003e8a3ea0] flush_work at ffffffff81098123
 #5 [ffff88003e8a3ef0] __cancel_work_timer at ffffffff81098456
 #6 [ffff88003e8a3f50] cancel_work_sync at ffffffff81098501

# 这个进程卡在 cancel_work_sync → wait_for_completion
# 说明有一个 work_struct 永远没有被执行完成

# 查看这个进程在等什么
crash> struct task_struct ffff88003e5b8000
struct task_struct {
  ...
  state = 2,  // TASK_UNINTERRUPTIBLE
  ...
}

与 kdump 相关的内核参数

除了 crashkernel,还有几个内核参数会影响 kdump 的行为,建议在生产环境中配置:

# /etc/sysctl.d/99-kdump.conf

# panic 时触发 kdump(而不是直接重启)
kernel.panic_on_oops = 1

# panic 后多少秒自动重启(给 kdump 时间完成转储)
# 设太短可能导致转储没完成就被强制重启
kernel.panic = 10

# SysRq 功能(允许通过 /proc/sysrq-trigger 手动触发崩溃测试)
kernel.sysrq = 1

# hung task 检测:进程 D 状态超过指定秒数触发告警
# 默认 120 秒,可根据业务调整
kernel.hung_task_timeout_secs = 120

# hung task 检测触发 panic(默认只告警不 panic)
# 如果你想让 hung task 也触发 kdump 捕获,设为 1
kernel.hung_task_panic = 1

# 硬件 NMI(不可屏蔽中断)触发 panic
# 对于硬件故障导致的挂死,NMI 是最后的手段
kernel.unknown_nmi_panic = 1

# 软锁定检测:CPU 长时间不让出触发告警
kernel.softlockup_panic = 1

一个容易踩的坑:kernel.panic 设为 0 的话,panic 后系统不会自动重启,会一直挂着。这在物理服务器上意味着你需要去机房按电源键。但如果你设太短(比如 1 秒),kdump 可能还没来得及完成转储就被强制重启了。我一般设 10 秒,这个值在大多数环境下都够用。

不同场景下的配置策略

物理服务器

物理服务器是 kdump 最有价值的场景。硬件故障、驱动 bug、固件问题都可能导致内核崩溃,而物理服务器上崩溃后的现场保存最难——你没有 hypervisor 层面的内存快照功能。

配置要点:

# 物理服务器内存通常较大,预留可以多一点
# GRUB 参数:
crashkernel=512M

# 配置 IPMI 查看 crash 时的控制台输出
# 即使 kdump 没能保存 vmcore,IPMI 的 System Event Log
# 也可能记录了崩溃前的硬件信息

# 考虑配置串口控制台,把 panic 信息输出到串口
# GRUB 参数追加:
console=tty0 console=ttyS0,115200

虚拟机(KVM/QEMU)

虚拟机环境配置 kdump 有几个注意点:

# 虚拟机内存通常不大,预留可以少一点
crashkernel=128M

# 确保虚拟机配置了足够预留内存
# 在 libvirt XML 中检查 memory 和 currentMemory 设置

# 虚拟机可以借助 hypervisor 的内存快照作为补充手段
# virsh dump <domain> <file> --memory-only
# 这个命令在虚拟机完全无响应时特别有用

容器宿主机

容器宿主机(Docker host / Kubernetes node)上配置 kdump 尤其重要,因为一个节点的内核崩溃会影响上面运行的所有容器:

# 宿主机配置 kdump 与普通服务器一致
crashkernel=256M

# 关键:确保 kdump 的 initramfs 包含存储驱动模块
# 如果使用 overlay2,需要确认相关模块在 initramfs 中
# 在 /etc/dracut.conf.d/kdump.conf 中添加:
add_drivers+=" overlay br_netfilter nf_conntrack "

# 转储完成后通知 Kubernetes
# 在 /etc/kdump.conf 中配置 kdump_post 脚本:
# kdump_post /usr/local/bin/kdump-notify.sh

kdump 通知脚本示例:

#!/bin/bash
# /usr/local/bin/kdump-notify.sh
# 在 kdump 转储完成后执行,$1 为状态(0=成功,1=失败)

if [ "$1" -eq 0 ]; then
    # 转储成功,发送通知
    logger -t kdump "vmcore captured successfully at $(date)"
    # 可以在这里调用企业微信/飞书/钉钉 webhook
    curl -s -X POST "https://your-webhook.example.com/notify" \
         -H "Content-Type: application/json" \
         -d "{\"event\":\"kernel_panic\",\"host\":\"$(hostname)\",\"vmcore\":\"$(ls -t /var/crash/ | head -1)\"}"
else
    # 转储失败
    logger -t kdump "vmcore capture FAILED at $(date)"
fi

vmcore 的存储管理

vmcore 文件动辄几个 GB,如果不管它,很快就会把磁盘撑爆。需要设置自动清理策略:

#!/bin/bash
# /usr/local/bin/cleanup-vmcore.sh
# 保留最近 5 份 vmcore,更老的自动删除

CRASH_DIR="/var/crash"
KEEP_COUNT=5

# 按修改时间排序,删除最老的
cd "$CRASH_DIR" || exit 1
ls -dt */ 2>/dev/null | tail -n +$((KEEP_COUNT + 1)) | while read dir; do
    echo "Removing old vmcore: $dir"
    rm -rf "$dir"
done

# 检查磁盘使用率,超过 80% 删除最老的
USAGE=$(df "$CRASH_DIR" | awk 'NR==2{print $5}' | tr -d '%')
while [ "$USAGE" -gt 80 ]; do
    OLDEST=$(ls -dt */ 2>/dev/null | tail -1)
    if [ -z "$OLDEST" ]; then
        break
    fi
    echo "Disk usage ${USAGE}%, removing: $OLDEST"
    rm -rf "$OLDEST"
    USAGE=$(df "$CRASH_DIR" | awk 'NR==2{print $5}' | tr -d '%')
done

加到 crontab:

# 每天凌晨 3 点清理
0 3 * * * /usr/local/bin/cleanup-vmcore.sh >> /var/log/vmcore-cleanup.log 2>&1

kdump 与 systemd-coredump 的区别

经常有人混淆 kdump 和 systemd-coredump,这里做个对比说明:

维度kdumpsystemd-coredump
捕获对象内核崩溃(kernel panic)用户态进程崩溃(segfault 等)
工作层级内核空间用户空间
转储内容整个系统内存映像单个进程的内存映像
触发条件内核 panic / Oops进程收到 SIGSEGV/SIGABRT 等
分析工具crashgdb / coredumpctl
文件大小几百 MB ~ 几 GB几 MB ~ 几百 MB
是否需要重启是,转储后系统重启否,只影响崩溃的进程

简单说:用户态程序崩溃用 coredump,内核崩溃用 kdump。两者不冲突,可以同时配置。

生产环境完整配置清单

把前面讲的汇总成一份可直接使用的配置清单:

#!/bin/bash
# kdump 生产环境配置脚本
# 适用于 RHEL/CentOS 8+,其他发行版需调整包名

set -euo pipefail

echo "=== 1. 安装组件 ==="
dnf install -y kexec-tools crash
dnf install -y "kernel-debuginfo-$(uname -r)" 2>/dev/null || \
    echo "Warning: kernel-debuginfo not found, crash analysis may be limited"

echo "=== 2. 配置 crashkernel 参数 ==="
GRUB_FILE="/etc/default/grub"
if ! grep -q "crashkernel=" "$GRUB_FILE"; then
    sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="crashkernel=256M /' "$GRUB_FILE"
    echo "Added crashkernel=256M to GRUB"
else
    echo "crashkernel already configured"
fi

echo "=== 3. 配置 sysctl 参数 ==="
cat > /etc/sysctl.d/99-kdump.conf << 'EOF'
kernel.panic_on_oops = 1
kernel.panic = 10
kernel.sysrq = 1
kernel.hung_task_timeout_secs = 120
kernel.hung_task_panic = 1
kernel.unknown_nmi_panic = 1
kernel.softlockup_panic = 1
EOF
sysctl --system

echo "=== 4. 配置 kdump.conf ==="
cat > /etc/kdump.conf << 'EOF'
path /var/crash
core_collector makedumpfile -l --message-level 1 -d 31
default reboot
failure_action shell
EOF

echo "=== 5. 启用并启动 kdump ==="
systemctl enable kdump
systemctl restart kdump

echo "=== 6. 创建 vmcore 自动清理 cron ==="
cat > /usr/local/bin/cleanup-vmcore.sh << 'SCRIPT'
#!/bin/bash
CRASH_DIR="/var/crash"
KEEP_COUNT=5
cd "$CRASH_DIR" || exit 1
ls -dt */ 2>/dev/null | tail -n +$((KEEP_COUNT + 1)) | while read dir; do
    rm -rf "$dir"
done
USAGE=$(df "$CRASH_DIR" | awk 'NR==2{print $5}' | tr -d '%')
while [ "$USAGE" -gt 80 ]; do
    OLDEST=$(ls -dt */ 2>/dev/null | tail -1)
    [ -z "$OLDEST" ] && break
    rm -rf "$OLDEST"
    USAGE=$(df "$CRASH_DIR" | awk 'NR==2{print $5}' | tr -d '%')
done
SCRIPT
chmod +x /usr/local/bin/cleanup-vmcore.sh
echo "0 3 * * * root /usr/local/bin/cleanup-vmcore.sh >> /var/log/vmcore-cleanup.log 2>&1" > /etc/cron.d/vmcore-cleanup

echo "=== 7. 更新 GRUB ==="
grub2-mkconfig -o /boot/grub2/grub.cfg

echo ""
echo "=== 配置完成 ==="
echo "请重启系统使 crashkernel 参数生效:sudo reboot"
echo "重启后验证:"
echo "  1. cat /proc/cmdline | grep crashkernel"
echo "  2. systemctl status kdump"
echo "  3. cat /sys/kernel/kexec_crash_loaded (应为 1)"
echo ""
echo "测试 kdump(会触发崩溃重启):"
echo "  echo c > /proc/sysrq-trigger"

总结

kdump 是 Linux 运维中"平时用不上,用时后悔没配"的典型工具。配置它花不了半小时,但内核崩溃时有没有 vmcore,可能决定了你排查问题需要 2 小时还是 2 天。

几个实战经验总结:

  1. 预留内存宁可多不可少。捕获内核启动需要内存,预留不够直接导致转储失败。256M 是大多数场景的安全值,大内存机器加到 512M。

  2. makedumpfile 的过滤级别用 -d 31。不压缩的 vmcore 可能比物理内存还大,-d 31 -l 能压缩到原始大小的 5-15%。

  3. 必须实际触发测试。配置完不测,等于在崩溃时赌运气。echo c > /proc/sysrq-trigger 这条命令,我在每台新服务器上线时都会跑一次。

  4. kernel.panic 不能设为 0。panic 后不重启,物理服务器就只能去机房了。但也不能设太短,10 秒是个合理值——给 kdump 足够时间完成转储。

  5. vmcore 要有清理策略。一个 vmcore 几个 GB,攒几次磁盘就满了,kdump 就成了新的故障源。

  6. 远程转储值得配。当本地文件系统出问题导致崩溃时,本地转储也会失败。SSH 远程转储是兜底方案。

  7. hung_task_panic 和 softlockup_panic 建议开启。很多内核问题不是 panic 而是死锁,开了这两个参数才能让 kdump 捕获到死锁现场。

最后一句话:kdump 不是可选项,是生产服务器的标配。别等炸了才想起来配。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Red Hat Enterprise Linux 内核崩溃转储指南 — Red Hat, kdump 官方文档,工作原理与配置参数说明
  2. Linux服务器宕机别慌!手把手教你用Kdump抓取内核崩溃现场 — CSDN,CentOS 7/8 环境下 kdump 配置实战与 crashkernel 内存预留指南
  3. RHEL 8系统kdump.service启动失败排查与修复 — CSDN,crashkernel 参数配置不当导致 kdump 服务启动失败的排查流程
  4. Linux内核崩溃现场取证指南:手把手教你用kdump和crash分析panic日志 — CSDN,crash 工具分析 vmcore 的实战操作指南
  5. Linux宕机救星:3种捕获完整Oops日志的方法对比 — CSDN,Oops 日志捕获方案对比与 kdump 配置指南