← 返回首页

eBPF 可观测性实战:零侵入内核追踪

Linux 内核 2026-10-14 · 阅读约 13 分钟

eBPF(Extended Berkeley Packet Filter)是 Linux 内核中最具革命性的技术之一,允许在无需修改内核源码或加载内核模块的情况下,安全高效地在内核中运行沙箱程序。本文将深入 eBPF 的核心机制,并展示如何利用它构建零侵入的可观测性系统。

一、eBPF 架构与安全模型

eBPF 程序在加载到内核之前,必须通过 verifier(验证器)的严格检查。验证器确保程序不会无限循环、不会访问非法内存、指令数量有限,从而保证内核安全。

#include 
#include 

// eBPF 程序入口,挂载到 sys_enter_execve 追踪点
SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve(struct trace_event_raw_sys_enter *ctx) {
    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));

    char filename[256];
    bpf_probe_read_user_str(&filename, sizeof(filename), 
        (void *)ctx->args[0]);

    bpf_printk("execve: comm=%s file=%s\n", comm, filename);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

二、BPF 映射(Maps)——内核与用户态的数据桥梁

eBPF Maps 是内核态 eBPF 程序与用户态程序之间共享数据的主要机制。支持多种数据结构:哈希表、数组、环形缓冲区、LRU 哈希等。

// 定义一个哈希映射,用于统计系统调用次数
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);    // syscall number
    __type(value, u64);  // count
} syscall_count SEC(".maps");

SEC("tracepoint/raw_syscalls/sys_enter")
int count_syscalls(struct trace_event_raw_sys_enter *ctx) {
    u32 syscall_id = ctx->id;
    u64 *count = bpf_map_lookup_elem(&syscall_count, &syscall_id);
    if (count) {
        __sync_fetch_and_add(count, 1);
    } else {
        u64 init_val = 1;
        bpf_map_update_elem(&syscall_count, &syscall_id, &init_val, BPF_ANY);
    }
    return 0;
}

三、网络数据包深度分析

eBPF 可以挂载到 XDP(eXpress Data Path)或 TC(Traffic Control)层,在网卡驱动层面对数据包进行处理,实现高性能的 DDoS 防御和负载均衡。

SEC("xdp")
int xdp_firewall(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > data_end) return XDP_PASS;

    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end) return XDP_PASS;

    // 丢弃来自黑名单 IP 的数据包
    if (ip->saddr == bpf_htonl(0xC0A80101))  // 192.168.1.1
        return XDP_DROP;

    return XDP_PASS;
}

四、性能火焰图生成

通过 eBPF 采样内核和用户态的调用栈,可以生成 CPU 火焰图,精确定位性能瓶颈。结合 perf_event 子系统,以 99Hz 频率采样,开销极低。

struct stack_key {
    u32 pid;
    int user_stack_id;
    int kernel_stack_id;
};

struct {
    __uint(type, BPF_MAP_TYPE_STACK_TRACE);
    __uint(max_entries, 1024);
    __uint(key_size, sizeof(u32));
    __uint(value_size, PERF_MAX_STACK_DEPTH * sizeof(u64));
} stack_traces SEC(".maps");

SEC("perf_event")
int profile_cpu(struct bpf_perf_event_data *ctx) {
    struct stack_key key = {};
    key.pid = bpf_get_current_pid_tgid() >> 32;
    key.kernel_stack_id = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK);
    // 聚合统计并输出火焰图数据
    return 0;
}

五、生产环境最佳实践

在生产环境中使用 eBPF 需要注意:使用 CO-RE(Compile Once, Run Everywhere)技术解决内核版本兼容性、通过 BPF ring buffer 替代 perf buffer 提升性能、以及合理设置资源限制防止 eBPF 程序消耗过多 CPU。

eBPF 正在重塑云原生可观测性的格局,从 Cilium 的网络策略到 Pixie 的自动遥测,eBPF 已成为现代基础设施的核心技术。

← 返回首页