DaemonSet运行eBPF程序必须以privileged权限、挂载/sys/fs/bpf和/lib/modules、启用hostPID,并采用CO-RE与幂等加载逻辑,否则因权限、内核符号或BTF缺失导致加载失败。

DaemonSet 要求 eBPF 程序必须在节点上本地加载,不能靠远程控制
eBPF 程序运行依赖内核头文件、BTF 信息和 bpf(2) 系统调用权限,而 Kubernetes Pod 默认是受限容器——没有 CAP_SYS_ADMIN、不挂载 /sys/fs/bpf、没提供 /lib/modules/$(uname -r)。直接把用户态加载器(比如用 libbpf-go 写的)扔进 DaemonSet 会卡在 permission denied 或 no such file or directory。
关键不是“能不能跑”,而是“谁来加载”:必须让容器具备内核交互能力,且加载时机要早于应用逻辑。
- 必须以
privileged: true运行,或至少显式添加CAP_SYS_ADMIN - 必须挂载宿主机的
/sys/fs/bpf(用于 map 持久化)和/lib/modules(编译期或运行时需读取内核符号) - 推荐使用
hostPID: true,避免因 PID namespace 隔离导致tracepoint或kprobe加载失败 - 镜像里不要打包内核头文件,而是通过挂载方式复用宿主机模块——否则不同节点内核版本不一致会导致
libbpf加载失败
用 libbpf-go + CO-RE 编写可移植加载器,避免节点间内核差异
硬编码 kprobe 地址或依赖特定内核结构体字段,在多节点集群中必然崩。CO-RE(Compile Once – Run Everywhere)是唯一靠谱路径,它靠 BTF + libbpf 的重定位能力实现跨内核版本兼容。
你的 Go 加载器代码里,ebpf.LoadCollectionSpec 后要检查 spec.Maps 和 spec.Programs 是否完整;加载失败时,libbpf 报错通常是 invalid argument 或 bad reloc,说明 BTF 缺失或 CO-RE 重定位失败。
立即学习“go语言免费学习笔记(深入)”;
- 构建镜像时,用
clang -target bpf -O2 -g -D__BPF_TRACING__ -I./headers ...编译,保留.debug_*和.BTFsection - 确保节点已开启 BTF(
/sys/kernel/btf/vmlinux存在),较新内核(5.8+)默认开启;老内核需用pahole -J生成 - Go 侧调用
ebpf.LoadCollection前,设ebpf.CollectionOptions.ProgramLoadOptions.LogLevel = 1,方便捕获 libbpf 日志定位重定位问题
DaemonSet YAML 必须绕过安全策略限制,且加载逻辑要幂等
哪怕配置全对,第一次启动成功了,节点重启后容器重建,eBPF 程序可能已驻留内核——重复加载会报 file exists(map 名冲突)或 program already loaded(attach 失败)。加载器必须能判断资源是否已存在。
典型错误是把 ebpf.LoadCollection 放在 main 函数入口无条件执行,没做 map 查重或 program 校验。
- 使用
ebpf.CollectionSpec.Map(name).CreateIfNotExist = true控制 map 创建行为 - 加载前先尝试
ebpf.LoadPinnedMap("/sys/fs/bpf/my_map"),命中则跳过创建 - DaemonSet 的
restartPolicy: Always是默认值,但你要保证容器退出后不反复拉起失败实例——建议加健康探针:exec: ["sh", "-c", "bpftool prog list | grep -q 'my_prog_name'"] - YAML 中务必设置
securityContext.privileged: true,hostPID: true,并挂载:volumeMounts: - name: bpf-fs mountPath: /sys/fs/bpf mountPropagation: Bidirectional - name: lib-modules mountPath: /lib/modules readOnly: true volumes: - name: bpf-fs hostPath: path: /sys/fs/bpf - name: lib-modules hostPath: path: /lib/modules
调试时别只看容器日志,得进节点查 bpftool 和 dmesg
容器里 fmt.Println("loaded!") 不代表程序真在运行。eBPF 加载成功只是第一步,attach 到 tracepoint/kprobe 后是否触发、是否有 perf event 丢包、map 数据是否更新,全得靠节点侧工具验证。
常见假成功现象:Go 程序返回 nil error,但 bpftool prog list 找不到对应 program,或 bpftool map dump name my_events 显示 0 条记录——大概率是 attach target 不存在(比如内核关闭了某个 tracepoint)或权限被 seccomp 拦截。
- 进节点执行
bpftool prog list | grep -A5 your_prog_name,确认tag和type正确(如tracepoint)、attach_type匹配 - 查
dmesg | tail -20,libbpf 加载失败常在这里吐详细错误,比如invalid instruction(BPF verifier 拒绝)或unknown btf id(BTF 缺失) - 如果用
perf_event_array,记得在 Go 里调用rd.Poll()并处理Lost字段,否则事件会静默丢失 - seccomp profile 若未显式放行
bpf和perf_event_open,即使 privileged 也会被拦截——K8s 1.25+ 默认启用RuntimeDefault,需在 pod spec 显式设securityContext.seccompProfile.type: Unconfined
真正卡住的地方,往往不是写 eBPF 逻辑,而是让那个 bpf(2) 系统调用在容器里顺利执行完——它需要内核、节点配置、容器权限、加载器健壮性四者严丝合缝。少一个环节,就只剩 operation not permitted。


















