排查过程中最让人血压升高的,往往不是底层的内核 Bug,而是安全策略的“盲目自信”。近期处理了一起严重的生产事故:某高吞吐的 Kafka 与 Elasticsearch 混合部署集群,在安全团队下发新版容器运行时合规规则后,多台 Node 节点相继出现 Load Average 飙升至 200+,Kubelet 心跳超时导致节点变成 NotReady,业务大面积断流。
一句话总结排查结论:安全工程师在 Falco 规则中写了一个监听 write 和 pwrite64 系统调用的规则,但漏掉了针对文件路径的宏过滤(Macro Filter)。这导致底层 eBPF 探针毫无节制地拦截节点上每秒数十万次的高频 I/O 系统调用,海量事件瞬间撑爆 eBPF Perf Ring Buffer,引发严重的 CPU Sys 态占用与 Soft Lockup,最终饿死 Kubelet 进程。
防御性安全加固是必须的,但脱离业务压测的规则下发,本质上就是对生产环境的自杀式 DDoS。
案发现场:系统态 CPU 的狂欢
监控系统发出刺耳的告警,Kafka 集群的 P99 延迟从 10ms 飙升到了 5000ms。切到终端,尝试 SSH 登录故障 Node,光是建立连接就卡了近十秒。
好不容易敲下 top 命令,看到的数据令人极度不适:
%Cpu(s): 5.2 us, 88.4 sy, 0.0 ni, 1.1 id, 0.2 wa, 0.0 hi, 5.1 si, 0.0 st
Load average: 214.35, 180.12, 110.45
用户态(us)CPU 只有 5%,而系统态(sy)竟然高达 88%,软中断(si)也有 5%。这说明 CPU 根本没在处理业务逻辑,全在内核态里打转。
用 pidstat -p ALL 1 查看具体是谁在消耗 CPU,排在第一的是 falco 进程,单进程跑满了约 400% 的 CPU(4核),但这还不足以解释整个 64 核宿主机的瘫痪。
真正致命的信息藏在 dmesg 里:
[ 3451.123456] NMI watchdog: BUG: soft lockup - CPU#12 stuck for 22s! [java:14562]
[ 3451.123470] RIP: 0010:bpf_prog_3a2b1c4d_falco_sys_enter+0x124/0x500
[ 3451.123485] Call Trace:
[ 3451.123490] <TASK>
[ 3451.123492] trace_call_bpf+0x9a/0x150
[ 3451.123495] perf_trace_sys_enter+0x140/0x200
[ 3451.123500] syscall_trace_enter.constprop.0+0x1a8/0x220
[ 3451.123505] do_syscall_64+0x15/0x80
内核调用栈清晰地指明了真凶:bpf_prog_..._falco_sys_enter。Kafka 进程(Java)在发起系统调用时,被 Falco 的 eBPF 程序钩住,由于处理逻辑极其繁重,直接触发了 CPU 软锁死(Soft Lockup)。
与此同时,查看 Falco 自身的日志:
{"level":"warning","msg":"Falco internal: drop event. Total drops: 850392019"}
系统正在以每秒数百万的量级丢弃事件。
抽丝剥茧:愚蠢的规则与 eBPF 的阿喀琉斯之踵
为了快速恢复业务,第一反应是直接介入现场,执行 systemctl stop falco 并 kubectl delete ds falco -n falco。拔掉这个“安全探针”后,节点 Load 瞬间掉回个位数,Kafka 恢复正常。
业务稳住了,接下来就是扒安全团队的“底裤”。调出引发故障的那个自定义规则配置文件 falco_rules.local.yaml,找到了罪魁祸首:
- rule: Detect Suspicious File Modifications
desc: Monitor write operations to system directories
condition: >
evt.type in (write, pwrite64, pwritev)
and container.id != host
and proc.name != "fluentd"
output: "Suspicious write detected (user=%user.name file=%fd.name)"
priority: WARNING
看懂了吗?写规则的人忘记加上目标目录的限制。
他们的本意可能是监控对 /etc 或 /bin 的写操作,但由于漏掉了类似 fd.name startswith "/etc/" 的前置过滤宏,这条规则的语义变成了:拦截所有容器内除 fluentd 外的任意写操作。
要理解为什么这会导致系统雪崩,必须弄懂 Falco eBPF 探针的工作原理。
Falco 的架构分为内核态的 eBPF 探针和用户态的规则引擎。内核探针挂载在 raw_tracepoint/sys_enter 和 sys_exit 上。
当 Falco 启动时,它会解析所有启用的规则,提取出需要监听的系统调用类型(Syscall ID)。一旦任何一条规则声明了对 write 系统调用的监听,eBPF 探针就会在内核中对全局所有的 write 操作进行上下文采集。
在这个 Kafka/ES 集群中,I/O 是极其密集的。
-
每次
write,eBPF 程序都会被触发。 -
eBPF 需要从内核空间提取进程信息、文件描述符、参数,打包成事件结构体。
-
将事件推入 Perf Ring Buffer 或 BPF Ringbuf。
-
如果用户态 Falco 引擎处理速度跟不上,Ring Buffer 就会满。
-
Buffer 满后,eBPF 程序内部自旋锁或丢弃逻辑会产生极大的 CPU 开销,严重拉长了
write系统调用的耗时。
原本一个只需几微秒的内核写操作,被强行注入了高昂的 eBPF 观测开销。海量的事件上下文切换不仅榨干了 CPU Sys 资源,更直接饿死了 Kubelet 的 PLEG(Pod Lifecycle Event Generator)循环,导致集群管控面认为节点宕机,开始触发 Pod 驱逐,最终演变成全局雪崩。
修复与避坑:防御性观测的底线
事后复盘,我给安全团队划定了三条绝对不可触碰的红线:
-
绝对禁止无限制拦截高频 I/O 系统调用。 像
read,write,recvfrom,sendto,epoll_wait这些系统调用,在任何生产环境的运行时安全监控中,除非在 eBPF 层面有极度严苛的 In-Kernel 过滤机制,否则坚决不能放入全局监控规则中。 -
修正规则语义,利用内核态过滤。 如果非要监控关键文件的修改,不应该去 Hook 宽泛的
write。更好的方式是使用openat系统调用配合O_TRUNC/O_RDWR标志位,或者利用 eBPF 的 LSM(Linux Security Modules)钩子针对特定 inode 进行监控。 修改后的合理规则应该尽量将条件收敛:yaml condition: > open_write and container and fd.name startswith "/etc/" -
容器安全探针必须做资源硬隔离。 Falco DaemonSet 必须配置严格的 CPU Limit 和优先级。如果它的性能跟不上,宁可让它 OOM 或被 Cgroup 限制,也不能让它拖垮整台宿主机的内核栈。
排查清单:Falco/eBPF 性能雪崩同类问题速查
如果你怀疑容器安全组件引发了性能问题,请按以下步骤快速确认:
-
CPU 态势观测:使用
top或mpstat观察sy(系统态)指标,如果sy持续异常偏高(>50%),且wa(I/O等待)不高,大概率是系统调用被大量 Hook 或频繁陷入内核态引发。 -
内核阻塞确认:通过
dmesg -T | grep -i "soft lockup"检查是否出现 CPU 死锁。如果 Call Trace 中包含bpf_prog_、trace_call_bpf或perf_trace_sys_enter,直接锁定 eBPF 探针。 -
Drop 指标核对:检查 Falco 或其他安全探针的 Metrics / 日志,搜索
Total drops或Ring buffer full关键字。事件大量丢弃是规则过于宽泛的铁证。 -
探针快速止血:情况危急时,不要花时间分析规则,直接停止安全组件服务(
systemctl stop或删减 DS),若系统负载在 5 秒内骤降恢复,即可定性为该组件引发的故障。
安全不仅要防范外部的黑客,更要防范内部失控的代码。在操作系统底层,哪怕是一行看似无害的过滤遗漏,也会在流量洪峰下化作摧毁整个集群的核弹。