WEBKT

告别 /proc 慢查询:利用 eBPF 实时监控 Conntrack 表爆满风险

13 0 0 0

在处理高并发业务或遭受 DDoS 攻击时,很多运维和开发同学都遇到过内核丢包的“头号杀手”——table full: dropping packet

当我们发现网络请求开始超时,习惯性地通过 cat /proc/net/nf_conntrack | wc -l 或者 sysctl net.netfilter.nf_conntrack_count 来排查时,往往已经慢了。更糟糕的是,在连接数达到几十万量级时,直接读取 /proc 文件会触发内核对哈希表的完整遍历,这种 $O(n)$ 的操作在系统负载极高时,无异于火上浇油。

今天我们聊聊如何用 eBPF 替代传统的轮询巡检,实现一个毫秒级响应、低开销的 Conntrack 监控方案。

1. 传统方式的“痛点”

传统的监控脚本(如 iptables-save 或定时读取 procfs)存在两个核心缺陷:

  • 高延迟与采样失真:由于轮询间隔(通常是秒级)的存在,瞬时的连接数暴涨可能在两次采样之间发生又结束,导致监控曲线出现“诡异”的平滑,无法捕捉突发峰值。
  • 性能损耗:读取 /proc/net/nf_conntrack 会涉及到复杂的内核转换,将内核哈希表内容序列化为文本。当条目极多时,不仅占用大量 CPU,还会产生锁竞争。

2. eBPF 为什么是更优解?

eBPF 允许我们在内核特定函数执行时直接运行定制化逻辑。对于 Conntrack 监控,我们不需要遍历整张表,只需要“窥探”内核维护的那个计数器。

在 Linux 内核中,每个网络命名空间(net namespace)的 Conntrack 计数实际上是实时维护在 struct netns_ct 结构体中的 count 变量里的。

3. 实现思路:寻找最优挂载点

要实现实时监控,我们可以选择两种路径:

方案 A:挂载到连接创建/销毁函数(Kprobe)

我们可以 hook 住 nf_conntrack_allocnf_conntrack_free(或者更底层的销毁回调)。每当一个新的连接建立或被回收,我们的 eBPF 程序就读取当前的 count 值。

  • 优点:绝对实时。
  • 缺点:在高频短连接场景下,hook 调用过于频繁,会有微小的性能开销。

方案 B:定时采样计数器(Iterators/Perf Event)

利用 eBPF 的 BPF_PROG_TYPE_ITER 或者简单的定时任务,直接读取内核全局变量 nf_conntrack_count

4. 核心逻辑演示(伪代码)

下面是一个基于 eBPF 的简单监控逻辑示例,通过获取内核 net 结构体中的计数器实现:

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>

SEC("kprobe/__nf_conntrack_hash_insert")
int BPF_KPROBE(nf_ct_insert, struct nf_conn *ct) {
    struct net *net = read_net_from_ct(ct); // 获取网络命名空间
    u32 count = 0;

    // 直接从内核结构体读取当前计数值,无需遍历哈希表
    bpf_probe_read_kernel(&count, sizeof(count), &net->ct.count);

    // 获取当前上限
    u32 max = 0;
    bpf_probe_read_kernel(&max, sizeof(max), &net->ct.sysctl_checksum); // 示例路径

    // 如果超过阈值(如 80%),通过 ring buffer 发送给用户态告警
    if (count > (max * 80 / 100)) {
        struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
        if (e) {
            e->current_count = count;
            e->max_count = max;
            bpf_ringbuf_submit(e, 0);
        }
    }
    return 0;
}

5. 如何部署到生产?

你并不需要从头写 C 代码,可以利用现有的开源工具链快速落地:

  1. eBPF Exporter:Cloudflare 开源的工具。你可以编写一个简单的 YAML 配置,定义你想获取的内核变量偏移量,它会自动将其转为 Prometheus 指标。
  2. BCC / bpftrace:如果是临时排查,使用 bpftrace -e 'kprobe:__nf_conntrack_hash_insert { printf("CT count: %d\n", ...); }' 可以在几秒钟内建立一个临时的热点观测窗口。
  3. 自研 Agent:对于大规模集群,建议编写一个简单的 GO 程序,利用 cilium/ebpf 库加载 eBPF 字节码,并将数据推送到告警系统。

6. 进阶:不只是告警,还能分析“元凶”

eBPF 的强大之处在于,当 nf_conntrack_count 激增时,我们不仅能告警,还能顺便记录下触发连接创建最多的 源 IP协议端口

通过在 eBPF 程序中维护一个简单的 BPF_MAP_TYPE_HASH,统计当前时刻各源 IP 的并发连接数。一旦触发阈值,用户态程序直接从 Map 中拉取“连接大户”,配合告警通知直接下发给开发人员,定位效率比单纯的“告警-手动排查”高出几个量级。

总结

面对 Linux 内核的网络性能瓶颈,传统的轮询工具正在退出舞台。利用 eBPF 直接读取内核状态,不仅能让我们获得前所未有的观测精度,还能在问题发生的瞬间精准捕捉上下文。如果你还在被 Conntrack 爆满困扰,不妨试试这个“上帝视角”。

内核观察者 eBPFNetfilter网络性能优化

评论点评