直接用eBPF追踪容器进程会失败,因eBPF无容器概念,需结合cgroup v2路径匹配与kprobe读取exec参数:先由用户态扫描/proc/[pid]/cgroup获取v2路径并注入map,eBPF内核态前缀匹配;再用kprobe attach到do_execveat_common等函数,安全读取filename和argv。

为什么直接用 eBPF 追踪容器进程会失败
因为容器进程在宿主机上就是普通 fork() 出的进程,eBPF 程序本身看不到“容器”这个概念。你 attach 到 tracepoint:syscalls:sys_enter_execve 时,拿到的是宿主机视角的 pid_t 和 comm,没有 cgroup path、没有 container_id、甚至同一个容器里多进程的 PID namespace 也和宿主机不一致。不关联 cgroup v2 层级,就等于追踪了一堆裸 PID,根本分不清谁属于哪个容器。
必须读取 /proc/[pid]/cgroup 并匹配 cgroup v2 路径
eBPF 程序无法直接访问 /proc(内核态无 VFS 上下文),所以得靠用户态配合:在内核中用 bpf_get_current_pid_tgid() 拿到当前进程 PID,再由用户态程序定期扫描 /proc/[pid]/cgroup,提取 cgroup v2 的 0::/kubepods/burstable/podxxx/... 这类路径。关键点:
- 只认 cgroup v2 —— 如果系统是 cgroup v1,
/proc/[pid]/cgroup格式不同,且无法可靠映射到容器;确认方式:stat -fc %T /sys/fs/cgroup输出应为cgroup2fs - 路径中包含 pod UID 或 container ID 的部分通常在最后一段,比如
.../pod-8a7f3e4c-9d2a-4b1c-8f0a-1234567890ab/或.../containerd-1a2b3c4d5e... - 不要依赖
/proc/[pid]/status中的NSpid——它只反映 PID namespace 层级,不携带容器元数据
用 libbpf-go 加载 tracepoint 并过滤目标 cgroup
用户态需维护一个运行中容器的 cgroup path 映射表(例如从 /sys/fs/cgroup/cgroup.procs 反查或监听 cgroup.events),然后在 Go 中用 libbpf-go 加载 eBPF 程序,并在 map 中写入允许的 cgroup paths 前缀(如 /kubepods/ 或具体 pod 路径)。示例关键逻辑:
// 用户态预加载允许的 cgroup 前缀
cgMap := obj.Maps.cgroup_map
for _, prefix := range []string{"/kubepods/burstable/pod-8a7f3e4c", "/docker/abcd1234"} {
key := [16]byte{}
copy(key[:], prefix)
_ = cgMap.Update(&key, &uint32(1), libbpf.MapUpdateAny)
}
<p>// eBPF 内核代码中做前缀匹配(需用 bpf_strncmp 或手动字节比对)
if (!bpf_strncmp(cgrp_path, allowed_prefix, sizeof(allowed_prefix) - 1)) {
bpf_printk("hit container exec: %s", filename);
}
注意:bpf_strncmp 不支持动态长度,必须用编译期确定的常量长度;否则要用循环逐字节比对,避免越界访问。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
execve 参数获取要绕过 BPF_PROG_TYPE_TRACEPOINT 的限制
tracepoint 类型的 eBPF 程序(如 syscalls:sys_enter_execve)无法直接读取用户态内存,filename 和 argv 是用户地址,不能用 bpf_probe_read_user 直接读——会触发 verifier 拒绝。正确做法:
- 改用
BPF_PROG_TYPE_KPROBEattach 到__x64_sys_execve或do_execveat_common,它们的上下文允许安全读取struct pt_regs *中的寄存器值(如rdi指向 filename) - 用
bpf_probe_read_user_str读取字符串,但必须指定最大长度(如 256),且返回值要检查是否截断(返回值 > 缓冲区大小表示截断) - argv 数组需先读指针数组头,再逐个读字符串;不建议全量读取,可只取前 3 个参数用于识别命令意图
真正难的不是写 eBPF,而是让每次 exec 都能稳定拿到容器身份 + 命令行,中间任何一环(cgroup 扫描延迟、kprobe 符号变化、字符串读取越界)都会导致漏事件或 crash。

















