购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第2章
初识eBPF

本章主要介绍eBPF的历史、关键特性、应用场景及框架,带大家了解eBPF技术。

2.1 eBPF历史

过去几年,eBPF不仅是云原生社区,也是内核社区最为火热的技术之一。从频繁占据各种技术会议的日程表,到近两年发展成立了eBPF自己的峰会,这样的流行势头并不是毫无理由的,它并不只是一个存在于学术论文上的前沿领域,而是已经被广泛应用在企业生产环境中的核心竞争力。在深入理解eBPF概念之前,让我们先把eBPF的完整形式搞清楚。

eBPF由extended(扩展)、Berkeley(伯克利)、Packet(数据包)、Filter(过滤器)4个单词的首字母组成,从字面意思出发显然并不能让我们真正理解eBPF是什么,除了知道它可以帮助过滤网络数据包及源自伯克利实验室的事实外,并不能说明为什么eBPF是当下最热门的技术领域之一,那就先让我们从eBPF技术相关的发展历程说起。

eBPF的起源可以追溯到1992年,当时使用CMU/Stanford数据包过滤器(CSPF)进行网络流量的数据包过滤是比较通用的方案,CSPF作为第一个数据包捕获和过滤的公开方案,在网络信息时代的早期具有开创性的意义,但方案也引入了一些问题,比如针对过滤操作需要在系统内存堆栈中模拟,同时在过滤算法的数据结构上也存在优化空间。为此,来自劳伦斯伯克利实验室的Steve McCanne和Van Jacobson首次提出了伯克利数据包过滤器(Berkeley Packet Filter,BPF)的概念,在CSPF的基础上,Steve McCanne和Van Jacobson通过引入基于寄存器的过滤机制和优化后无环控制流图过滤算法,明显提升了数据包的捕获速度,并且提供了一套可扩展和可移植的通用网络监控接口,为BPF的后续发展奠定了基础。

BPF首次被引入Linux内核版本是在1997年,在内核2.1.75版本中tcpdump工具集成了BPF能力并用于捕获数据包。2012年,seccomp-bpf被引入内核3.5版本中,seccomp作为Linux中重要的安全特性,支持通过策略定义的方式控制来自用户空间的应用程序是否允许使用指定范围的系统调用。值得注意的是,这是BPF在内核中的能力范围首次超过之前单纯的包过滤范畴,为后续逐步成为内核中重要的通用性执行环境框架迈出了第一步。

在2014年的内核3.18版本中,BPF特性迎来了重大变化。我们知道随着现代处理器架构逐步转为64位寄存器,BPF基于传统的指令集架构和虚拟机设计已经无法满足多处理器系统架构的需求。为此,Alexei Starovoitov引入了扩展的BPF(也就是eBPF)设计,包含如下几个重要演进:

❑传统的BPF指令集被彻底修改并转向适配64位寄存器,eBPF虚拟机也更接近于现代处理器架构,寄存器数量从2个增加到11个,这使得程序参数能够在eBPF虚拟机寄存器中传递给函数。同时,eBPF字节码可以映射到本地机器指令并完成即时编译,大大提升了传统BPF程序运行性能。

❑通过引入eBPF maps,使得用户态应用程序和eBPF程序之间可以共享数据信息。

❑增加了eBPF验证器,确保eBPF程序在内核中运行的安全性。

❑增加了eBPF辅助函数,用于eBPF程序和内核其他模块的交互。

❑引入bpf()系统调用,使得用户空间程序可以和内核中运行的eBPF程序直接交互,eBPF虚拟机从此不再只能在内核中使用,而是可以直接暴露在用户空间中。

在2018年,eBPF成为Linux内核中的一个独立子系统。同年,BTF(BPF Type Format,BPF类型格式)也被引入内核,有效提升了eBPF程序的可移植性及在调试上的便利性。2020年,Linux安全模块(Linux Security Module,LSM)允许集成加载eBPF,这样的扩展让eBPF的强大能力可以更好地被应用到各种安全平台中,在网络和可观测性领域外,安全也成为eBPF一个重要的应用场景。

除此之外,越来越多的内核开发者和eBPF爱好者在社区贡献了大量的用户态开源工具,同时关于eBPF程序的指令长度限制也在5.2版本内核中得以解决,而如尾部调用和函数调用等eBPF程序关键特性的引入也进一步降低了eBPF的开发和应用门槛。

如今,企业生产环境中广泛使用的Linux内核版本基本都已支持BPF“扩展”特性,现在eBPF和BPF这两个技术术语基本上可以互换使用了。此外,BCC GitHub官网上也列举了内核各版本中有关eBPF特性的很多其他重要变更,有兴趣的读者可以在代码中进一步回溯eBPF的发展历程。随着eBPF Summit等国内外会议的火热召开,eBPF在技术圈已经拥有了完备的生态和越来越重要的影响力。关于本节提及的一些eBPF关键特性,我们也会在后续章节中重点讲述。

2.2 eBPF的关键特性和应用场景

eBPF已经被看成一个独立的技术领域名称,它是在内核中的执行环境和编程框架。系统内核是计算机操作系统的核心,也是提供系统稳定性和安全性的关键,因此在架构设计上它一直被视为某种程度上的禁区。而eBPF通过可编程的方式允许用户在操作系统内核中加载和执行自定义程序,同时通过提供内核中轻量级的沙箱环境,为系统内核提供必要的隔离。

2.2.1 Linux内核

在介绍eBPF之前,本小节我们先简单了解一下Linux内核,因为如果不提及内核,我们可能很难完整解释eBPF。Linux内核是位于应用程序和硬件之间的软件层,它为与底层硬件的交互提供了坚实有力的基础。Linux将其内存分为两个不同的区域——内核空间和用户空间,它们之间存在一个明确的分界线。

通常,应用程序运行在被称为用户空间的非特权层,这里不能直接访问底层硬件,而是必须按照操作系统定制的规则行事。比如你想在主机上发送一些网络数据包,并不能直接访问网卡,而需要通过框架或高级编程语言发出系统调用完成与内核的通信。除了发送和接收网络数据,这样的硬件交互还包括文件系统读写和简单的内存访问等。

内核空间是操作系统的核心所在。它可以完全不受限制地访问所有硬件,包括系统内存、文件系统、CPU资源等。由于内核本身依赖特权访问,内核空间受到保护,只允许运行可信代码,包括内核自身代码和各种设备驱动程序。除此之外,内核还负责协调并发进程,让多个上层应用程序可以同时运行。

当我们的应用程序向内核发起请求时,往往需要复制内核空间的一大块上下文数据到用户空间中。之所以这样做,是因为操作系统对内核区域明确的边界划分,用户空间程序不可能将指针直接指向内核空间内存,而这样跨越用户态边界的操作对程序性能会产生很大影响。

除了性能影响之外,在用户空间内的应用程序只能通过系统调用抽象后的程序化接口来完成与内核空间的交互,这样的约束势必会导致用户态程序无法获得完整的内核空间上下文。当然,这也符合操作系统设计中的安全和稳定性原则。

为了解决这样的问题,在eBPF外也有一些传统方案,比如Intel DPDK(Data Plane Development Kit),通过在用户空间中实现网卡驱动程序去摆脱内核依赖,此做法确实可以提升数据包处理速率,但需要在用户空间中付出更大的代价去重复编写内核驱动代码,目前的应用场景也更多局限在网络性能优化上。

另一种方案是通过编写内核模块将需要的功能直接添加到内核空间中,好处是摆脱了内存边界的束缚,在内核空间中可以自由访问底层硬件资源。但内核模块也有明显的缺点:首先,由于没有边界和系统调用接口的约束,在内核空间系统无法控制内核模块中添加的自定义逻辑的安全性和健壮性,可能引发系统全局的崩溃;其次,每一次Linux内核版本的迭代都会带来大范围的逻辑变更,这种情况下老版本内核模块需要不断重构来保证与新版本的兼容性,所以内核模块的方案显然也不是生产推荐的可持续方案。

众所周知,Linux内核非常复杂,它由约3000万行的庞大代码构成。在用户态应用程序中,一个简单的文件打印操作背后可能包含上百个系统调用。如果我们想在内核中引入一个新功能,除非你是一个熟悉内核代码库的开发者,否则这肯定是一个艰巨的挑战。虽然Linux内核每2~3个月就会有一些小版本迭代,但终端用户中的大多数人并不会直接使用Linux内核,而是使用如Ubuntu、Debian这样的Linux发行版,所有这些正式发行版使用的都是旧的内核版本,而最新的内核变更通常需要3~5年甚至更久的时间才能最终触达用户。

这样的迭代效率显然困扰着应用开发者,尤其是应用程序在很大程度上都依赖内核,虽然系统调用接口在大多数情况下是足够的,但是开发者会需要更多的灵活性来适配新硬件,实现新的文件系统或者希望自定义系统调用。因此,通过观察和拦截应用程序与内核交互是很多开发者认为最直接的解决方案,然而即使抛开前面提及的内核安全问题,面对Linux内核过高的开发门槛和漫长的发布周期,普通开发者的自定义需求必然很难被社区接收并最终发布进入到内核版本中。eBPF的出现恰恰为身在这样困局中的开发者提供了一种可行的解决方案,在下一小节中让我们一起来了解eBPF是什么及它能帮助我们解决哪些问题。

2.2.2 eBPF的关键特性

在上一小节中,我们对Linux内核有了初步了解,鉴于其在整个系统分工中的监控和特权能力,操作系统内核一直是实现系统可观测性、安全性和网络功能的首选位置。同时,由于操作系统内核的核心地位和对其安全、稳定性的严格要求,内核的版本迭代是相对缓慢的,同时关于操作系统层面的创新性变革也是少之又少。

下面介绍的3个重要特性可以说是eBPF改变内核创新困境的关键法宝。

1.动态加载

应用开发者可以将eBPF程序动态地加载到内核中,也可以随时移除它。这样的eBPF程序就像在操作系统内运行沙箱程序一样,可以在应用运行时向操作系统中添加自定义功能。一旦程序被加载绑定到一个内核事件上,无论什么原因导致事件发生,与之绑定的eBPF程序就会被该事件触发运行。例如我们将一个eBPF程序绑定给打开文件的系统调用,那么只要任何进程试图打开文件,不管进程是否已经在运行状态,eBPF程序都会被加载并随之运行。与之对比,在传统方案下,开发者需要等待内核升级的漫长流程,同时不得不重启机器以加载新版本的内核特性,显然eBPF在内核中动态加载的特性是一个巨大的优势。而对于一个普通的应用开发者而言,可以基于eBPF动态加载的优势实时观测自己希望获取的网络、安全等内核事件,在掌握系统进程可见性的同时,进而基于自身需求创造出更多的自定义内核功能。

2.安全性

在上一节中我们提到了内核模块的修改内核行为方式,这样的传统方案之所以不被推荐,是因为增加一个内核模块可能会给系统带来巨大风险,导致全局性崩溃。而eBPF允许开发者在内核中运行任意代码,并可以绑定在关键的内核事件中使用,以观测和控制操作系统,这样的行为看起来更是一个令人担心的方案。那么eBPF如何保证其程序的安全运行呢?

这里就需要介绍eBPF验证器(verifier)机制。在验证器中首先会确认eBPF程序需访问的所有区域,并确认程序在所有输入条件下都会在一定数量的指令内安全终止,避免无限循环的发生。例如对程序中引用的空指针的自动校验、对辅助函数中的参数校验及访问内存字节的校验等。此外,eBPF验证器还会通过dryrun的试运行方式验证eBPF程序完整的生命周期,保存并返回程序中每条指令的执行审计。一个eBPF程序只有通过了验证器所有的安全校验后,才能够被传递给内核执行。

这样的机制从理论上确保了eBPF在内核中执行的安全性,但从安全角度出发,我们必须清楚地意识到没有绝对安全的机制。系统管理员应当基于权限最小化原则,严格收敛eBPF程序加载和执行特权,因为一个有eBPF内核特权的攻击者同样利用特权在系统内核中执行恶意程序。而对于非特权用户,历史上也曾发生过源自eBPF虚拟机的高危漏洞CVE-2017-16995导致一个非特权用户利用漏洞提权并最终获得内核的最高读写权限,这无疑会使整个系统暴露在巨大的安全风险下,同时也会直接动摇企业在生产环境中应用eBPF技术的决心。当然,这也直接说明了eBPF验证器至关重要的作用,可能eBPF验证器中一行潜藏的漏洞代码就会在很长时间影响整个eBPF技术的发展趋势。关于eBPF对系统安全可能带来的威胁和挑战,我们会在后续章节中做进一步介绍。

3.高效性

无须在系统内核和用户空间之间切换使得eBPF在处理成本上比用户态程序更有优势。eBPF支持将加载的程序字节码进行即时编译(JIT),这样程序会以本地指令集的形式在CPU上运行,所以eBPF程序的执行是快速、高效的。此外,eBPF在将观测到的系统事件发送到用户空间之前,可以在内核中进行过滤,以进一步节约传输成本。除了最早支持的网络数据包过滤,当下eBPF程序已经可以广泛收集整个系统中的所有事件信息,同时支持通过程序定制化复杂逻辑的过滤器,只发送用户态感兴趣的关键信息。

通过以上关键优势,eBPF已经被广泛应用于各种云原生生产环境中来提供高性能网络和负载平衡,同时以低成本的系统开销帮助企业运维监控和收集安全可观测数据,通过追踪及时发现企业应用在性能和安全方向上的关键问题。在下一小节我们会进一步介绍eBPF在云原生环境中的应用场景。

2.2.3 eBPF的应用场景

基于动态加载、安全性和高效性等特性,eBPF逐步发展为一个支持无侵入式修改和扩展操作系统内核功能的编程框架。由于内核在整个系统中具有至关重要的位置,对于它的修改在某种程度上一直被视为禁区,然而eBPF为这个特殊的空间带来了可编程性,也给技术爱好者们提供了一个充分展现创造力的空间。

通过eBPF可以在运行时以安全和高效的执行方式在操作系统中添加自定义程序,同时使用内核中在用户态很难触达的底层硬件资源。在云原生迅速普及的今天,不难发现其与eBPF结合的众多应用场景主要集中在网络、可观测性和安全等方向。

1.网络

基于eBPF,开发者可以在系统网络接口和内核网络技术栈的不同位置开发自定义程序,通过应用特殊的逻辑、网络策略或协议来满足不同应用场景下的需求,比如改变数据包的发送链路、修改数据包内容等。可以说,网络功能是eBPF最早大规模应用在生产环境中的应用场景,基于eBPF的网络工具也已经被企业客户广泛使用。

比如在负载均衡领域,Facebook(已更名为Meta)早期推出的高性能四层负载均衡器项目Katran和Cloudflare推出的Unimog边缘负载均衡器都基于eBPF技术和Linux内核中的XDP实现数据包的高效转发,通过XDP可以将eBPF程序绑定到网络接口上,而eBPF程序可以在内核主网络堆栈处理数据包之前对每个到达的数据包执行处理逻辑,比如丢弃数据包或修改数据包并进行转发操作。这样的处理方式避免了数据包在内核态和用户态之间的频繁切换,显著提升了负载均衡场景下的数据包处理效率。

在Istio社区最新的Ambient Mesh架构下,eBPF也被作为关键技术引入,用于简化服务网格中的网络层实现,并进一步提升网络传输性能和灵活性。

另外,不得不提的是CNCF社区中已经毕业的Cilium,它是一个基于eBPF的开源网络平台,同时提供了高效的网络安全策略模型及3~7层的负载均衡策略,能够完全取代Kubernetes原生的kube-proxy组件。同时在eBPF中实现了高效的哈希表以支持超大规模集群网络,被视为CNCF中最为先进的Kubernetes CNI(Container Network Interface,容器网络接口)插件,同时也是被云服务商和企业客户广泛集成和使用的云原生网络解决方案。其子项目Hubble和Tetragon也是Cilium平台中重要的组成部分,提供了可视化的观测能力和运行时安全监控和事件响应能力。关于Tetragon我们将会在本书的后续章节做详细的源码解读。

2.可观测性

可观测性也是eBPF重要的应用场景,尤其在云原生环境中,Kubernetes架构下的集群由复杂的控制平面和数据面节点中多个系统组件构成,企业业务负载的平稳保障离不开底层所有系统组件的正常运行。通过使用eBPF可以了解主机上发生的一切。通过收集内核事件数据并将其传递给用户空间,eBPF实现了一系列可观测性场景下的开源工具,帮助企业更加深入了解发生的事件,同时从系统全局收集并汇总自定义指标,而不仅仅是从单个容器应用出发。针对集群中关键的系统组件,可以通过更加精细的自定义事件指标进行密切监测,同时使用eBPF可以在很小开销的前提下,完成关键监控数据的实时获取和可视化展示。

通过前面的介绍,我们知道eBPF程序可以在内核空间中执行函数或事件时被动态加载运行,而无须侵入式修改内核代码。这样的能力为基于eBPF的可观测性创新式扩展提供了无限可能,我们可以将自定义的eBPF程序挂载到下列内核事件位置上,帮助实现对系统可观测性的自定义扩展,在第3章中将列举一些常见的eBPF程序挂载类型和各自特点。

提升系统可观测能力是eBPF最直接的应用场景,其中Linux IO Visor项目中的BCC(BPF Compiler Collection,BPF编译器集合)是最为人熟知的开源工具集,它在很大程度上弥补了eBPF技术在终端工具和可调试性上的短板。BCC项目在本质上是一个编译器,支持将用户基于C、Python或Lua等语言编写的程序编译成内核模块并用于最终的可观测或网络追踪。此外,BCC项目还提供了超过一百个的成熟可观测工具集,其中很多工具都已被应用在企业大规模生产环境中,同时作为很多可观测解决方案的基础工具。在此基础上,Inspektor Gadget项目将很多源自BCC的工具引入Kubernetes中典型的可观测和安全场景,帮助用户将系统内核中采集观测到的底层数据映射为Kubernetes典型场景下的资源模型,从而提升Kubernetes集群可观测运维能力。

Pixie是Kubernetes可观测领域另一个重要的开源项目。Pixie基于eBPF技术自动采集集群中的服务和资源请求及相关的网络指标数据,在保证自身性能开销的前提下还提供了强大的可视化能力和可插拔的PxL脚本,来实现对采集目标数据源的自定义扩展。通过使用Pixie,可以从全局了解集群服务、资源和应用流量的拓扑结构,同时可以更深入了解Pod维度的应用请求、火焰图、资源状态等精细化的可观测指标视图。

3.安全

基于eBPF,我们几乎可以捕获到主机上任何的底层活动,而这正是安全工具的基础,通过将监控捕获到的正在发生的系统事件与工具已经定义好的安全策略和规则相比较,可以帮助安全运维人员实时发现可疑的安全攻击事件,这就是安全工具的基础工作方式。使用eBPF技术提供云原生安全能力的工具主要集中在运行时安全和网络安全方向上。

在之前的网络部分,我们介绍了eBPF可以在数据包传输链路上对其内容进行高效的校验或转发,这样的特性在对网络攻击的安全防护上同样重要。比如针对DDoS攻击的防护,DDoS攻击是互联网环境中最为常见的攻击方式之一,通过在网络基础设施层的数据包泛洪,致使目标机器无法及时处理正确的网络信息。利用eBPF可以实现在数据包到达和进行堆栈处理之前完成特征校验,如果确认是不符合校验的恶意数据,可以在数据包进入内核网络堆栈前将其丢弃,从而完成对DDoS攻击的高效防御。此外,针对内核漏洞的网络攻击也是层出不穷,由于内核版本迭代缓慢及在生产环境上热升级节点内核的复杂性,攻击者可以基于已经公开披露的内核漏洞尝试对企业生产环境发起攻击。此时与其等待内核补丁的发布并真正实施在生产环境节点中,不如选择eBPF程序,通过针对漏洞攻击路径定制化相应的缓解措施,并支持动态加载到内核指定执行路径上,从而在不改变主机任何代码且无须重启系统的前提下完成对指定内核漏洞的“止血”措施。

网络策略是eBPF在网络安全方向上的另一个重点应用,尤其在云原生环境中,Kubernetes原生的网络策略依赖如Calico、Cilium等具体网络插件的实现,eBPF可以帮助实现更加安全和高效的网络策略模型,同时利用其可观测上的先天优势建立更加直观的可视化网络策略拓扑视图。除此以外,eBPF还可以帮助在网络流量中透明地注入证书或令牌,帮助应用无感知构建流量加密。

在运行时,eBPF可以帮助我们实时监控主机上每个应用程序或运行脚本的行为活动,通过安全工具集成的专家经验拦截恶意脚本的执行、对主机敏感目录的访问或者判断是否有疑似容器逃逸行为的发生。其中,Linux内核中的seccomp是较早基于eBPF实现的安全特性,它可以基于规则配置实现细粒度的系统调用审计或拦截,虽然规则配置上的复杂性和专业性限制了seccomp特性的大规模应用,但是通过对内核系统调用的过滤和拦截,确实可以帮助企业安全运维人员抵御绝大多数CVE漏洞攻击。随着云原生安全的发展,涌现出了如Falco、Tetragon、Tracee等优秀的开源安全项目,一方面面向终端用户提升了安全规则配置的易用性;另一方面在传统主机安全运行时工具的基础上,利用eBPF在安全可观测性上的全面性,并结合云原生应用安全攻防的特点,重点提供了针对网络、文件系统、恶意进程等典型容器逃逸等攻击行为的运行时安全检测和告警能力,同时结合云原生容器资产特性和eBPF对系统底层资源的事件采集能力,将采集到的事件审计定位到细粒度的应用Pod维度,进一步提升了对安全事件的可溯源性和安全策略配置的精细度。关于以上云原生安全开源项目,在后续章节中还会有更为详细的介绍。

2.3 eBPF的架构

通过前文我们已经对eBPF的关键特性和在云原生环境中的典型应用场景有了一定的了解,本节将重点介绍eBPF的架构,图2-1是eBPF概要架构图。

基于网络、可观测性和安全这三个eBPF最重要的应用场景,我们一起来简要剖析eBPF程序的构建编译和运行流程。在用户空间,开发者可以通过Go、Rust和C++等高级语言直接编写eBPF程序,在开源社区可以直接找到语言对应的SDK库,在完成eBPF用户态程序的编写后,通常使用LLVM编译器将程序翻译成eBPF指令。LLVM编译后生成的ELF文件中包含了程序代码、Map描述符、重定向信息和BTF元数据等基础元素,同时还面向如libbpf这样的eBPF加载器提供了必要的信息用于将程序加载到系统内核中。此外,LLVM还提供了如eBPF对象文件的反编译工具这样的开发者工具。有关eBPF运行时、后端编译器等主要基础设施层的介绍可以在官网中找到更多的信息。

在用户态基础设施层之上,面向具体的应用场景,已经涌现了众多优秀的开源项目,像为人熟知的工具集项目BCC,其中集成了大量已经被广泛应用在企业生产环境中的可观测和网络工具及相应的eBPF程序范例。CNCF社区中的Cilium、Falco等项目是eBPF技术在云原生网络和安全领域中应用的代表项目,在被各头部云服务商集成使用的同时也有力推动了eBPF技术的发展。通过这些开源项目对典型eBPF应用场景的封装,结合云服务商集成后提供的产品化能力,使得终端用户可以在不了解eBPF底层逻辑的前提下直接享受其给企业生产运维带来的便利。在eBPF官网还列举了更多正在蓬勃发展的eBPF应用开源项目,有兴趣的读者可以查看各项目源码和介绍。

图2-1 eBPF概要架构图

图2-2展示了eBPF程序典型的工作流程。当eBPF程序经过LLVM/Clang编译为eBPF定义的字节码后,会通过系统调用bpf()函数将字节码指令注入内核中,程序会首先经过eBPF验证器,这里包含对字节码安全性和合规性的多重校验,也包含对eBPF Map的访问,在经过验证器的校验后还会对指令中的描述符进行替换。此时如果内核支持并开启了即时编译模式,内核会把字节码编译成本地机器码并添加至缓存,这时的机器码无须通过虚拟机即可直接运行。

图2-2 eBPF程序典型的工作流程

此时eBPF程序已经被加载到内核中,它不依附于任何对象,而是通过一个引用计数器。之前用于注入的bpf()系统调用会返回一个文件描述符,在内核中这个文件描述符是对程序的一个引用,在用户空间执行系统调用的进程会拥有这个文件描述符,当该进程退出时,文件描述符被释放,程序的引用计数随之递减,当引用归零时,内核会删除该eBPF程序。

接下来,程序是如何被关联到内核事件上的呢?在进行bpf()系统调用时,会通过传递指定的prog_type和expected_attach_type参数来声明程序的挂载类型,验证器中也会对声明的类型进行相关校验,内核中支持数十种挂载程序类型,我们也会在后面的章节中对典型的程序类型进行概要说明。

对于开发者来说,在eBPF程序中会面向应用场景编写定制化的逻辑,而如何在用户空间和内核之间安全地共享数据是一个核心问题。eBPF Map正是解决这个问题的关键数据结构。在eBPF程序和用户空间中均可以访问eBPF Map,也可以被多个程序或指定程序的不同运行时重复访问,因此开发者可以将eBPF Map作为全局变量使用,完成内核eBPF程序和用户空间应用程序之间的双向数据交互。

文件描述符通常只能在程序的生命周期内使用,而eBPF程序在内核中的生命周期又与文件描述符对应的计数器绑定,如果文件描述符被释放且引用为零,那么程序也将被删除,同样的引用计数方式也被应用在eBPF Map上,这就导致eBPF程序之间很难完成数据共享,此时我们可以通过内核BPF中的系统调用BPF_OBJ_PIN或BPF_PROG_BIND_MAP,将eBPF程序或Map固定(pinning),这样即使用户态加载程序退出并不再持有对程序或Map的文件描述符引用,它们也不会被自动清理掉,其他用户态程序还是可以通过缓存在临时文件系统上的路径进行加载。此外还可以使用BPF链接(BPF links)为eBPF程序和与之对应挂载的事件时间建立一个抽象层,BPF链接本身可以被钉在内核事件钩子对应的文件系统上,从而为与之链接的eBPF程序建立一个额外的引用,在加载程序退出时仍然保持内核中eBPF程序的运行。

至此,我们对eBPF的实现原理以及将eBPF程序从用户空间加载到内核空间中并完成执行的工作流程有了一个大致了解,在后面的章节中我们还会对流程中涉及的一些关键技术做更为详细的讲述。

2.4 本章小结

本章首先介绍了eBPF的发展历史,概要介绍了BPF从最初的数据包过滤器功能开始,逐步迭代并迎来重大转折,发展成为近年来业界最热门的技术之一所经历的关键事件。然后通过介绍Linux系统内存中用户空间和内核空间的边界及内核版本缓慢迭代所带来的一些主要问题,引出了eBPF技术对操作系统层面带来的创新,基于eBPF程序可动态加载到内核中运行,以及在安全性和运行效率上的优势,eBPF逐步发展成为一个支持无侵入式修改和扩展操作系统内核功能的编程框架。在云原生环境中,网络、可观测性和安全是eBPF技术典型的应用场景,在社区也涌现了众多优秀的开源项目,其中很多已经被集成应用到集群大规模生产环境中。eBPF已不是纸上谈兵的前沿技术,而是真正在企业生产实践中得到了检验的一门技术,它的出现为内核带来了可编程性,也为其发展带来了无穷的可能性,可以说eBPF正在释放的创新才刚刚开始。

最后我们介绍了eBPF的基础架构及eBPF程序从用户空间加载到内核空间并完成执行的简要工作流程,在下一章中我们会详细讲解eBPF工作流程所涉及的关键技术点,以及常见的eBPF程序开发模式。到目前为止我们还是在一个相对概念化的层面讨论eBPF,在第3章中我们将会更加具体化地了解eBPF开发,并更深入地探索基于eBPF技术应用程序的组成部分。 fb/ru1ilJHEGrzS0G+P/l3dug2oWT8Zj4lFA8fxNttQHueU5dJSNiztKLyDRG4Nl

点击中间区域
呼出菜单
上一章
目录
下一章
×