在过去一年里,ARMS基于eBPF科技创造了Kubernetes监控,提供多语言非侵入式应用程序性能,系统性能,网络性能观察能力,验证了eBPF技术的有效性。eBPF而且技术和生态都发展的很好,前途是光明的,作为这项技术的实践者,这篇文章的目标是回答7核心问题介绍eBPF技术本身,为所有人解锁eBPF的面纱。
eBPF是什么?
eBPF它是一种可以在内核中运行沙盒程序的技术,它提供了一种在内核事件和用户程序事件发生时安全注入代码的机制,这样非内核开发者也可以控制内核。随着内核的发展,eBPF 逐渐从最初的包过滤走向网络、内核、安全、跟踪等,而且其功能特点还在快速发展,早期的 BPF 被称为经典 BPF,简称cBPF,就是这种功能的扩展,制作礼物BPF这叫做延伸BPF,简称eBPF。
eBPF的应用场景是什么?
网络最佳化
eBPF高性能和高度可扩展性,使其成为网络方案中网络数据包处理的首选方案:
- 高性能
JIT编译器提供了近乎内核的本机代码执行效率。
- 高度可扩展
在内核的上下文中,可以快速添加协议解析和路由策略。
故障诊断
eBPF通过kprobe,tracepoints跟踪机制同时具有内核和用户跟踪能力,这种端到端的跟踪能力可以快速诊断故障,同时eBPF以更有效的方式支持揭示profiling的统计,不需要像传统系统那样泄露大量的采样数据,以便持续实时profiling成为可能。
安全保障
eBPF您可以看到所有系统调用,所有网络数据包和socket网络操作,与集成的流程上下文跟踪相结合,网络操作级过滤,系统调用过滤,可以更好地提供安全保障。
性能监控
与传统的系统监控组件相比,如sar,只能提供静态counters和gauges,eBPF支持可编程的动态收集和边缘计算,以聚合定制的指标和事件,大大提高性能监控的效率和想象力。
eBPF为什么会出现?
eBPF本质上是为了解决内核迭代速度慢和系统需求变化快之间的矛盾,在eBPF一个常见的域示例是eBPF相对于Linux Kernel类似于Javascript相对于HTML,突出的是可编程性。一般来说,对可编程性的支持通常会带来一些新的问题,比如内核模块,其实就是为了解决这个问题而设计的,但是他没有提供一个好的边界,内核模块会影响内核本身的稳定性,不同的内核版本需要适配。eBPF采取以下策略,使其成为一种安全高效的内核可编程技术:
- 安全
eBPF 程序在执行之前必须经过验证者的验证,并且不能包含无法到达的指令;eBPF 程序不能随意调用核函数,只能在 API 中定义的辅助功能;eBPF 堆栈空间最多只有 512 字节,想要更多储物空间,您必须使用映射存储。
- 高效
借助于实时编译器(JIT),且因为 eBPF 指令仍然在内核中运行,不需要将数据复制到用户状态,事件处理的效率大大提高。
- 标准
通过BPF Helpers,BTF,PERF MAP提供标准接口和数据模型给开发者使用。
- 强大的
eBPF 不仅寄存器的数量增加了,推出了全新的 BPF 映射存储,还在 4.x 内核中原来的单个包过滤事件逐渐扩展为内核状态函数、用户状态函数、跟踪点、绩效事件(perf_events)以及安全保障等领域。
eBPF怎么用?
5个步骤
1、使用 C 发展一种语言 eBPF 程序;
当插件点触发事件时要调用的eBPF沙盒程序,程序将以内核模式运行。
2、借助 LLVM 把 eBPF 将程序编译成 BPF 字节码;
eBPF 将程序编译成 BPF 字节码,用于后续eBPF验证并在虚拟机中运行。
3、通过 bpf 系统调用,把 BPF 字节被提交给内核;
以用户模式传递bpf系统,将BPF字节加载到内核中。
4、验证并运行内核 BPF 字节码,并将相应的状态保存到 BPF 映射中;
内核验证BPF字节安全性,并确保在相应事件发生时调用正确的eBPF程序,如果有状态要保存,写出相应的BPF映射中,例如,监控数据可以写入BPF映射中。
5、用户程序通过 BPF 映射查询 BPF 字节的运行状态。
通过查询了解用户状态BPF映射的内容,获取字节码操作的状态,例如获取捕获的监控数据。
一个完整的 eBPF 程序,它通常包含两部分:用户状态和内核状态:用户状态程序需要通过 BPF 系统调用与内核交互,而且完整 eBPF 程序装入、事件安装、映射创建和更新等;在内核状态下,eBPF 程序也不能任意调用核函数,但是需要通过 BPF 辅助功能完成所需的任务。尤其是在访问内存地址时,必须依靠 bpf_probe_read 串行函数读取存储器数据,以确保安全有效地访问内存。在 eBPF 当一个程序需要一大块存储空间时,我们还需要基于应用场景,介绍一种特定类型的 BPF 映射,并向用户空间程序提供运行状态数据。
eBPF程序分类和使用场景
bpftool feature probe | grep program_type
以上命令可以查看系统所支持的eBPF程序类型,一般有以下几种类型:
eBPF program_type socket_filter is availableeBPF program_type kprobe is availableeBPF program_type sched_cls is availableeBPF program_type sched_act is availableeBPF program_type tracepoint is availableeBPF program_type xdp is availableeBPF program_type perf_event is availableeBPF program_type cgroup_skb is availableeBPF program_type cgroup_sock is availableeBPF program_type lwt_in is availableeBPF program_type lwt_out is availableeBPF program_type lwt_xmit is availableeBPF program_type sock_ops is availableeBPF program_type sk_skb is availableeBPF program_type cgroup_device is availableeBPF program_type sk_msg is availableeBPF program_type raw_tracepoint is availableeBPF program_type cgroup_sock_addr is availableeBPF program_type lwt_seg6local is availableeBPF program_type lirc_mode2 is NOT availableeBPF program_type sk_reuseport is availableeBPF program_type flow_dissector is availableeBPF program_type cgroup_sysctl is availableeBPF program_type raw_tracepoint_writable is availableeBPF program_type cgroup_sockopt is availableeBPF program_type tracing is availableeBPF program_type struct_ops is availableeBPF program_type ext is availableeBPF program_type lsm is available
特定参考https://elixir.bootlin.com/linux/v5.13/source/include/linux/bpf_types.h
主要分为3大型使用场景:
- 跟踪
tracepoint, kprobe, perf_event等,主要用于从系统中提取跟踪信息,然后进行监控、排错、性能优化等。提供数据支持。
- 网络
xdp, sock_ops, cgroup_sock_addr , sk_msg等,主要用于过滤和处理网络数据包,从而实现网络观测、过滤、各种功能,如流量控制和性能优化,你可以在这里丢包,重定向。
cilium基本都用上了hook点。
- 安全和其他
lsm,用于安全,其他人flow_dissector, lwt_in它们并不常用,没有更多的细节。
eBPF的最佳实践是什么?
找到内核的插入点
从正面可以看出来eBPF程序本身并不难,困难在于找到合适的事件源来触发操作。对于监测和诊断领域,跟踪类eBPF该程序的事件源包括3类:核函数(kprobe)、内核跟踪点(tracepoint)或绩效事件(perf_event)。此时有2问题需要回答:
1、内核中都有哪些核函数、内核跟踪点或绩效事件?
- 使用调试信息获取核函数、内核跟踪点
sudo ls /sys/kernel/debug/tracing/events
- 使用bpftrace获取核函数、内核跟踪点
# 查询所有内核桩和跟踪点sudo bpftrace -l# 使用通配符查询所有系统调用跟踪点sudo bpftrace -l 'tracepoint:syscalls:*'# 使用通配符查询包含以下内容的所有名称"open"跟踪点sudo bpftrace -l '*open*'
- 使用perf list获取绩效事件
sudo perf list tracepoint
2、对于核函数和内核跟踪点,当您需要跟踪它们的传入参数和返回值时,如何查询这些数据结构的定义格式?
- 使用调试信息获取
sudo cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/format
- 使用bpftrace获取
sudo bpftrace -lv tracepoint:syscalls:sys_enter_openat
以上信息具体怎么用,请参考bcc。
应用的桩插入点
1、如何查询用户进程跟踪点?
- 静态编译语言通过-g编译选项保留调试信息,应用程序二进制文件将包含DWARF(Debugging With Attributed Record Format),带有调试信息,是的 readelf、objdump、nm 等工具,可用于跟踪的查询函数、变量的相等符号列表
# 查询符号表readelf -Ws /usr/lib/x86_64-linux-gnu/libc.so.6# 查询USDT信息readelf -n /usr/lib/x86_64-linux-gnu/libc.so.6
- 使用bpftrace
# 查询uprobebpftrace -l 'uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:*'# 查询USDTbpftrace -l 'usdt:/usr/lib/x86_64-linux-gnu/libc.so.6:*'
uprobe 它是基于文件的。当文件中的函数被跟踪时,除非这个过程 PID 并过滤,默认情况下,所有使用该文件的进程都将被检测。
以上是静态编译的语言,类似于他内核的痕迹,应用程序的符号信息可以存储在 ELF 在二进制文件中,或者以单独文件的形式,把它放到调试文件里;而内核的符号信息除了可以存放到内核在二进制文件中之外,还会以 /proc/kallsyms 和 /sys/kernel/debug 和其他表单暴露给用户空间。
对于非静态编译语言,主要有两种类型:
1、解释性语言
使用类似编译型语言应用程序跟踪点查询方法,在解释器级别查询它们 uprobe 和 USDT 跟踪点,如何将解释器级别的行为与应用程序行为关联起来,需要相关语言的专家来分析。
2、即时编译语言
这类语言的应用源代码会先编译成字节码,然后是实时编译器(JIT)为机器代码执行而编译,会有很多优化,追踪非常困难,类似于解释性编程语言,uprobe 和 USDT Trace只能在实时编译器上使用,从即时编译器跟踪点参数里面获取最终应用程序的函数信息。找出即时编译器跟踪点同应用程序运行之间的关系需要相关语言的专家来分析。
可以参考一下BCC应用程序跟踪基于,跟踪用户的流程,本质上,它是通过断点执行的 uprobe 处理程序。尽管内核社区一直对 BPF 做了很多性能调整,跟踪用户状态函数(尤其是锁争用、高频率功能,如内存分配)还是有可能带来很大的性能开销。因此,我们正在使用 uprobe 时,尽量避免跟踪高频函数。
以上信息具体怎么用,请参考:https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#events--arguments
相关问题和插桩点
一个理想的状态是所有问题都清楚了,那些插桩点要观察,然而,这需要技术人员对端到端的软件栈细节有透彻的理解,更合理的方法是二八法则,软件堆栈数据流的核心80%抓住血管,担保问题会在这种背景下被发现。这时候用内核栈和用户栈查看具体的调用栈就可以发现核心问题,例如,发现网络正在丢失数据包,但是我不知道我为什么会失去它,至此,我们知道网络丢包会被调用kfree_skb核函数,那么我们是的:
sudo bpftrace -e 'kprobe:kfree_skb /comm=="<your comm>"/ {printf("kstack: %s\n", kstack);}'
找到这个函数的调用栈:
kstack: kfree_skb+1 udpv6_destroy_sock+66 sk_common_release+34 udp_lib_close+9 inet_release+75 inet6_release+49 __sock_release+66 sock_close+21 __fput+159 ____fput+14 task_work_run+103 exit_to_user_mode_loop+411 exit_to_user_mode_prepare+187 syscall_exit_to_user_mode+23 do_syscall_64+110 entry_SYSCALL_64_after_hwframe+68
然后就可以回到上面的函数了,看他们在什么条件下打哪条线,你可以找到问题所在。这种方法不仅可以定位问题,也可以用来加深对内核调用的理解,比如:
bpftrace -e 'tracepoint:net:* { printf("%s(%d): %s %s\n", comm, pid, probe, kstack()); }'
可以查看所有网络相关跟踪点及其调用栈。
eBPF的实现原理是什么?
5个模块
eBPF内核主要由以下部分组成5模块协作:
1、BPF Verifier(验证器)
确保 eBPF 程序安全性。验证器创建要作为有向非循环图执行的指令(DAG),确保程序不包含不可到达的指令;然后模拟指令的执行过程,确保不执行无效指令,我从这里的一些学生那里了解到,这里的验证者是没有保证的100%的安全,所以对所有人来说BPF程序,它们都需要严格的监测和评估。
2、BPF JIT
将 eBPF 字节编译成本地机器指令,以便在内核中更有效地执行。
3、多个 64 位寄存器、一个程序计数器和一个 512 由一堆字节组成的存储模块
用于控制eBPF程序的运行,保存堆栈数据,参与和参加。
4、BPF Helpers(辅助功能)
为提供一系列应用程序 eBPF 与程序内核的其他模块交互的函数。这些功能都不是 eBPF 所有程序都可以调用,可用的特定功能集包括 BPF 程序类型确定。注意,eBPF里面所有的参考资料,参数的修改必须符合BPF规范,除了局部变量的变化,应该使用其他更改BPF Helpers完成,如果BPF Helpers不支持,它不能被修改。
bpftool feature probe
你可以看到不同类型的eBPF有哪些程序可以运行BPF Helpers。
5、BPF Map & context
用于提供大块存储,用户空间程序可以访问这些存储,进一步控制 eBPF 程序的运行状态。
bpftool feature probe | grep map_type
通过上面的命令,可以看到系统支持哪些类型map。
3个动作
先说重要的系统调用bpf:
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
这里cmd是关键,attr是cmd的参数,size是参数大小,所以关键是看cmd有哪些:
// 5.11内核enum bpf_cmd {BPF_MAP_CREATE, BPF_MAP_LOOKUP_ELEM, BPF_MAP_UPDATE_ELEM, BPF_MAP_DELETE_ELEM, BPF_MAP_GET_NEXT_KEY, BPF_PROG_LOAD,BPF_OBJ_PIN,BPF_OBJ_GET, BPF_PROG_ATTACH, BPF_PROG_DETACH, BPF_PROG_TEST_RUN,BPF_PROG_GET_NEXT_ID, BPF_MAP_GET_NEXT_ID, BPF_PROG_GET_FD_BY_ID, BPF_MAP_GET_FD_BY_ID,BPF_OBJ_GET_INFO_BY_FD, BPF_PROG_QUERY, BPF_RAW_TRACEPOINT_OPEN, BPF_BTF_LOAD, BPF_BTF_GET_FD_BY_ID, BPF_TASK_FD_QUERY, BPF_MAP_LOOKUP_AND_DELETE_ELEM, BPF_MAP_FREEZE, BPF_BTF_GET_NEXT_ID, BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH, BPF_LINK_CREATE,BPF_LINK_UPDATE, BPF_LINK_GET_FD_BY_ID,BPF_LINK_GET_NEXT_ID, BPF_ENABLE_STATS, BPF_ITER_CREATE,BPF_LINK_DETACH,BPF_PROG_BIND_MAP,};
核心是PROG,MAP相关的cmd,就是程序装入和映射处理。
1、程序装入
调用BPF_PROG_LOAD cmd,会将BPF程序装入到内核,但eBPF 程序不像普通的线程,它从开始就一直在那里跑,在事件被触发之前,它不会被执行。这些事件包括系统调用、内核跟踪点、核函数和用户状态函数的调用退出、网络事件,等等,所以你需要第一个2个动作。
2、绑定事件
b.attach_kprobe(event="xxx", fn_name="yyy")
以上是将一个特定的事件绑定到一个特定的BPF函数,实际实现原理如下:
(1)借助 bpf 系统调用,加载 BPF 节目结束后,记住返回的文件描述符;
(2)通过attach知道操作对应函数类型的事件号;
(3)根据attach返回值调用 perf_event_open 创建性能监控事件;
(4)通过 ioctl 的 PERF_EVENT_IOC_SET_BPF 命令,将 BPF 该程序绑定到性能监控事件。
3、映射操作
通过MAP相关的cmd,控制MAP增删,那么用户状态就是基于此MAP与内核状态交互。
eBPF发展现状?
内核支持
建议>=4.14
生态
eBPF生态自下而上的情况如下:
1、基础设施
支持eBPF基本能力的发展。
- Linux Kernal
- LLVM
2、开发工具集
主要用于装载,编译,调试eBPF程序,不同的语言有不同的开发工具集:
- Go
- https://github.com/cilium/ebpf
- https://github.com/aquasecurity/libbpfgo
- C/C++
- https://github.com/libbpf/libbpf
3、eBPF应用
- bcc(https://github.com/iovisor/bcc)
提供一套开发工具和脚本。
- bpftrace(https://github.com/iovisor/bpftrace)
基于bcc,提供一种脚本语言。
- cilium(https://github.com/cilium/cilium)
网络最佳化和安全
- Falco(https://github.com/falcosecurity/falco)
网络安全性
- Katran(https://github.com/facebookincubator/katran)
高性能4层负载平衡
- Hubble(https://github.com/cilium/hubble)
可观测
- Kindling(https://github.com/CloudDectective-Harmonycloud/kindling)
可观测
- Pixie(https://github.com/pixie-io/pixie)
可观测
- kubectl trace(https://github.com/iovisor/kubectl-trace)
调度bpftrace脚本
- L3AF(https://github.com/l3af-project/l3afd)
分布式环境中的启动和管理eBPF程序平台
- ply(https://github.com/iovisor/ply)
动态linux trace
- Tracee(https://github.com/aquasecurity/tracee)
Linux运行时安全监控
4、追踪生态网站
- https://ebpf.io/projects
- https://github.com/zoidbergwill/awesome-ebpf
写在最后
用好eBPF前提是对软件栈的了解
通过上面的介绍,我相信大家都是对的eBPF已经有了足够的了解,eBPF提供的只是一个框架和机制,核心还是要用的eBPF人们对软件栈的理解,找到合适的插桩点,并且可能与应用问题相关联。
eBPF杀手锏是全覆盖,无侵入,可编程
1、全覆盖
内核,插桩点全覆盖的应用。
2、无侵入
没有必要修改任何hook的代码。
3、可编程
动态分布eBPF程序,边缘动态执行指令,动态聚集分析。
作者 | 炎寻
原始链接:http://click.aliyun.com/m/1000346074/
本文为阿里云原创内容,未经允许不得转载。
评论列表(4条)