欢迎大家来到IT世界,在知识的湖畔探索吧!
1. 什么是 eBPF?
eBPF 是什么呢? 从它的全称“扩展的伯克利数据包过滤器 (Extended Berkeley Packet Filter)” 来看,它是一种数据包过滤技术,是从 BPF (Berkeley Packet Filter) 技术扩展而来的。
eBPF允许在操作系统中运行沙盒程序的方式,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加额外的功能。然后在 JIT 编译器和验证引擎的帮助下,操作系统确保它像本地编译的程序一样具备安全性和执行效率。
如今,eBPF 被广泛用于驱动各种用例:在现代数据中心和云原生环境中提供高性能网络和负载均衡,以低开销提取细粒度的安全可观测性数据,帮助应用程序开发人员跟踪应用程序,为性能故障排查、预防性的安全策略执行(包括应用层和容器运行时)提供洞察,等等。可能性是无限的,eBPF 开启的创新才刚刚开始。
BPF 最初代表伯克利包过滤器 (Berkeley Packet Filter),但是现在 eBPF(extended BPF) 可以做的不仅仅是包过滤,这个缩写不再有意义了。eBPF 现在被认为是一个独立的术语,不代表任何东西。在 Linux 源代码中,术语 BPF 持续存在,在工具和文档中,术语 BPF 和 eBPF 通常可以互换使用。最初的 BPF 有时被称为 cBPF(classic BPF),用以区别于 eBPF。
蜜蜂是 eBPF 的官方标志,最初是由 Vadim Shchekoldin 设计的。在第一届 eBPF 峰会上进行了投票,并将蜜蜂命名为 eBee。
BPF 提供了一种在内核事件和用户程序事件发生时安全注入代码的机制,这就让非内核开发人员也可以对内核进行控制。随着内核的发展,BPF 逐步从最初的数据包过滤扩展到了网络、内核、安全、跟踪等,而且它的功能特性还在快速发展中,这种扩展后的 BPF 被简称为 eBPF(相应的,早期的 BPF 被称为经典 BPF,简称 cBPF)。实际上,现代内核所运行的都是 eBPF,如果没有特殊说明,内核和开源社区中提到的 BPF 等同于 eBPF。
tcpdump 和 BCC 之所以这么高效强大,都是得益于 BPF/eBPF 技术。
- Katran: Facebook 开源的高性能网络负载均衡器。
- cilium.io:Isovalent 开源的容器网络方案 。
- bcc :BCC 是一个 BPF 编译器集合,包含了用于构建 BPF 程序的编程框架和库,并提供了大量可以直接使用的工具。使用 BCC 的好处是,它把上述的 eBPF 执行过程通过内置框架抽象了起来,并提供了 Python、C++ 等编程语言接口。这样,你就可以直接通过 Python 语言去跟 eBPF 的各种事件和数据进行交互。
- bpftrace:动态工具。
- libbpf 帮你避免了直接调用内核函数,提供了跨内核版本的兼容性(即一次编译到处执行,简称 CO-RE)
ebpf为什么性能这么好呢?这主要得益于 BPF 的两大设计:
- 第一,内核态引入一个新的虚拟机,所有指令都在内核虚拟机中运行。
- 第二,用户态使用 BPF 字节码来定义过滤表达式,然后传递给内核,由内核虚拟机解释执行。
eBPF 的诞生是 BPF 技术的一个转折点,使得 BPF 不再仅限于网络栈,而是成为内核的一个顶级子系统。
2. ARM vs eBPF
APM全称:Application Performance Management。目前市场上的APM方案大多是参考Google的Dapper(大规模分布式系统的跟踪系统)实现的,如Cat、Skywalking。
APM技术,历经长期演进与积淀,逐步趋向采纳OpenTelemetry这一标准化的开源框架,实现了Tracing领域的深刻变革,极大促进了追踪技术的统一化、标准化进程,标志着监控技术迈向了一个全新的专业化高度。
- APM主要是在业务程序代码中进行拦截:意味着可以拿到业务进程中更多的信息
- eBPF主要是在操作系统代码中进行拦截:意味着可以拿到操作系统中更多的信息
在可观测领域中,APM和eBPF是两种不同的采集方式,最终目的都是为了实现排查故障。所以,我们必须对故障有一个清晰的认知。大多数的应用故障是从代码中产生的。不同类型的代码对应的数据采集方式也有所不同。
目前大概有5种采集方案:
① APM方案
- 对业务程序:采用字节码插桩或者手动埋点的方式
② eBPF方案
- 对业务程序:采用eBPF解析流量数据包的方式
- 对操作系统:采用eBPF对关键内核方法进行检测
③ APM+指标采集+流量采集
- 对业务程序:采用字节码插桩或者手动埋点的方式
- 对操作系统:采集操作系统/proc文件的指标数据,对于一些socket指标可以通过连接跟踪来采集
④ APM+eBPF
- 对业务程序:采用字节码插桩或者手动埋点的方式
- 对操作系统:采用eBPF对关键内核方法进行检测
⑤ APM+指标采集+流量采集+少量eBPF
- 对业务程序:采用字节码插桩或者手动埋点的方式
- 对操作系统:采集操作系统/proc文件的指标数据,对于一些socket指标可以通过连接跟踪来采集,对网络Span跟Trace的关联可以采用eBPF
3. eBPF 编程组件
3.1. 钩子
eBPF 程序是事件驱动的。预定义的钩子包括系统调用、函数入口/退出、内核跟踪点、网络事件等。
如果预定义的钩子不能满足特定需求,则可以创建内核探针(kprobe)或用户探针(uprobe),以便在内核或用户应用程序的几乎任何位置附加 eBPF 程序。
3.2. 如何编写 eBPF 程序
在很多情况下,eBPF 不是直接使用,而是通过像 Cilium、bcc 或 bpftrace 这样的项目间接使用,这些项目提供了 eBPF 之上的抽象,不需要直接编写程序,而是提供了指定基于意图的来定义实现的能力,然后用 eBPF 实现。
如果不存在更高层次的抽象,则需要直接编写程序。Linux 内核期望 eBPF 程序以字节码的形式加载。虽然直接编写字节码当然是可能的,但更常见的开发实践是利用像 LLVM 这样的编译器套件将伪 c 代码编译成 eBPF 字节码。
网络相关
- Cilium:一个依赖于BPF和XDP技术的项目,提供了“基于eBPF程序动态生成的内核内高速网络和容器安全策略执行”。
- Open vSwitch(OvS)及其相关项目Open Virtual Network(OVN,一种开源网络虚拟化解决方案)正在考虑在各个层面使用eBPF。
- Katran – Meta开源的基于XDP的L4负载均衡。
- Project Calico: 知名的K8s网络插件,也是用了eBPF提供一个低延迟,高吞吐的数据面
可观测性相关
- beyla:一个使用eBPF的应用指标采集工具,基于eBPF实现了内核层面http/https和应用层面go-rpc等指标的自动采集。
- Hubble:和Cilium配套的监控系统,使用eBPF的K8s网络、服务、安全性可观测的平台。
- pixie – 利用eBPF实现对Kubernetes的可观测性。其功能包括协议跟踪、用户应用性能profile,以及支持分布式bpftrace部署。
安全相关
- Falco:使用eBPF检测主机和容器中的恶意行为。
3.3. 加载器和校验架构
eBPF加载器是一种工具/机制,用于将用户空间编写的eBPF字节码加载到内核空间中。加载器负责将编译后的eBPF程序传输到内核,并准备在特定的内核钩子点上运行。eBPF加载器不仅仅是一个传输工具,它还负责初始化和配置eBPF程序运行所需的各种资源,如eBPF Maps、Helper函数等。此外,它还确保eBPF程序能够在内核的正确位置执行,并与内核其他部分进行交互。
eBPF验证器是内核中的一个关键组件,负责在eBPF程序加载到内核之前进行安全性和正确性检查。验证器的主要目的是确保eBPF程序不会对内核的稳定性和安全性造成威胁。当一个eBPF程序被加载时,验证器会对其进行一系列静态分析和检查,以确保程序是安全且正确的。
3.4. 验证
验证步骤用来确保 eBPF 程序可以安全运行。它可以验证程序是否满足几个条件,例如:
- 加载 eBPF 程序的进程必须有所需的能力(特权)。除非启用非特权 eBPF,否则只有特权进程可以加载 eBPF 程序。
- eBPF 程序不会崩溃或者对系统造成损害。
- eBPF 程序一定会运行至结束(即程序不会处于循环状态中,否则会阻塞进一步的处理)。
4. GitHub 模板:轻松构建 eBPF 项目和开发环境
面对创建一个 eBPF 项目,您是否对如何开始搭建环境以及选择编程语言感到困惑?别担心,我们为您准备了一系列 GitHub 模板,以便您快速启动一个全新的eBPF项目。只需在GitHub上点击 Use this template 按钮,即可开始使用。
- https://github.com/eunomia-bpf/libbpf-starter-template:基于C语言和 libbpf 框架的eBPF项目模板
- https://github.com/eunomia-bpf/cilium-ebpf-starter-template:基于C语言和cilium/ebpf框架的eBPF项目模板
- https://github.com/eunomia-bpf/libbpf-rs-starter-template:基于Rust语言和libbpf-rs框架的eBPF项目模板
- https://github.com/eunomia-bpf/eunomia-template:基于C语言和eunomia-bpf框架的eBPF项目模板
这些启动模板包含以下功能:
- 一个 Makefile,让您可以一键构建项目
- 一个 Dockerfile,用于为您的 eBPF 项目自动创建一个容器化环境并发布到 Github Packages
- GitHub Actions,用于自动化构建、测试和发布流程
- eBPF 开发所需的所有依赖项
5. 总结
5.1. eBPF在系统监控领域的创新
eBPF 提供了四种不同的操作机制,以满足系统监控领域的需求和场景。这些操作方式包括:
1、内核跟踪点(Kernel Tracepoints):内核跟踪点是由内核开发人员预定义的事件,可以使用 TRACE_EVENT 宏在内核代码中设置。这些跟踪点允许 eBPF 程序挂接到特定的内核事件,并捕获相关数据进行分析和监控。
2、USDT(User Statically Defined Tracing):USDT 是一种机制,允许开发人员在应用程序代码中设置预定义的跟踪点。通过在代码中插入特定的标记,eBPF 程序可以挂接到这些跟踪点,并捕获与应用程序相关的数据,以实现更细粒度的观测和分析。
3、Kprobes(Kernel Probes):Kprobes 是一种内核探针机制,允许 eBPF 程序在运行时动态挂接到内核代码的任何部分。通过在目标内核函数的入口或出口处插入探针,eBPF 程序可以捕获函数调用和返回的参数、返回值等信息,从而实现对内核行为的监控和分析。
4、Uprobes(User Probes):Uprobes 是一种用户探针机制,允许 eBPF 程序在运行时动态挂接到用户空间应用程序的任何部分。通过在目标用户空间函数的入口或出口处插入探针,eBPF 程序可以捕获函数调用和返回的参数、返回值等信息,以实现对应用程序的可观察性和调试能力。
上述这些机制提供了丰富而灵活的方式,让 eBPF 能够与内核和应用程序交互,捕获关键事件和数据,并实现深入的可观察性和调试功能。通过结合这些机制,开发人员可以更好地理解系统的行为、排查问题,并进行性能优化和故障排除。
5.2. eBPF在安全领域的创新
- eBPF可以在内核态实时监控网络流量和数据包,并且根据事先定义的规则进行拦截和处理。这使得系统可以立即响应网络事件,包括攻击、异常流量等。
- eBPF允许在运行时动态加载和卸载安全规则,而无需重新启动系统或修改内核代码。这使得网络安全策略可以根据实际需求动态调整和更新,提高了灵活性和响应速度。
- eBPF提供了一种灵活的编程模型,可以使用高级语言编写网络安全规则,例如检测特定的网络流量模式、拦截恶意请求等。这使得用户可以根据具体的需求定制安全策略,更好地适应不同的网络环境和应用场景。
- eBPF可以与传统的网络安全解决方案(如防火墙、入侵检测系统)结合使用,提供更加全面和多层次的网络安全保护。通过与传统解决方案的结合,可以弥补它们的不足,并提供更强大的网络安全防御能力。
在主机安全方面,eBPF与LSM(Linux Security Modules)机制相结合,实现动态的主机资源监控和拦截。
LSM是Linux内核的访问控制架构,用于实施各种访问控制策略。它通过在系统调用、文件系统访问等操作中插入钩子的方式,来保护进程、文件等资源。当eBPF和LSM结合使用时,可以发挥以下优势:
- 丰富的安全策略:eBPF可以与LSM结合,以实现更复杂、更细粒度的安全策略。LSM提供了钩子来拦截系统调用和文件访问等操作,而eBPF可以通过这些钩子来执行自定义的安全策略,从而实现更精细的访问控制。
- 动态的安全策略更新:eBPF允许用户在运行时动态加载和卸载安全策略,而不需要重新编译或重新加载内核模块。这种灵活性使得用户能够及时更新安全策略以应对新的安全威胁或者变化的环境。
- 全面的安全解决方案:eBPF和LSM结合可以提供一个全面的安全解决方案,涵盖了网络安全、系统调试、审计和访问控制等方面。这种综合性的安全解决方案可以帮助用户更好地保护其系统和数据免受各种安全威胁的影响。
BCC、libbpf 以及内核源码,都主要使用 C 语言开发 eBPF 程序,而实际的应用程序可能会以多种多样的编程语言进行开发。所以,开源社区也开发和维护了很多不同语言的接口,方便这些高级语言跟 eBPF 系统进行交互。比如,我们课程多次使用的 BCC 就提供了 Python、C++ 等多种语言的接口,而使用 BCC 的 Python 接口去加载 eBPF 程序,要比 libbpf 和内核源码的方法简单得多。
在这些开发库的基础上,得益于 eBPF 在动态跟踪、网络、安全以及云原生等领域的广泛应用,开源社区中也诞生了各种编程语言的开发库,特别是 Go 和 Rust 这两种语言,其开发库尤为丰富。
在使用这些 Go 语言开发库时需要注意,Go 开发库只适用于用户态程序中,可以完成 eBPF 程序编译、加载、事件挂载,以及 BPF 映射交互等用户态的功能,而内核态的 eBPF 程序还是需要使用 C 语言来开发的。
而对于 Rust 来说,由于其出色的安全和性能,也诞生了很多不同的开发库。下面的表格列出了常见的 Rust 语言开发库,以及它们的使用场景:
从 Go 和 Rust 语言的开发库中你可以发现,纯编程语言实现的开发库(即不依赖于 libbpf 和 BCC 的库)由于很难及时通过内核进行同步,通常都有一定的功能限制;而 libbpf 和 libbcc 的开发语言绑定通常功能更为完善,但开发和运行环境就需要安装 libbpf 和 libbcc(部分开发库支持静态链接,在运行环境不再需要 libbpf 或 libbcc)。因为底层的实现是类似的,所以掌握了 libbpf 和 BCC 的使用方法,在学习其他语言的绑定时会更容易理解。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/86849.html