一个监控探针炸了 24 台服务器:eBPF 是把双刃剑

eBPF内核监控事故复盘Linux

eBPF 探针炸了 24 台服务器:CentOS 4.18 内核 eBPF 上下文清理 bug 复盘

TL;DR 在测试集群部署了一个基于 eBPF 的观测工具(kindling),想看看性能数据。结果 24 台物理机内核崩溃重启,900 个 Pod 被连带重建。原因不是程序写得差,而是 CentOS 4.18 内核的一个深层 bug:两个 eBPF 程序同时运行时,后结束的那个会污染前一个的执行上下文,导致内核 panic。这事让我彻底改变了对 eBPF 的看法——它不是”装上就完事的黑盒探针”,而是一把在内核里运行的刀。


📌 本文要点

  • eBPF 跟普通监控工具的本质区别:跑在用户态的程序挂了只崩自己,eBPF 挂了崩整个内核
  • 4.18 内核的 eBPF 上下文清理 bug——为什么重启一万次也治不好
  • 多个 eBPF 工具的冲突问题:Cilium、Falco、Kindling 各自为政,都不知道内核里还有别的探针
  • 内核版本决定了 eBPF 的安全水位,老内核上跑 eBPF 要有心理准备

🏗️ 背景与环境

项目信息
集群类型测试集群(jiagou_wcloud_offline_binhai)
灰度机器数31 台物理机
操作系统CentOS 7(4.18 内核)
已有 eBPF 探针安全部的安全检测探针
新部署工具Kindling(eBPF 观测工具)
容器编排Kubernetes

重点标记 4.18 内核已有 eBPF 探针——这两个条件叠加,就是事故的导火索。


🔧 先搞懂:eBPF 到底是什么

eBPF(extended Berkeley Packet Filter)是 Linux 内核里的一项技术,允许你在不改内核代码、不重启内核的情况下,在内核里跑一段小程序

你可以把它理解成给内核装传感器/探针

eBPF 原理对比图

eBPF 的常见用途:

  • 观测/监控 — 看网络延迟、系统调用耗时,不需要装 agent 轮询 CPU
  • 安全检测 — 实时拦截可疑的系统调用
  • 网络加速 — 比如 Cilium 用 eBPF 替代 iptables 做容器网络

现在很多云原生基础设施已经在用 eBPF 了,你很可能被动地在用它——比如你的 K8s 集群如果用了 Cilium,那 eBPF 已经在你的内核里跑了。你只是不知道而已。


🌙 一个”测试工具”的部署

晚上九点。

我想在集群上试试 kindling 这个观测工具——它是基于 eBPF 的,能无侵入地观测网络延迟、系统调用等指标,很适合做性能分析。

我选的是测试集群,灰度了 31 台机器。

我以为这是在”安全地做测试”。

但我忽略了一个事实:这个所谓的测试集群,跟线上用的是同一个内核。而且这个集群上已经跑着安全部的 eBPF 探针了。

两个 eBPF 程序在同一台机器的内核里共存,我没有意识到有任何风险。


💥 部署四分钟后,机器开始崩了

部署后四分钟,第一台物理机内核崩溃重启。

然后第二台,第三台……两个小时内一共 24 台物理机陆续内核 panic 重启

900 个 Pod 被牵连重建。测试环境的所有服务全部中断。

事故时间线

这就是 eBPF 跟普通程序最残酷的区别:

程序对比

跑在用户态的监控工具,崩了重启一下就行。跑在内核态的 eBPF 探针,崩了带走整个节点。


🔍 根因:4.18 内核的 eBPF 上下文清理 bug

追查的结果指向了 CentOS 4.18 内核的一个已知 bug。

根因分析流程图

要触发这个 bug,需要两个条件同时满足

  1. 内核版本是 4.18(或更早的版本)
  2. 同时运行两个或以上的 eBPF 程序

两个条件都满足了。


🔄 重启一万次也没用

发现机器崩了之后,初步反应是——重启看看能不能恢复。

机器是重启了,然后……又崩了。

因为这不是一个”运行时状态被污染”的问题(比如内存泄漏,重启能清一下状态)。这是内核代码本身的缺陷——4.18 内核的 eBPF 上下文清理逻辑从根本上就是错的。只要两个 eBPF 程序同时加载、同时运行,这个 bug 就一定会被触发。重启一万次也改变不了这个事实。

实际上,最终恢复的方式也不是重启,而是安全部直接停掉了其中一个 eBPF 探针。直到某个探针被移除,崩溃才真正停止。

重启对比


🤔 你以为只有一个 eBPF 程序在跑

我事后回想这件事,最让我后怕的一个问题是:我在部署之前,根本不知道这台机器上已经跑着另一个 eBPF 程序。

这不是我的疏忽,而是这类系统的普遍现状:

工具用途谁装的
Cilium容器网络(eBPF)云平台
Falco / Tetragon安全检测(eBPF)安全部
Kindling / Pixie观测 profiling(eBPF)SRE / 开发

各自安装时都不知道对方存在。而且 eBPF 不像普通的进程——你用 ps aux 能看到。它是在内核里跑的,对用户态是完全透明的。不是你”忘了排查”,而是你根本不知道要排查什么

多个 eBPF 程序在内核里共享 maps、hooks、程序链表,它们的隔离性在旧内核上并不完善。5.x 以上的内核在这方面做了很多改进,但 4.18 这种老内核——坦率地说——还活在 eBPF 的”西部时代”。


📊 内核版本决定 eBPF 的安全水位

不同内核版本的 eBPF 安全性差别很大:

内核版本对比

这次事故用的就是 4.18。

如果你的生产环境还在用 CentOS 7(3.10 或 4.18 内核),跑 eBPF 就要有”可能会崩”的心理准备。不是说不能跑——很多公司跑得好好的——但你需要补上对应的监控和应急手段。


🛠️ 怎么修的

定位到问题后,修复和预防分几步走:

1. 立即止血:停掉冲突的 eBPF 探针

安全部停掉了其中一个 eBPF 探针,崩溃停止。这是紧急恢复手段——不是长久之计,但先止血。

2. 升级内核版本

长期方案是将测试集群和线上集群的内核从 4.18 升级到 5.x+。5.x 内核的 eBPF 验证器更强,BPF 链接机制更完善,多程序共存的隔离性大幅改善。

3. 建立 eBPF 程序注册与发现机制

部署任何 eBPF 工具前,必须先查询当前节点上已有的 eBPF 程序列表。可以通过 bpftool prog listbpftool map list 查看。我们把这个检查加到了部署流程的 pre-check 阶段。

4. 加内核 panic 告警

eBPF 崩了就是 kernel panic,但日志很容易被淹没在系统重启的噪音里。我们加了基于 dmesg 的 kernel panic 检测告警,重启后自动检查上次崩溃原因。


🔚 最讽刺的一个点

整件事最讽刺的地方在于:kindling 本身是用来观测系统的,结果它自己搞崩了系统。

我用一个观测工具看系统性能,结果这个观测工具本身变成了系统的问题。

这让我想到一句话:

在分布式系统里,观测链路本身也是系统的一部分。

你的监控探针可能比你监控的服务更危险—— 因为它有更大的权限(内核态), 有更低的可见性(不暴露在进程列表里), 有更少的测试(大部分 eBPF 工具在 CI 里跑的都是用户态模拟,不是真正的内核环境)。


✅ 复盘 Checklist:部署 eBPF 工具前过一遍

  • 当前内核版本是多少? 低于 5.x 要格外小心
  • 这台机器上已经有什么 eBPF 程序在跑了?bpftool prog list
  • 停掉这个探针,我有恢复手段吗? eBPF 崩了就是 kernel panic
  • 如果它崩了,我怎么知道是它崩的? 加 kernel panic 检测告警
  • 测试集群的内核版本跟线上一样吗? “测试”不等于”安全”
  • 多个 eBPF 工具之间有冲突风险吗? 老内核上隔离性不完善

eBPF 是把好刀,但它是一把在内核里转的刀。确认刀刃不会脱手之前,别急着开机。