当前位置: 首页 > Linux

Wasm-bpf- 为云原生 Webassembly 提供通用的 eBPF 内核可编程能力

时间:2023-04-06 01:29:34 Linux

Wasm-bpf:为云原生Webassembly提供通用的eBPF内核可编程性作为原生软件组件的高性能、跨平台和多语言软件沙箱环境,Wasm轻量级容器也非常适合作为下一代无服务器平台的运行时。另一个激动人心的趋势是eBPF的兴起,它使云原生开发者能够构建安全的网络、服务网格和各种可观察性组件,同时也在逐步渗透和深入到内核的各种组件中,提供更强大的内核态可编程交互能力。Wasm-bpf是一个新的开源项目[1],它定义了一套抽象的eBPF相关系统接口,并提供了一套相应的开发工具链、库和通用的Wasm+eBPF运行平台实例,允许任何应用程序在Wasm虚拟机或Wasm轻量级容器具有将使用场景下沉和扩展到内核态的能力,获取内核态和用户态几乎所有的数据,实现对整个操作系统在网络和安全方面的控制。层级的可编程控制,从而极大地扩展了WebAssembly生态在非浏览器端的应用场景。基于eBPF的系统接口,为Wasm带来更多可能或许你也读过SolomonHykes(Docker的创始人之一)的话:如果2008年已经有WASM+WASI,我们就不需要创建Docker在所有。Wasm就是那么重要。服务器端的WebAssembly是计算的未来。现在大多数在浏览器之外运行的Wasm轻量级容器都需要使用WASI(WebAssembly系统接口),因为它们不能依赖浏览器中现有的JavaScript引擎接口。这些运行时允许Wasm应用程序以类似POSIX(但不完全相同)的方式与其主机操作系统交互。但是,相对于传统容器中几乎所有可以使用的系统调用,WASI目前能够提供的系统资源非常有限。目前仅在文件系统和socket网络连接方面提供一些基础支持。对于操作系统底层资源,Wasm的访问、控制和管理能力还有很多差距,比如Wasm模块或其他外部进程的执行资源限制和行为观察,网络的快速转发和处理数据包,甚至与wasm沙箱之外的其他进程进行通信。外设等,还没有比较成熟的解决方案。这也使得大部分Wasm轻量级容器在实际应用中主要专注于纯计算密集型应用,在网络和安全方面仍然需要依赖传统的容器技术。这也是我们建立Wasm-bpf项目的初衷:借助当前内核态eBPF提供的系统接口以及与用户态交互的能力,扩展整个WASI的生态蓝图,并带来Wasm应用程序更多可能的使用场景。能够增强用户空间中的eBPF程序。或者说,类似于运行在浏览器中的Wasm程序,通过JavaScript引擎接口访问浏览器提供的各种系统资源,Wasm-bpf方案通过eBPF虚拟机访问操作系统的各种资源;得益于eBPF目前在Linux内核甚至Windows等其他操作系统中的广泛支持,以及不同内核版本和架构之间的可移植性,以及内核BPF验证引擎的可靠性,我们仍然可以在一定程度上保证应用程序的可移植性性和安全界限。Wasm-bpf项目对内核态eBPF虚拟机和用户态的系统接口实现了完整的抽象机制,并提供了相应的工具链将eBPF应用程序编译成Wasm模块,帮助实现内核态eBPF和Wasm在用户模式下。它们之间没有序列化,高效的共享内存双向通信,通过代码生成技术,提供了简单方便的开发体验,与其他用户态eBPF开发框架几乎一致。随着Wasm组件模型生态支持的不断完善,我们也可以为eBPF社区带来更多的用户态开发语言。Observability、network等不同语言的eBPF应用和数据处理插件也可以轻松集成、复用、统一管理。在几乎成为eBPF用户模式事实上的API标准的libbpf库和WAMR(wasm-micro-runtime)之上,只需要300多行代码就可以构建一个完整的通用Wasm-eBPF运行时组件,并支持大部分eBPF使用场景——任何使用任何主流Wasm运行时,或任何eBPFuserland库,或任何编程语言的人都可以轻松添加相应的虚拟机支持,并重用我们的工具链轻松实现Wasm-eBPF程序编写和开发。在eunomia-bpf项目中,已经有一些结合eBPF和Wasm的探索,但不是针对Wasm原生应用场景设计的,不符合Wasm-eBPF的通用编程模型,性能比较低,所以我们创建了一个新的开源仓库发布,让Wasm-bpf项目专注于使用eBPF来增强和扩展WebAssembly使用场景,并进一步完善相应的工具链和开发库支持:https://github.com/eunomia-bp...eBPF:安全有效地扩展内核eBPF是一项革命性的技术,起源于Linux内核,可以在操作系统的内核中运行沙盒程序。它用于安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。从历史上看,由于内核具有监督和控制整个系统的特权能力,操作系统一直是实现可观察性、安全性和网络功能等功能的理想场所。同时,由于操作系统内核对稳定性和安全性的高要求,内核新功能的迭代通常非常谨慎,难以接受定制化和不太通用的功能改进。因此,与用户模式下的更多功能相比,内核模式下操作系统级别的创新率历来相对较低[2]。eBPF从根本上改变了这个公式。通过允许沙盒程序在操作系统中运行,应用程序开发人员可以在运行时动态地以编程方式向操作系统添加附加功能。然后操作系统保证安全和执行效率,就像借助即时编译(JIT)编译器和验证引擎进行本地编译一样。eBPF程序在内核版本之间是可移植的,并且可以自动更新,避免工作负载中断和节点重启。如今,eBPF被广泛应用于各种场景:在现代数据中心和云原生环境中,它可以提供高性能的网络数据包处理和负载均衡;可观察性,帮助应用程序开发人员跟踪应用程序,为性能故障排除提供洞察力;确保应用程序和容器运行时等的安全执行。可能性是无限的,eBPF在操作系统内核中释放的创新才刚刚开始[3]。eBPF的未来:一个JavaScript可编程的内核接口对于浏览器来说,JavaScript的引入带来的可编程性开启了一场巨大的革命,让浏览器演变成几乎独立的操作系统。现在让我们回到eBPF:为了了解eBPF对Linux内核的可编程性影响,深入了解Linux内核的结构及其与应用程序和硬件的交互方式会很有帮助[4]。Linux内核的主要目的是抽象出硬件或虚拟硬件,并提??供一致的API(系统调用),允许应用程序运行和共享资源。为实现这一目标,我们维护了一系列分配这些职责的子系统和层。每个子系统通常允许一定程度的配置以考虑到用户的不同需求。如果您无法配置所需的行为,则需要更改内核。从历史上看,要改变内核的行为,或者让用户编写的程序能够在内核中运行,有两种选择:对内核模块的原生支持编写内核模块来更改内核源代码,并说服Linux内核社区这种改变是必要的。等待几年,让新内核版本成为商品。定期修复它,因为每个内核版本都可能破坏它。由于缺乏安全边界而导致破坏Linux内核的风险。事实上,这两种方案都不常用,前者太贵,后者便携性差。有了eBPF,就有了一个新的选项,可以在不改变内核源代码或加载内核模块的情况下,对Linux内核的行为进行重新编程,同时确保不同内核版本之间具有一定程度的行为一致性和兼容性,以及安全性。为了实现这个目标,eBPF程序还需要有一套相应的API,让用户自定义的应用程序可以运行和共享资源---换句话说,从某种意义上说,eBPF虚拟机也提供了一套API类似于系统调用机制,借助eBPF和用户态通信机制,Wasm虚拟机和用户态应用程序也可以获得这套“系统调用”的完整使用权。一方面,它可以通过编程方式扩展传统系统调用的能力;更高效的可编程IO处理。如上图所示,今天的Linux内核正在向新的内核模型演进:用户自定义应用程序可以在内核态和用户态下执行,用户态通过传统的系统调用访问系统资源,内核态通过传统的系统调用访问系统资源BPFHelperCalls与系统的各个部分交互。截至2023年初,内核中的eBPF虚拟机Helper系统接口已经超过220个,涵盖了非常多的应用场景。值得注意的是,BPFHelperCall和systemcall并不是竞争关系。它们的编程模型和具有性能优势的场景是完全不同的,不会完全相互替代。Wasm和Wasi相关生态的情况类似。专门设计的wasi接口需要经过漫长的标准化过程,但在特定场景下或许能为用户态应用获得更好的性能和可移植性保证,而eBPF是在保证本质和可移植性的前提下沙箱,可以提供快速灵活的系统接口扩展方案。目前的eBPF还处于早期阶段,但凭借目前eBPF与内核接口和用户态交互的能力,通过Wasm-bpf的系统接口转换,Wasm虚拟机中的应用程序几乎有能力获取任何在内核态和用户态运行。调用数据和返回值(kprobe,uprobe...);以极低的成本收集和理解所有系统调用,并获取所有网络操作的数据包和套接字级数据(跟踪点、套接字...);在网络数据包处理解决方案中添加额外的协议分析器,并轻松编写任何转发逻辑(XDP、TC...)以满足不断变化的需求,而无需离开Linux内核的数据包处理环境。不仅如此,eBPF还具备向用户空间任意进程的任意地址写入数据的能力(bpf_probe_write_user[5]),有限范围内修改内核函数的返回值(bpf_override_return[6]),甚至直接在内核模式下执行一些系统调用[7];幸运的是,在加载到内核之前,eBPF会对字节码进行严格的安全检查,确保不会出现内存越界等操作。同时,很多可能扩大攻击面、带来安全隐患的功能,需要在编译内核时明确选择启用;在Wasm虚拟机将字节码加载到内核之前,您还可以显式选择启用或禁用某些eBPF功能,以确保沙箱的安全性。所有这些场景都不需要离开Wasm轻量级容器:与传统应用程序中使用Wasm作为数据处理或控制插件不同,这些步骤是由Wasm虚拟机外部的逻辑实现的,现在可以在Wasm轻量级容器中实现container实现eBPF和几乎所有eBPF可以访问的系统资源,完成控制和交互,甚至实时生成eBPF代码改变内核的行为逻辑,实现整个系统从用户态到内核态的可编程性。用户空间与eBPF程序的交互过程eBPF程序是基于函数和事件驱动的。当内核或用户空间应用程序通过某个挂钩点时,将运行特定的eBPF程序。要使用一个eBPF程序,首先我们需要使用clang/LLVM工具链将相应的源代码编译成bpf字节码,其中包含相应的数据结构定义,maps和progs定义,progs是程序段,maps可以用来存储数据或与用户空间进行双向通信。之后,我们就可以借助用户态开发框架和加载框架实现一个完整的eBPF应用。对于一个完整的eBPF应用,通常的用户态eBPF开发框架通常需要包括用户态和内核态两部分:用户态程序需要通过一系列的系统调用(主要是bpf系统调用)与内核进行交互),并创建对应的map保存内核态数据或与用户态通信,根据配置动态选择加载不同的程序段,动态修改字节码或配置eBPF程序的参数,加载相应的字节码信息进入内核,并通过验证器保证安全性,实现maps和内核的双向通信,通过ringbuffer/perfbuffer等机制将数据从内核态传输到用户态(反之亦然)。内核态主要负责具体的计算逻辑和数据采集。定义在用户态Wasm-eBPF系统接口之上的新eBPF开发框架该项目本质上是希望将Wasm沙箱作为构建在操作系统之上的另一个用户态运行时空间,让Wasm应用程序可以在sandbox实现与运行在普通用户模式下的eBPF应用程序相同的编程模型和执行逻辑。Wasm-bpf需要在主机上(在沙箱之外)构建一个运行时模块,以及一些在沙箱内编译为Wasm字节码的运行时库以提供全面支持。要实现一个完整的开发模型,我们需要:一个Wasm模块可以对应多个eBPF程序;一个eBPF程序实例也可以被多个Wasm模块共享;eBPF程序可以从Wasm沙箱中动态加载到内核中,所有需要的挂载点挂载和卸载,控制多个eBPF字节码对象的完整生命周期,支持大部分eBPF程序类型;可以通过多种类型的Maps与内核通信,支持大部分的Maps类型;通过ringbuffer和perfeventpolling高效地将信息从内核态发送到用户态(对于ringbuffer,也可以反过来);可以适配几乎所有使用eBPF程序的应用场景,并且可以在不改变WasmVM系统接口的情况下,随着内核功能的增加而进化和扩展。这就是Wasm-bpf项目目前所做的。我们还提出了一个新的WASI提案:WASI-eBPF[7]。在Wasm-bpf项目中,Wasm与eBPF虚拟机之间的所有通信都不需要经过序列化和反序列化机制,而是通过工具链和代码生成技术以及BTF(BPFTypeFormat[12])信息的支持,我们可以通过可能不同的结构内存布局、不同的字节序机制和不同的指针宽度来实现eBPF和Wasm之间的正确通信。在运行时几乎不会引入额外的开销;通过eBPFMaps通信时,数据可以直接从内核态复制到Wasm虚拟机的内存中,避免了多次复制带来的额外损失。同时,通过自动生成骨架(bpf代码框架)和类型定义,也大大提升了用户态程序的eBPF-Wasm开发体验。得益于libbpf提供的CO-RE(Compile-Once,RunEverywhere)技术,在不同内核版本之间移植eBPF字节码对象不需要额外的重新编译过程,也没有LLVM/Clang依赖[12]。通常编译后的eBPF-Wasm模块只有90Kb左右,可以在不到100ms的时间内动态加载到内核中并执行。我们还在仓库中提供了几个示例,分别对应可观察性、网络、安全等各种场景。感谢华南理工大学赖晓政副教授,西安邮电大学陈立军教授团队,大炭科技王璞老师和石继成老师在Wasm和eBPF结合方面的指导和帮助。在接下来的工作中,我们将参与2023OpenSourceProject旅的同学对Wasm-bpf的一些具体应用场景进行更深入的研究和讨论,并在下一篇博客中给出更详细的原理分析和性能分析,以及一些相应的代码示例。Wasm-bpf编译工具链和运行时模块目前由eunomia-bpf开源社区开发和维护。感谢中科院软件所PLCT实验室对社区的大力支持和资助,感谢社区伙伴的贡献。接下来,我们也将在相应的eBPF和Wasm相关的工具链和运行时上做更多的改进和探索,积极向上游社区提供反馈和贡献。参考文献[1]wasm-bpfGithub开源地址:https://github.com/eunomia-bp...[2]当WASM遇上eBPF:使用WebAssembly编写、分发、加载和运行eBPF程序:https://zhuanlan.zhihu.com/p/...[3]https://ebpf.io/[4]什么是eBPF:https://ebpf.io/what-is-ebpf[5]进攻性BPF:理解和使用bpf_probe_write_userhttps://embracethered.com/blo...[6]云原生安全攻防|利用eBPF逃避容器技术的分析与实践:https://security.tencent.com/...[7]内核版本。md:https://github.com/iovisor/bc...[8]WebAssembly:没有容器的Docker:https://zhuanlan.zhihu.com/p/...[9]云原生项目的可扩展性WebAssembly简介https://mp.weixin.qq.com/s/fa...[10]WASI-eBPF:https://github.com/WebAssembl...[11]BPFBTF详解:https://www.ebpf.top/post/ker...[12]BPFPortabilityandCO-RE(CompileOnce,RunAnywhere):https://cloud.tencent.com/dev...