Linux运维15 min read次阅读

服务器莫名重启、控制台只剩一行 Call Trace:用 kdump 把内核崩溃的现场抓回来

一台跑着 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-debuginfokernel-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=auto256M;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 撑爆——栈只替你指方向,不替你下结论。

分享:

相关文章

Too many open files:ulimit、LimitNOFILE 与 fd 泄漏的完整排查链路
Linux运维12 min read

Too many open files:ulimit、LimitNOFILE 与 fd 泄漏的完整排查链路

服务半夜开始报错 accept4() failed (24: Too many open files),或者 Nginx 频繁 502。这种故障十次有八次是文件句柄(fd)耗尽,但光改 ulimit -n 往往没用——因为 systemd 起的服务根本不吃 limits.conf 那套。从内核 file-max 到进程 LimitNOFILE,到定位到底是哪个进程在漏 fd,把每一层都讲透,并给出不停机临时救急和永久修复两条路。

磁盘显示没满却写不进去:df 和 du 对不上的三种真实场景
Linux运维12 min read

磁盘显示没满却写不进去:df 和 du 对不上的三种真实场景

报警说磁盘 100%,可 du 加起来差了一大截;或者 df -h 明明还有空间,应用却报 No space left on device。这两种情况我都遇过不止一次,根因通常是已删除但被进程占用的文件、inode 耗尽,或者文件被挂载点盖住。逐个拆开讲怎么定位、怎么在不重启服务的前提下把空间腾出来。

评论区