尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

eBPF与BCC实战:从内核插桩到可观测性工具开发指南

eBPF与BCC实战:从内核插桩到可观测性工具开发指南 1. 先聊清楚eBPF到底是什么为什么这两年这么火很多人第一次听到eBPF都是被那句内核里跑JavaScript或者给内核装个摄像头给吸引过来的。说实话这种类比不算错但容易让人误解。我自己的理解是eBPF就是一张可以在Linux内核里安全运行的虚拟机通行证它能让你把一段自定义的逻辑挂到内核函数的入口、出口、网络数据包路径甚至硬件事件上在不改内核源码、不重启机器、不加载危险内核模块的前提下拿到系统内部运行时的真实状态。那BCC是什么呢全称是BPF Compiler Collection直译过来就是BPF编译器集合。它是目前生态里最成熟、最好上手的一套eBPF开发工具链底层用C语言写内核插桩程序用户态用Python控制整个生命周期把编译、加载、挂载、采集数据、清理资源这一整套动作封装得明明白白。我经常跟别人说如果你只想快速看到系统里发生了什么不用去啃内核源码BCC是你最省力的切入点如果你想深入搞性能调优或者做云原生可观测性BCC又是绕不开的底子。这篇文章适合谁我觉得有两类人。第一类是刚接触云原生、容器、微服务遇到问题只能靠重启大法的运维和开发BCC能让你像老中医一样望闻问切直接看到进程在等什么、锁在等谁、网络包堵在哪。第二类是已经开始研究eBPF但被一堆内核概念kprobe、tracepoint、perf event、BTF劝退的初学者我会尽量把这些词拆开揉碎配合真实可跑的代码让你看完就能上手。先说一句大实话eBPF不是万能的。它拿不到用户态的所有内存内容也不适合做需要在内核里长时间跑复杂计算的事情更不是让你绕过Linux安全机制的旁门左道。但它在可观测性、性能分析、安全监控、网络转发这几个方向上的能力确实强得离谱以至于Google、Meta、Netflix这些公司早就在生产环境里大规模用了。下面我就从eBPF的底层原理开始一步步带你走到BCC的实际开发。1.1 从内核钩子说起为什么传统方法都不够好在eBPF出现之前如果你想观测内核的行为大概有这几条路第一改内核源码重新编译。这在生产环境基本不可行内核动一发牵全身何况大多数公司用的还是发行版自带的内核根本没资格改。第二写内核模块。这是最灵活的办法但也是最危险的。一个内核模块里就算只有一行空指针解引用都可能直接导致整个系统panic。我在早期的排查经历里确实见过同事调试内核模块的时候把线上机器搞挂那种压力不是谁都承受得起的。第三用系统自带的工具比如perf、strace、tcpdump。这些工具能力有限它们更像是已经拍好的照片你只能看到预定义好的那些事件不能根据自己的业务逻辑去定制采集规则。eBPF的聪明之处在于它把内核代码降级成了内核数据。你的代码在被加载之前要经过一个严格的验证器verifier它逐条指令检查内存访问边界、循环次数、死锁风险保证这段程序绝不会让内核崩溃或陷入死循环。这就意味着你拥有了和内核模块一样强大的插桩能力却没有内核模块那样的杀伤力。我打一个生活化的比方。传统内核模块像是你拿着钥匙直接闯进别人家的厨房能做饭也能烧了房子eBPF则像是隔着玻璃橱窗操作机械臂做饭动作范围被严格限定在安全区域但依然能切菜、炒菜、颠勺。这个让你动又能保证你不搞破坏的机制是eBPF一切能力的基础。1.2 eBPF能做什么不能做什么搞清楚边界比学会API更重要。先列一下eBPF的典型战场可观测性统计某个系统调用的频率、延迟分布追踪函数参数和返回值实时分析进程的CPU、内存、IO行为。性能剖析通过采样内核态和用户态调用栈快速定位CPU热点函数解决到底谁在消耗CPU这类问题。网络与安全在TCTraffic Control层或XDPeXpress Data Path层处理数据包实现负载均衡、DDoS防护、容器网络策略。故障排查追踪锁竞争、文件读写延迟、socket生命周期配合指标定位系统慢在哪里。再说不建议做的在内核里做大量计算或复杂的状态机逻辑eBPF程序的指令上限和执行时间都受到严格限制。绕过安全策略去偷看其他进程的内存eBPF虽然能访问部分内核结构和进程上下文但它的设计初衷是观测与保护不是越权。追求完全的跨内核版本兼容却不愿意花时间了解BTF和CO-RE这是很多初学者踩坑的根源。清楚了这些边界你再看BCC就不会觉得它是个玄学工具了。它只是把eBPF的插桩、验证、加载、数据采集这些流程做了工程化封装让你专注在采集什么数据和怎么解释数据上。2. BCC让eBPF从内核专家玩具变成通用排查利器BCC这个项目最早由IO Visor社区发起算是eBPF工具链里的老大哥。我最开始接触BCC的时候它的文档还比较简陋很多用法要靠读源码和看示例程序去猜。但即便如此BCC还是比直接用clang bpftool 裸写BPF指令要友好一个数量级。它的存在极大降低了eBPF的上手门槛也让业内多了无数基于它的二次开发项目。2.1 BCC解决的核心痛点编译、加载、采集三座大山先说编译。eBPF程序运行在内核所以你得把C代码编译成BPF字节码。问题来了BPF字节码的生成需要clang/LLVM的特定后端支持而且不同内核版本支持的指令集和辅助函数还不完全一样。BCC的做法是内部封装好编译流程你只需要写C代码和Python代码BCC会在运行的时候自动调用本地clang去完成编译。再说加载。编译出来的字节码还得通过系统调用bpf系统调用加载进内核经过verifier验证后再挂载到指定的钩子点。这个流程里有无数细节比如程序类型要选对是kprobe程序还是tracepoint程序权限要够一般是root或者CAP_BPFmap要提前创建好。BCC把这些动作全部封装成Python接口你调用一个函数就完成了真的是一顿操作猛如虎一看代码没几行。最后说采集。eBPF程序本身在内核态运行采集到的数据要送到用户态去看这个通道通过BPF map实现。map的类型有哈希表、数组、环形缓冲区、栈等BCC统一抽象成Python对象你往内核程序里写数据在Python里读数据完全不用关心底层是perf event还是ring buffer。这一层抽象让写BCC程序变得像写普通Python脚本一样顺畅。2.2 BCC的核心组件工具集、框架、底层库BCC项目其实分三层tools目录内置了上百个现成工具比如bcc/opensnoop追踪文件打开、bcc/biolatency块IO延迟统计、bcc/execsnoop追踪进程执行、bcc/tcpconnect追踪TCP连接。这些工具开箱即用很多场景你根本不用自己写代码直接用它们就能定位问题。Python/Go/Lua绑定BCC官方提供了Python绑定后来社区也贡献了Go绑定。你可以把BCC当作一个库在自己的脚本里嵌入eBPF程序。这也是我日常开发中最常走的路径。底层依赖BCC依赖libbpf和内核头文件。在内核开启BTFBPF Type Format的情况下BCC可以做到一次编译到处运行CO-RE这对于要跑在多版本内核上的生产环境来说价值极高。我当时给团队推BCC时最打动他们的就是那个tools/*slower*系列工具。比如filetop、biosnoop这种工具几个字母就能在内核里实时追踪到文件读写、块设备IO的来源放在以前你得用perf写半天脚本才能勉强达到一半效果。2.3 BCC与libbpf、bpftrace怎么选很多人问有BCC了为什么还要libbpf和bpftrace我的理解是这样的BCC适合写逻辑相对复杂的监控程序内核态部分用C用户态用Python生命周期管理方便开发效率高。libbpf是更贴近底层的库配合CO-RE机制适合把eBPF程序编译成独立的二进制文件嵌入到Go、Rust等应用里适合对启动速度和部署体积有要求的场景。bpftrace是一个专门用AWK风格语法写eBPF脚本的工具适合快速做一次性诊断比如统计每个进程打开文件的次数一行脚本就搞定了写复杂逻辑反而会力不从心。我的建议是想快速验证想法用bpftrace想认真做一个巡检工具或者故障排查脚本用BCC想嵌入到自己的服务里做生产级采集器学libbpf。三者不是替代关系而是不同层级的工具。3. 从零搭建BCC开发环境三分钟跑通第一个程序BCC的环境搭建网上教程很多但很多都给你贴一堆源码编译步骤实际上在主流发行版上根本没那么复杂。我试过Ubuntu、Debian、CentOS、Fedora以下方案都是验证过能跑的。3.1 安装BCC不同发行版的正确姿势在Ubuntu 20.04及以上版本直接用官方仓库的二进制包就行sudo apt-get update sudo apt-get install bpfcc-tools linux-headers-$(uname -r)注意Ubuntu上BCC工具的命令名称后面会有bpfcc后缀比如opensnoop对应的是opensnoop-bpfcc这和其他发行版不一样新手经常在这里栽跟头。在CentOS/RHEL 8上如果内核支持BTF通常4.18以上且开启了CONFIG_DEBUG_INFO_BTF可以直接用iovisor的yum仓库sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm sudo yum install -y bcc bcc-tools python3-bcc如果你像我一样喜欢追新内核特性那就得从源码编。BCC的官方仓库有编译脚本但需要装clang、llvm、cmake、flex、bison、libelf-dev等一系列依赖整个过程大概十几分钟。我个人的建议是除非你需要BCC没有打包的新特性否则优先用发行版仓库的包省时省力还稳定。注意BCC依赖的内核头文件必须和当前运行的内核版本严格匹配。如果你自己升级过内核却忘了装对应的linux-headers或kernel-develBCC加载时会直接报找不到vmlinux.h或内核模块符号。3.2 验证安装先跑一个现成工具安装完成之后先别急着写代码跑一个自带的工具测试环境sudo opensnoop-bpfcc你会看到终端不断刷新每一行代表一个文件打开事件包含进程名、PID、文件描述符和文件路径。如果这个命令能正常输出恭喜你BCC环境已经通了。如果报Failed to load BCC》之类的错误大概率是内核头文件缺失或者权限问题先回头检查3.1节。我强烈建议你把/usr/share/bcc/tools/目录下的工具都过一遍每个都是一份活文档比任何教程都直观。我之前排查容器里文件句柄泄漏就是用bcc/opensnoop和bcc/filetop两个工具配合十分钟就定位到是某个日志库没有关闭句柄。3.3 第一个BCC程序Hello World与事件采集框架现在写一个最简单的BCC程序功能是打印每次execve系统调用的进程名和参数。这是所有BCC教程的Hello World#!/usr/bin/python3 from bcc import BPF # 这部分C代码会被编译成BPF字节码加载进内核 bpf_text int hello(void *ctx) { char msg[] Hello, eBPF!; bpf_trace_printk(msg, sizeof(msg)); return 0; } b BPF(textbpf_text) # 挂载到所有execve系统调用入口 b.attach_kprobe(eventexecve, fn_namehello) print(追踪中按CtrlC退出...) while True: try: (task, pid, cpu, flags, ts, msg) b.trace_fields() print(fPID {pid}: {msg.decode()}) except KeyboardInterrupt: break这段代码里有个非常核心的APIattach_kprobe(eventexecve, fn_namehello)。它的意思是把内核函数hello挂到execve这个内核函数的入口处。每次任何进程执行execve内核就会调用hello并通过bpf_trace_printk把消息打印到trace管道。运行sudo python3 hello.py你每敲一条命令比如ls、cat、echo就能看到对应的输出。这就完成了第一次在不改内核代码的情况下从内核态采集到用户态日志的完整闭环。提示bpf_trace_printk的性能开销很大生产环境不要高频调用它。它只是学习期的调试工具实际项目里应该用BPF map采集聚合数据在用户态周期性读取。4. BCC开发的核心概念与API再也不用怕kprobe、tracepoint这些黑话BCC用起来不难但你要真正掌握它必须理解里面几个关键概念。这些概念也是eBPF生态的通用词汇看懂它们你再去看libbpf、bpftrace的代码会轻松很多。4.1 动态插桩kprobe与kretprobekprobe是内核提供的动态探针机制可以在任意内核函数入口处插入探针。kretprobe则是在函数返回处插入探针。BCC里对应两个方法attach_kprobe(event函数名, fn_name处理函数)attach_kretprobe(event函数名, fn_name返回处理函数)一个经典的用法是统计某个函数的耗时。你在入口记录当前时间戳存入map在返回处读取时间戳算出差值。这个思路可以用来给任何内核函数做延迟统计我经常用它排查存储相关的内核路径瓶颈。bpf_text BPF_HASH(start, u32, u64); // 以PID为键存时间戳 int do_entry(struct pt_regs *ctx) { u32 pid bpf_get_current_pid_tgid(); u64 ts bpf_ktime_get_ns(); start.update(pid, ts); return 0; } int do_return(struct pt_regs *ctx) { u32 pid bpf_get_current_pid_tgid(); u64 *ts start.lookup(pid); if (ts 0) return 0; // 没找到入口记录就跳过 u64 delta bpf_ktime_get_ns() - *ts; bpf_trace_printk(PID %d 耗时 %lld ns, pid, delta); start.delete(pid); return 0; } b BPF(textbpf_text) b.attach_kprobe(eventvfs_read, fn_namedo_entry) b.attach_kretprobe(eventvfs_read, fn_namedo_return)这里要特别提醒bpf_ktime_get_ns()获取的是系统启动以来的单调时钟适合算差值千万别用它当绝对时间戳。动态插桩的威力在于它不依赖内核是否预留了埋点随便一个函数名只要能通过/proc/kallsyms查到符号就能探。缺点也很明显内核内部函数名会随着版本变化你的脚本可能换个内核版本就挂了。所以生产环境我会优先用下一节说的tracepoint实在找不到对应的tracepoint才用kprobe。4.2 静态追踪tracepoint与TP_PROTOtracepoint是内核开发者预定义好的静态跟踪点它有稳定的ABI某种程度上来讲这是官方认可的观测入口。因为不依赖函数名兼容性比kprobe好太多。在BCC里挂载tracepoint的API是b.attach_tracepoint(tpsyscalls:sys_enter_openat, fn_nametrace_openat)事件参数要这样获取bpf_text int trace_openat(struct tracepoint__syscalls__sys_enter_openat *args) { u32 pid bpf_get_current_pid_tgid(); char filename[256]; // args-filename 是用户态指针需要bpf_probe_read读取 bpf_probe_read_user(filename, sizeof(filename), args-filename); bpf_trace_printk(PID %d 打开文件: %s, pid, filename); return 0; } 这里你可能会好奇struct tracepoint__syscalls__sys_enter_openat这个结构体是哪来的。它是BCC根据内核的tracepoint格式自动生成的里面的字段名可以在/sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/format里查到。我踩过的坑是直接访问用户态指针比如args-filename会导致verifier拒绝加载必须用bpf_probe_read_user或bpf_probe_read_kernel去安全读取。这个规则新手一定要记住否则你会被一堆invalid mem access错误折磨到怀疑人生。4.3 BPF Map内核与用户态之间的数据通道BPF map是eBPF程序的内核态数据结构也是内核态和用户态交换数据的桥梁。BCC的map定义方式非常简洁BPF_HASH(counter, u32, u64); // 哈希表key为u32value为u64 BPF_ARRAY(events, u64, 1024); // 定长数组1024个u64 BPF_PERCPU_ARRAY(stats, u64, 128); // 每CPU数组避免锁竞争在用户态Python里你可以像操作字典一样操作map# 读取所有键值 for key, value in b[counter].items(): print(f键 {key.value} 计数 {value.value}) # 直接更新 b[counter][ctypes.c_uint32(pid)] ctypes.c_uint64(100)这里又有一个新手陷阱map的key/value类型必须和C侧声明完全一致包括无符号类型。比如C侧是u32Python侧就得用ctypes.c_uint32用int虽然大多数时候可以但遇到负值或大数就会对不上。我写过一个统计系统调用次数的工具就是在这里踩了一脚数据总是对不上最后查了半天才发现是字段类型不匹配。4.4 数据采集模式ring buffer vs perf buffer早期BCC程序都用perf buffer把事件流送到用户态它的问题是当事件过多时会有一定的乱序和丢事件风险。现在的推荐方案是ring bufferBPF ring buffer它支持高性能、乱序可控、动态数据长度。BCC从较新版本开始支持BPF_RINGBUF_OUTPUTBPF_RINGBUF_OUTPUT(events, 8); // 8页大小 // 提交事件 int submit_event(struct pt_regs *ctx) { struct event_t e {}; e.pid bpf_get_current_pid_tgid(); events.ringbuf_output(e, sizeof(e), 0); return 0; }Python侧接收def callback(ctx, data, size): event b[events].event(data) print(f收到事件PID{event.pid}) b[events].open_ring_buffer(callback) b.ring_buffer_poll()我用ring buffer重写过几版采集工具最大的感受是吞吐能力比perf buffer高不少特别是面对每秒数万次事件流的时候基本不再丢数据。在新项目里优先用ring buffer就对了。5. 实操用BCC排查真实问题从看懂到能自己写工具理论讲再多不如亲手跑几个案例。这一节我会分享三个我实际用过的场景从简单到复杂每个场景都会给出完整代码和关键解释你可以直接抄作业。5.1 追踪文件打开操作快速定位谁动了我的文件场景描述线上有个服务日志里不断出现Too many open files你怀疑是哪个模块在疯狂打开文件不关闭。传统做法是lsof -p PID看一下但lsof只能看到当前的快照看不到打开动作是从哪来的。用BCC写一个opensnoop的简化版#!/usr/bin/python3 from bcc import BPF import ctypes bpf_text #include uapi/linux/ptrace.h #include uapi/linux/limits.h struct data_t { u32 pid; char comm[TASK_COMM_LEN]; char fname[NAME_MAX]; }; BPF_PERF_OUTPUT(events); int trace_openat(struct tracepoint__syscalls__sys_enter_openat *args) { struct data_t data {}; data.pid bpf_get_current_pid_tgid(); bpf_get_current_comm(data.comm, sizeof(data.comm)); bpf_probe_read_user(data.fname, sizeof(data.fname), args-filename); events.perf_submit(data, sizeof(data)); return 0; } b BPF(textbpf_text) b.attach_tracepoint(tpsyscalls:sys_enter_openat, fn_nametrace_openat) print(追踪openat系统调用CtrlC退出) def print_event(cpu, data, size): event b[events].event(data) print(fPID {event.pid} 进程 {event.comm.decode()} 打开 {event.fname.decode()}) # 可以在这里加过滤逻辑比如只看某个进程 b[events].open_perf_buffer(print_event) while True: try: b.perf_buffer_poll() except KeyboardInterrupt: break跑起来之后把输出重定向到文件或者用grep过滤目标进程名就能看到它每秒钟打开哪些文件。上个月我就靠这个工具发现某个Java服务疯狂打开/tmp/*.tmp文件最后定位到是临时文件清理逻辑有bug。5.2 统计系统调用频率从系统很慢到哪里慢遇到系统负载很高但不知道谁在干活第一反应是看CPU占用但CPU占用高的原因太多了。用BCC统计各个进程的系统调用次数能帮你快速缩小范围。#!/usr/bin/python3 from bcc import BPF import time bpf_text BPF_HASH(syscall_count, u32, u64); int count_syscall(struct tracepoint__raw_syscalls__sys_enter *args) { u32 pid bpf_get_current_pid_tgid(); u64 *count syscall_count.lookup(pid); if (count) { (*count); } else { u64 one 1; syscall_count.update(pid, one); } return 0; } b BPF(textbpf_text) # 追踪所有系统调用入口 b.attach_tracepoint(tpraw_syscalls:sys_enter, fn_namecount_syscall) print(统计中按CtrlC打印结果...) while True: try: time.sleep(5) except KeyboardInterrupt: break print(--- 当前Top 10 ---) for k, v in sorted(b[syscall_count].items(), keylambda kv: kv[1].value, reverseTrue)[:10]: print(fPID {k.value}: {v.value} 次) b[syscall_count].clear()注意这里用的是raw_syscalls:sys_enter这个tracepoint覆盖了所有系统调用不需要一个一个去挂。输出里会发现某些PID的计数高得离谱再用strace -p PID跟进就能看到具体是哪个系统调用。5.3 追踪函数参数与返回值深入内核内部机制有时候工具层面的观测不够你得看到内核函数到底接收了什么参数、返回了什么结果。kprobe这时候就派上用场了。下面以tcp_sendmsg为例统计每个TCP连接发送的数据量#!/usr/bin/python3 from bcc import BPF import ctypes bpf_text struct tcp_info_t { u32 pid; u32 saddr[4]; u32 daddr[4]; u16 sport; u16 dport; u64 sent_bytes; }; BPF_HASH(total_sent, u64, struct tcp_info_t); int trace_tcp_sendmsg(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size) { u32 pid bpf_get_current_pid_tgid(); u64 key pid; u64 *existing total_sent.lookup(key); struct tcp_info_t info {}; info.pid pid; info.sent_bytes size; // 解析TCP控制块里的四元组 struct sock_common *skc sk-__sk_common; info.saddr[0] skc-skc_rcv_saddr; info.daddr[0] skc-skc_daddr; info.sport skc-skc_num; info.dport skc-skc_dport; total_sent.update(key, info); return 0; } b BPF(textbpf_text) # kprobe传递内核函数参数的方式函数原型里声明 b.attach_kprobe(eventtcp_sendmsg, fn_nametrace_tcp_sendmsg) print(追踪TCP发送...)这里有个非常重要的点如果用kprobe你要在BPF C函数原型里完全按照被探测函数的参数列表来声明参数。比如tcp_sendmsg的原型是int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)那么你的程序里就要有对应的三个参数前面的struct pt_regs *ctx是BCC自动加的用来访问寄存器。如果不清楚函数原型可以先从/proc/kallsyms确认符号存在再用grep在内核源码里查原型。提醒访问内核结构体字段比如sk-__sk_common.skc_daddr时最好先确认目标内核版本里这个字段的路径没变否则编译和验证都会报错。我通常先在测试环境里跑一遍再上生产。6. 常见问题与排查技巧实录这一节能救你的命BCC环境看着简单实际开发中会遇到各种问题。我把这几年踩过的坑整理成一个速查表希望能帮你少走弯路。6.1 编译加载类问题报错症状常见原因解决办法Failed to load BCC: Unknown symbol内核头文件不匹配或者kprobe函数名在当前内核不存在核实uname -r安装对应头文件用grep查询/proc/kallsyms确认函数名invalid mem access指针类型错误直接访问了用户态或者内核态未授权区域用bpf_probe_read_user或bpf_probe_read_kernel读取BPF program too large程序指令数超限或循环次数太多简化逻辑用map做聚合而不是逐事件处理Permission denied当前用户不是root或者容器缺少CAP_BPF、CAP_SYS_ADMIN用sudo运行容器场景加--privileged或明确为容器授予能力我从第一次上手到能熟练写BCC程序差不多花了一个星期其中三分之一时间都在和这些报错搏斗。印象最深的是invalid mem access后来才明白eBPF的验证器不允许直接解引用指针一切外部内存的访问都必须通过辅助函数这是设计使然不是bug。6.2 调试BCC程序的三板斧遇到问题时我会按照下面这个顺序排查第一用python3直接跑看它输出的完整堆栈。BCC的报错其实挺详细的它会告诉你具体是哪一行编译不过去或者verifier的检查失败原因是什么。别急着去网上搜先自己读一遍报错信息。第二打开BCC的调试输出可以看到编译命令和加载过程b BPF(textbpf_text, debug0x02) # 0x02代表打印verifier日志这个开关能看到verifier的详细检查过程虽然信息量大但能定位到具体是哪条指令出了问题。第三在C代码里加日志是个笨办法但很有效。bpf_trace_printk的字符串会被送到/sys/kernel/debug/tracing/trace你可以开一个终端窗口cat /sys/kernel/debug/tracing/trace_pipe实时看内核态打印的消息。注意这个操作本身有一定性能开销纯调试时用。6.3 生产环境使用BCC的几条心得体会BCC程序写完之后不是跑通就完了。我在生产环境部署过几套BCC采集工具总结了几条经验这里一并分享第一不要在生产环境直接挂kprobe探到高频函数而不加过滤。比如tcp_sendmsg、vfs_read这种超级热点的函数每个包都要触发再牛的机器也扛不住。一定要在程序入口加采样率控制或者用map做聚合只上报变更后的数值。第二采集的数据要尽量在用户态做后处理。内核态负责采集原始数据聚合、关联、告警这些逻辑放到Python或者下游系统里。这样可以避免eBPF程序占用过多内核资源也让调试更灵活。第三持续关注内核版本的eBPF特性演进。现在主流内核都有BTF支持BCC的CO-RE能力越来越完善。如果条件允许尽量升级到较新的内核这样很多老问题会自动消失比如重启后内核头文件不匹配导致工具失效这类事。第四把BCC工具纳入到监控告警体系里。BCC不只是给人看控制台用的你可以把它的输出推到Prometheus、Grafana或者云平台监控里变成持续运行的定时巡检任务。比如定时跑一次tcpconnect把新连接数上报异常时触发告警。这样才算把eBPF的价值真正沉淀下来。我在实际使用中的体会是BCC的入门曲线比想象中平缓但深水区的东西不少。它最大的魅力不在于某个工具多好用而在于它给了你一种亲眼看到内核运行细节的能力。有了这种能力再去排查线上疑难杂症你就再也不用手足无措地重启大法了。后面有机会我再写一篇基于BCC做性能压测分析的文章把今天提到的这些概念串起来玩一遍。
返回列表