当前位置: 首页 > Linux

当WASM遇上eBPF:使用WebAssembly编写、分发、加载和运行eBPF程序-龙力科技

时间:2023-04-06 23:30:39 Linux

文/郑雨生eBPF技术探索SIG贡献者,浙江大学学生是当今云原生世界最流行的两个轻量级代码执行沙盒/VM,分别是eBPF和WebAssembly。它们都运行由C、C++、Rust等语言编译而成的高性能字节码程序,并且都是跨平台和可移植的。两者最大的区别是eBPF运行在Linux内核,而WebAssembly运行在用户空间。我们希望对两者进行一些尝试:使用WASM编写一个通用的eBPF程序,然后将其分发到任何不同版本和架构的Linux内核中,无需重新编译即可运行。WebAssemblyvseBPFWebAssembly(缩写为Wasm)是一种基于堆栈虚拟机的二进制指令格式。Wasm被设计为可移植目标,作为C/C++/RUST等高级语言的编译目标,使客户端和服务器应用程序能够部署在Web上。WASM的运行时有多种实现,包括浏览器和独立系统,它可用于视频和音频编解码器、图形和3D、多媒体和游戏、密码计算或可移植语言实现等应用程序。虽然WASM是为提高网页中性能敏感模块的性能而提出的字节码标准,但WASM不仅可以在浏览器中使用,还可以在其他环境中使用。WASM已经发展成为面向云原生软件组件的轻量级、高性能、跨平台和多语言的软件沙箱环境。与Linux容器相比,WebAssembly的启动速度可以提高100倍,内存和磁盘占用空间小得多,并且具有定义更完善的安全沙箱。然而,代价是WebAssembly需要自己的语言SDK和编译器工具链,这使得它成为比Linux容器更受限制的开发环境。WebAssembly越来越多地用于难以部署Linux容器或应用程序性能至关重要的边缘计算场景。WASM的编译部署过程如下:wasm-compile-deploy通常可以将C/C+/RUST等高级语言编译成WASM字节码,并在WASM虚拟机中加载运行。WASM虚拟机通过解释执行或JIT将WASM字节码翻译成对应平台(x86/Arm等)的机器码。eBPF源自BPF,本质上是内核中一个高效灵活的虚拟机组件,它以安全的方式在许多内核挂钩点执行字节码。BPF的最初目的是为了高效的网络包过滤。经过重新设计,eBPF不再局限于网络协议栈,而是成为了内核的顶层子系统,演化为一个通用的执行引擎。开发者可以基于eBPF开发性能分析工具、软件定义网络、安全等诸多场景。eBPF有一些编程限制,需要验证者来保证它在内核应用场景下是安全的(例如,没有死循环、内存越界等),但这也意味着eBPF的编程模型不是图灵完备的.相比之下,WebAssembly是一种图灵完备的语言,具有扩展的WASI(WebAssemblySystemInterface,WASM系统接口),可以打破沙箱并访问原生OS库,同时WASM运行时可以安全隔离并接近原生执行用户的性能-空间代码。这两个领域有很多不同之处,但也有很多重叠之处。已经有一些尝试在Linux内核中运行WebAssembly,但大部分都没有成功。eBPF是这种应用场景更好的选择。但是WebAssembly程序可以处理许多类似内核的任务,并且可以AOT编译到本机应用程序中。来自CNCF的WasmEdgeRuntime是一个很好的基于LLVM的编译器,用于云原生WebAssembly。本机应用程序将所有沙箱检查合并到本机库中,这允许WebAssembly程序表现得像一个独立的unikernel“库操作系统”。此外,这种AOT编译的沙盒WebAssembly应用程序可以在微内核操作系统(如seL4)上运行,并且可以接管许多“内核级”任务[1]。WebAssembly可以下降到内核级别,而eBPF也可以上升到应用程序级别。在Sidecar代理中,EnvoyProxy率先使用Wasm作为扩展机制来对数据平面进行编程。开发人员可以使用C、C++、Rust、AssemblyScript、Swift和TinyGo等语言编写特定于应用程序的代理逻辑,并将该模块编译成Wasm。通过代理Wasm标准,代理可以在Wasmtime和WasmEdge[2]等高性能运行时机制中执行那些Wasm插件。虽然目前很多应用同时使用两者,但大多数时候这两个虚拟机是相互独立的,没有交集:比如在可观察性应用中,通过eBPF探针获取数据,获取数据后,用户可以动态导入WASM插件模块进行可配置的数据处理。WASM模块和eBPF程序的分发、运行、加载和控制相互独立,只有数据流关联。我们的尝试一般来说,一个完整的eBPF应用程序分为用户空间程序和内核程序两部分:用户空间程序负责加载BPF字节码到内核,或者负责读取统计信息或事件细节由内核返回,用于相关的数据处理和控制。内核中的BPF字节码负责执行内核中的特定事件,必要时将执行结果通过maps或perf-event事件发送到用户空间。用户态程序可以控制一些eBPF程序参数和变量,以及加载eBPF程序之前的挂载点;它还可以通过映射等方式进行用户态和内核态的双向通信。一般来说,用户态eBPF程序可以基于libbpf库来开发,以控制内核态eBPF程序的加载和运行。那么,如果把用户态的控制和数据处理逻辑全部移到WASM虚拟机中,通过WASM模块对eBPF字节码进行打包分发,在WASM虚拟机内部控制整个eBPF程序的加载和执行machine,也许我们可以将两者的优点结合起来,让任何eBPF程序都具有以下特点:可移植性:使eBPF工具和应用程序完全独立于平台和可移植,无需重新编译就可以跨平台分发。隔离性:借助WASM的可靠性和隔离性,eBPF程序的加载和执行以及用户态的数据处理过程更加安全可靠;事实上,一个eBPF应用程序通常拥有比内核模式更多的用户模式控制代码。包管理:借助WASM的生态和工具链,可以完成eBPF程序或工具的分发、管理和加载。目前,eBPF程序或工具生态可能缺乏通用的包管理或插件管理系统。跨语言:目前eBPF程序由多种用户态语言(如Go++等)开发,可以将30多种编程语言编译成WebAssembly模块,让各种背景(C、C、Go、Rust、Java、TypeScript等)用他们选择的语言编写eBPF用户态程序,无需学习新语言。敏捷性:对于大型eBPF应用程序,WASM可以用作插件扩展平台:扩展可以在运行时直接从控制平面交付和重新加载。这不仅意味着每个人都可以使用官方的和未修改的应用程序来加载自定义扩展,而且可以在运行时推送和/或测试eBPF程序的任何错误修复和/或更新,而无需更新和/或重新部署新的二进制文件。轻量级:WebAssembly微服务消耗1%的资源。与Linux容器应用相比,冷启动时间为1%:我们可能会利用这一点将eBPF实现为服务,让eBPF程序的加载和执行更轻量、更快速、更容易做到。eunomia-bpf是eBPFTechnologyExplorationSIG[3][5]发起并孵化的项目,也在github[4]上开源。eunomia-bpf是一个用于eBPF程序的轻量级开发加载框架,包括一个用户模式动态加载框架/运行时库,以及一个用于编译WASM和eBPF字节码的简单工具链容器。实际上,在WASM模块中编写eBPF代码与我们熟悉的使用libbpf框架或Coolbpf开发eBPF程序的方式基本相同。WASM的复杂性会隐藏在eunomia-bpf的编译工具链和运行时库中。开发者可以专注于eBPF程序的开发和调试,无需了解WASM的背景知识,也无需担心WASM编译环境的配置。使用WASM模块分发和动态加载eBPF程序。eunomia-bpf库包含一个简单的命令行工具(ecli),它包含一个小型WASM运行时模块和eBPF动态加载功能,可以直接下载使用:ecli会自动下载并加载wasm模块sigsnoop/app。网页中的wasm,其中包含一个eBPF程序,用于跟踪内核中进程发送和接收的信号。这里我们可以看到一个简单的JSON格式的输出,包括进程的PID、信号的类型、发送方和接收方以及信号的名称。它还可以附带一些命令行参数,例如:我们可以通过-p来控制它跟踪哪个进程,在内核态eBPF程序中进行一些过滤和处理。也可以使用ecli动态加载使用其他工具,比如opensnoop:opensnoop会跟踪进程的open()调用,即内核中所有打开文件的操作。这里我们可以看到进程的PID、UID、返回值、调用。标志、进程名称和文件名等信息。内核态的eBPF程序会被包含在WASM模块中进行分发,加载时会通过BTF信息和libbpf进行重定位操作,以适应不同的内核版本。同时,由于用户态的相关处理代码完全由WASM编写,而内核态由eBPF指令编写,不受具体指令集(x86、Arm等)的限制,可以运行在不同的平台上。使用WASM开发打包eBPF程序同样以上面提到的sigsnoop为例。要跟踪进程的信号发送和接收,我们首先需要在sigsnoop.bpf.c中编写内核态eBPF代码:这里我们使用tracepoint/signal/signal_generate这个tracepoint来跟踪内核中的信号产生事件。内核态代码通过BPF_MAP_TYPE_PERF_EVENT_ARRAY向用户态输出信息。为此,我们需要在sigsnoop.bpf.h头文件中定义一个导出信息的结构:可以直接使用eunomia-bpf编译工具链编译成JSON格式。生成了一个package.json文件,可以直接使用ecli加载运行:我们所有的编译工具链都已经打包成docker镜像发布到dockerhub,可以直接开箱即用。此时只有内核态的eBPF代码和一些辅助信息被动态加载运行,这有助于eunomia-bpf库自动获取内核态向用户态上报的事件。如果我们要在用户态配置和调整一些参数和处理数据,就需要在用户态编写代码,将内核态的eBPF代码和用户态的代码打包成一个完整的eBPF程序。一行命令就可以直接生成eBPF程序的用户态WebAssembly开发框架:我们提供了C语言版本的WASM开发框架,包括以下文件:ewasm-skel.h:用户头文件-模式的WebAssembly开发框架,包括预编译的eBPF程序字节码和eBPF程序框架辅助信息,用于动态加载。eunomia-include:一些header-only库函数和辅助开发的辅助文件。app.c:用户态WebAssembly程序的主要代码,包括eBPF程序的主要逻辑和eBPF程序的数据处理流程。以sigsnoop为例,用户态包含一些命令行解析、eBPF程序配置和数据处理的代码,信号事件的英文名称会根据信号编号添加到事件中:最后,容器镜像可以用于用一行命令完成WebAssembly/eBPF程序的编译和打包可以使用ecli一键运行:由于我们基于一次编译运行的libbpf框架完成了加载和启动eBPF程序的操作在任何地方,编译和运行两个步骤是完全分离的,可以通过网络访问或直接以任何方式分发和部署eBPF程序,而不依赖于特定的内核版本。凭借WebAssembly的轻量级特性,eBPF程序的启动速度也远高于以图片形式分发的libbpf程序,通常不到100ms即可完成。使用LLVM和Clang编译运行,消耗了大量的时间和资源,是质的飞跃。上面提到的示例程序的完整代码可以在这里找到[6]。Demo视频我们在B站也有一个demo视频,演示了如何将一个eBPF工具程序从bcc/libbpf-tools移植到eunomia-bpf,并使用WASM或JSON文件分发加载eBPF程序:https://www.bilibili.com/video...我们是如何做到的ecli是一个简单的命令行工具,基于我们的底层eunomia-bpf库和运行时实现。我们的项目架构如下图所示:archecli工具是基于ewasm库实现的,其中包含一个WAMR(wasm-micro-runtime)runtime,以及一个基于libbpf库构建的eBPF动态加载模块。粗略地说,我们在WASM运行时和用户模式??libbpf之间添加了一个额外的抽象层(eunomia-bpf库),以便可以从JSON对象动态加载eBPF代码一次编译到处运行。JSON对象在编译时会被包含在WASM模块中,所以在运行时,我们可以通过解析JSON对象来获取eBPF程序的信息,进而动态加载eBPF程序。使用WASM或JSON编译分发eBPF程序的流程图大致如下:和字节码转换成JSON格式。在用户态开发的高级语言(如C语言)中嵌入JSON数据,并提供一些API用于以JSON形式操作eBPF程序骨架。将用户态程序和JSON数据编译成WASM字节码并打包为WASM模块,然后在目标机器上加载并运行WASM程序。从WASM模块加载嵌入式JSON数据,并使用eunomia-bpf库动态加载和配置eBPF程序框架。我们需要做的就是绑定少量原生API和WASM运行时,在WASM代码中处理JSON数据。您可以在单个WASM模块中拥有多个eBPF程序。如果您不使用我们提供的WASM运行时,或者想使用其他语言开发用户态eBPF辅助代码,您可以基于我们提供的eunomia-bpf库完成一些WebaAssembly绑定。另外,对于eunomia-bpf库,eBPF程序可以在没有WASM模块和runtime的情况下启动并动态加载,但此时只有内核态的eBPF程序字节码被动态加载和运行。你可以手动或用任何语言修改JSON对象来控制eBPF程序的加载和参数,通过eunomia-bpf自动获取内核态上报的返回数据。对于初学者来说,这可能比使用WebAssembly更简单方便:只需编写一个内核模式的eBPF程序,然后使用eunomia-cc工具链将其编译成JSON格式,最后使用eunomia-bpf库加载并运行它.无需考虑任何用户态辅助程序,包括WASM。有关详细信息,请参阅我们的用户手册[7]或示例代码[8]。未来方向目前,eunomia-bpf是一个非常重要的具有特定API标准和相关生态的开发工具链。我们希望如果有机会,可以和SIG社区的其他成员一起讨论,形成一个特定的API标准,基于eBPF和WASM等技术,共同提供一个通用的、跨平台的、内核版本的插件——在生态系统中,将eBPF和WASM的超能力添加到各自的应用程序中。目前,eunomia-bpf跨内核版本的动态加载特性仍然依赖于内核的BTF信息。SIG社区本身的coolbpf项目[9]可以提供BTF的自动生成和低版本内核的适配功能。未来对低版本内核的支持将基于Coolbpf已有的部分完成。同时,我们也会提供Coolbpf的API实现和远程编译后端,提供类似eunomia-bpf的完全内核态编译运行分离的功能,让使用CoolbpfAPI开发eBPF的程序在运行后可以在任意内核上运行远程编译。版本和架构直接使用,部署时无需再次连接远程服务器;编译后的eBPF程序也可以直接作为Go、Python、Rust等语言的开发包使用,方便开发者轻松获取eBPF程序上报的信息。并且不需要再次编译任何eBPF程序。SIG社区孵化于大学的LinuxMicroscope(LMP)项目[10],已经有一些基于eunomia-bpf的计划提供通用的、标准化的、可随时下载运行的eBPF程序或工具库,还在改进阶段。参考资料[1]eBPF与WebAssembly:哪个VM将主宰云原生时代?https://juejin.cn/post/704372...[2]eBPF与Wasm:探索服务网格数据平面的未来:https://juejin.cn/post/704372://cloudnative.to/blog/e...【3】eBPF技术探索SIG主页:https://openanolis.cn/sig/ebp...【4】eunomia-bpfGithub仓库:https://github.com/eunomia-bp...【5】eunomia-bpf龙蜥社区镜像仓库:https://gitee.com/anolis/eunomia【6】sigsnoop示例代码:https://gitee.com/anolis/euno...【7】eunomia-bpf使用手册:https://openanolis.cn/sig/ebp...【8】更多示例代码:https://gitee.com/anolis/euno...【9】coolbpf项目介绍:https://openanolis.cn/sig/ebp...【10】LMP项目介绍:https://openanolis.cn/sig/ebp...——完——