一台跑着 PostgreSQL 的机器,凌晨三点被监控标记「重启」,登录一看 uptime 才 47 分钟,而上次维护是上周。第一反应当然是翻 dmesg,结果 dmesg 在 02:51 之后就什么都没有了,像是被人直接拔了电。但 IPMI 的带外日志写得很清楚:不是断电,是 kernel panic 之后内核自己 reboot 的(我们给 panic 配了 kernel.panic=30,崩了 30 秒还没人接管就自己重启保活)。
这种「进程都没了、日志也断了」的局面,靠 top、journalctl 那些用户态的东西是查不出什么的。内核已经把控制权交还给固件了,你能捡到的只剩崩溃瞬间留在内存里、还没被覆盖的那点现场。而这块现场,要么靠 kdump 提前备好捕获内核把它 dump 下来,要么就真的永远丢了。
下面这套我在 Alibaba Cloud Linux 4(RHEL 9 系内核)和 CentOS 7 上都实操过。命令以 RHEL 系为准,Debian/Ubuntu 把包名和路径换一下(kexec-tools、kdump-tools、crash 同名,配置文件位置和 grub 更新命令不同)即可。
先分清:它到底是哪种「崩」
内核不会平白无故重启,崩的形态不一样,排查入口也不同。常见几种:
- Oops:内核态跑飞了(空指针解引用、野指针、未对齐访问),但还能继续跑。会打一段 Call Trace 进 dmesg,进程可能被杀,机器不一定重启。这是最轻的,dmesg 里通常能直接看到。
- BUG:内核自己断言失败,比如
BUG: unable to handle page fault,比 Oops 严重,可能接着 panic。 - kernel panic:内核觉得环境已经不可信,主动调用
panic()停下。接着要么 halt、要么按kernel.panic的设置重启。echo c > /proc/sysrq-trigger造的就是这种。 - soft lockup / hard lockup:某个 CPU 在内核态关了抢占还死循环,超过 20 秒(
kernel.watchdog_thresh默认 10,翻倍即 20s)不出调度,看门狗报BUG: soft lockup - CPU#3 stuck for 23s。机器没死,但那个核僵了,往往连带服务卡死。 - hung task:某个进程在 D 状态(不可中断睡眠,通常是等 IO)卡超过
kernel.hung_task_timeout_secs(默认 120s),内核打印INFO: task xxx:1234 blocked for more than 120 seconds。这是「等现象」,不是「崩」,但它经常是存储或文件系统出问题的前兆,放任不管下一步可能就是真 panic。
区分它们很关键:soft lockup 和 hung task 机器还活着,你能登进去抓 dmesg;而真 panic 重启后 dmesg 已经被清,不靠 kdump 就没戏。所以更稳妥的做法是——平时就开着 kdump,等出事时 vmcore 已经在 /var/crash 躺着了,而不是出了事才想起来装。
kdump 到底在干什么
原理不复杂,但第一次配的人容易绕晕。普通内核崩了就直接死。kdump 的思路是:开机时用一个叫 kexec 的机制,预先把一个精简的「捕获内核(capture kernel)」加载进一块预留内存里,这块内存普通内核碰不到。真崩的时候,不是走正常的重启流程,而是由 kexec 直接把 CPU 切到那个捕获内核上跑。捕获内核起来后第一件事就是把「生产内核」那一整片内存原样 dump 成文件(vmcore),存到磁盘或扔到远程,然后再正常重启。
要点就两个:预留内存和捕获内核。预留内存太小,dump 大内存机器会失败;捕获内核没加载成功,/sys/kernel/kexec_crash_loaded 会是 0,等于白配。
安装与预留内存
RHEL 系装这两个包:
dnf install -y kexec-tools crash
# crash 是后面分析 vmcore 用的,也可以只装在生产机、分析在另一台
关键一步是给内核加 crashkernel= 启动参数,预留捕获内核的内存。老做法是写死一个值,比如 crashkernel=256M,但大内存机器(比如 256G、512G)256M 根本不够 dump(vmcore 要装下全部内存,靠压缩能小很多,但捕获内核本身和系统结构也要占)。现代内核支持 crashkernel=auto,会根据内存大小自动算:
# RHEL 8+/Alibaba Cloud Linux:用 grubby 改所有内核
grubby --update-kernel=ALL --args="crashkernel=auto"
# 如果你机器特别大(>1T)或 auto 算出来不够,手动给个大的:
# grubby --update-kernel=ALL --args="crashkernel=1G"
改完必须重启才生效,因为预留内存是启动阶段向内核申请的,运行时加不进去。重启后确认两块:
cat /proc/cmdline | tr ' ' '\n' | grep crashkernel
# 应能看到 crashkernel=auto(或你写的值)
cat /sys/kernel/kexec_crash_loaded
# 输出 1 表示捕获内核已加载;0 表示没加载成功,下面 kdump 起不来
CentOS 7 上 crashkernel=auto 对大内存有时算得保守,我吃过亏:一台 128G 的机器 auto 只留了 160M,dump 到一半捕获内核 OOM 自杀,vmcore 是残缺的。后来直接写 crashkernel=512M 才稳。所以大内存机器别太信 auto,手动给足更保险。
配置 /etc/kdump.conf
捕获内核起来后往哪 dump、怎么压缩,靠 /etc/kdump.conf 决定。最常用的最小配置:
# 存到本地磁盘的 /var/crash(默认就是这)
path /var/crash
# 核心收集器:makedumpfile 做压缩 + 过滤无关页
# -l 用 lzo 压缩;-d 31 是过滤级别(丢掉空闲页/缓存页,能小一个数量级)
# --message-level 1 只打印必要的进度
core_collector makedumpfile -l --message-level 1 -d 31
如果 /var 分区本身就在出问题的磁盘上、怕 dump 时也炸,可以把 vmcore 直接扔到远程,避免本地盘再写挂:
# 方式一:SSH 推到分析机
ssh root@10.10.4.100
path /var/crash
# 方式二:NFS 挂一个远端目录
nfs 10.10.4.100:/data/kdump
path /var/crash
我一般生产环境用 NFS 或 SSH,因为本地盘如果就是罪魁祸首(比如存储驱动 panic),往本地写 vmcore 多半也失败,那你这波就白崩了。
配置完启用并确认:
systemctl enable --now kdump
# Alibaba Cloud Linux / RHEL 8+ 用 kdumpctl 看状态
kdumpctl status
# 期望:kdump: operational
# CentOS 7 用
systemctl status kdump
kdumpctl status 报 operational 之前别以为配好了。我见过不少人改完 grub 没重启,或者 crashkernel= 写错位置,kdump 服务是 active 但捕获内核根本没 load,真崩了没 dump——这种「假配置」比不配还坑,因为你会以为自己有兜底。
造一次崩溃验证 kdump 真的能抓
配完最该做的一步,是主动触发一次崩溃,确认 vmcore 真能出来。否则等真出事才发现 kdump 没工作,哭都来不及。注意:这一步会让机器立刻重启,务必在维护窗口做。
# 开启 sysrq 的 crash 触发位(很多发行版默认 kernel.sysrq=16,只允许部分功能)
sysctl kernel.sysrq=1
# 触发一次内核崩溃
echo c > /proc/sysrq-trigger
机器会黑一下、由捕获内核起来 dump、然后重启。重启后去看:
ls -lh /var/crash/
# 应该有类似 127.0.0.1-2026-09-14-03:12:05/vmcore 这样的目录
vmcore 的大小取决于内存和压缩级别。一台 64G 的机器,-d 31 压完通常 2~8G,比裸内存小得多。如果看到 vmcore 特别小(几 KB)或者目录是空的,说明 dump 没成功,回去查 kdumpctl status 和 /var/log/messages 里 kdump 的报错。
用 crash 把 vmcore 拆开看
vmcore 是原始内存镜像,人看不了,得用 crash 工具配带调试符号的内核一起读。调试符号包一般叫 kernel-debuginfo 和 kernel-debuginfo-common,装完 vmlinux 在 /usr/lib/debug/lib/modules/$(uname -r)/vmlinux。注意:分析机的 vmlinux 版本必须和崩溃那台机器的内核版本一字不差,否则 crash 拒绝打开。
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/127.0.0.1-2026-09-14-03:12:05/vmcore
进去后是交互式 shell,几个最高频的命令:
# 崩溃时的完整调用栈,第一现场就在这
bt
# 所有 CPU 的栈(soft lockup 时看哪个核卡死特别有用)
bt -a
# 崩溃瞬间的内核日志,等价于那一刻的 dmesg,通常能看到 Oops/BUG 全文
log
# 进程表,带状态。带 UN 标记的是不可中断睡眠,hung task 排查看这里
ps
# 内存信息,-i 看 slab 占用,内存泄漏/踩踏类问题看这里
kmem -i
# 崩溃时打开的文件
files
实战里 90% 的 panic,靠 bt 一个命令就能定位。比如我那台 PG 机器,vmcore 里 bt 打出来栈顶是:
#0 panic
#1 oops_end
#2 page_fault_oops
#3 do_user_addr_fault
#4 ext4_file_write_iter
#5 ...
栈一路指到 ext4_file_write_iter——也就是说崩在 ext4 写文件的路径上。再看 log 里的 Oops 原文:BUG: unable to handle page fault for address 0x0000000000000010(空指针解引用,偏移 0x10)。结合当时那台机器刚换过一块第三方 NVMe 盘、用了厂商给的私货驱动,基本可以锁定是存储驱动在写路径上解引用了空指针,内核态崩了。后来把厂商驱动回退到内核自带 nvme 驱动,再没复现。
这就是 kdump 的价值:用户态日志什么都看不到,只有 vmcore 能告诉你崩在内核哪一行、哪个驱动。
soft lockup 和 hung task 别和 panic 混为一谈
前面提过,这俩机器其实还活着,但表现很像「卡死」,容易误判成 panic。它们的关键区别:
- soft lockup 是 CPU 在内核态长时间没发生调度(softirq 或内核线程占死了一个核)。
bt -a看那个核的栈,往往卡在某个驱动的 while 循环或自旋锁上。这种不一定是驱动 bug,也可能是实时性要求高的活把核占满了。 - hung task 是进程卡在 D 状态超过 120 秒,通常是底层存储/网络存储响应不了(NFS 服务端挂了、SAN 链路抖、云盘 IO 超时)。它不是内核崩,是「等不到」。根因在存储侧,不是计算侧。
这俩的现场,机器活着时直接 dmesg | tail -200 就能拿到完整栈,不需要 kdump。但如果你没及时抓,机器之后又真 panic 重启了,那原始 dmesg 就没了——所以监控里把 dmesg 实时外发(比如用 rsyslog 转发到远端日志服务器)是很值的,跟 kdump 是两道互补的保险。
几个上线前要想清楚的坑
预留内存别太抠。自动算的在大内存机器上偏保守,前面说了。给少了的结果是 dump 中途失败,vmcore 残缺,crash 打不开。经验值:机器 ≤64G 用 crashkernel=auto 或 256M;64~256G 给 512M;再大给 1G。这是捕获内核自身 + dump 元数据的开销,不是 vmcore 大小。
vmcore 占空间要盯。本地落盘的话,/var/crash 会越攒越多,一台 128G 机器几次崩溃就是几十 G。配合 logrotate 思路,写个定时任务只留最近 N 份,老的打包传对象存储后删。
kernel.panic 的重启值别设太小。我们设 30 秒,是为了给 kdump 留够 dump 时间——dump 大内存可能要几分钟,如果你 panic 设成 5 秒就强制重启,捕获内核还没 dump 完就被你打断了,vmcore 又是残缺的。kdump 和 panic 超时是配套的,改一个要想另一个。
还有一层要想清:kdump 抓的是「崩溃那一瞬间的快照」,它能告诉你崩在哪,但 often 给不出「为什么崩在这之前」。要找到根因,vmcore 的栈 + 当时的负载 + 近期变更(换驱动、升内核、改参数)得一起看。见过有人拿着 vmcore 栈顶一个 ext4 函数,就断定是文件系统坏了要换盘,结果根因是上层应用疯狂写小文件把 journal 撑爆——栈只替你指方向,不替你下结论。

