关于webassembly:Wasmbpf-为云原生-Webassembly-提供通用的-eBPF-内核可编程能力

1次阅读

共计 6700 个字符,预计需要花费 17 分钟才能阅读完成。

作者:郑昱笙,陈茂林

Wasm 最后是以浏览器平安沙盒为目标开发的,倒退到目前为止,WebAssembly 曾经成为一个用于云原生软件组件的高性能、跨平台和多语言软件沙箱环境,Wasm 轻量级容器也非常适合作为下一代无服务器平台运行时。另一个令人兴奋的趋势是 eBPF 的衰亡,它使云原生开发人员可能构建平安的网络、服务网格和多种可观测性组件,并且它也在逐渐浸透和深刻到内核的各个组件,提供更弱小的内核态可编程交互能力。

Wasm-bpf 是一个全新的开源我的项目 [1],它定义了一套 eBPF 相干零碎接口的形象,并提供了一套对应的开发工具链、库以及通用的 Wasm + eBPF 运行时平台实例,让任意 Wasm 虚拟机或者 Wasm 轻量级容器中的利用,有能力将应用场景下沉和拓展到内核态,获取内核态和用户态的简直所有数据,在网络、平安等多个方面实现对整个操作系统层面的可编程管制,从而极大的拓展 WebAssembly 生态在非浏览器端的利用场景。

基于 eBPF 的零碎接口,为 Wasm 带来更多可能

兴许你也曾经看过 Solomon Hykes (Docker 的创始人之一) 这句话:

如果在 2008 年曾经有了 WASM + WASI,咱们基本不须要创立 Docker。Wasm 就有这么重要。服务端的 WebAssembly 是计算的将来。

因为无奈依赖浏览器中现有可用的 JavaScript 引擎接口,所以目前大多数在浏览器外运行的 Wasm 轻量级容器须要应用 WASI(WebAssembly 零碎接口)。这些运行时容许 Wasm 应用程序以与 POSIX 相似(但不完全相同)的形式与其 host 操作系统交互。

然而,绝对于传统的容器中能够应用简直所有的零碎调用,目前 WASI 所能提供的系统资源十分无限,目前仅仅在文件系统、socket 网络连接等方面提供了一些根本的反对,对于操作系统底层资源的拜访、管制和治理能力依然存在大量空白,例如对 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 社区带来更多用户态开发语言,不同语言实现的可观测性、网络等 eBPF 利用和数据处理插件也能够被轻松集成、复用、对立治理。

在简直曾经成为 eBPF 用户态事实上的 API 规范的 libbpf 库,和 WAMR(wasm-micro-runtime) 之上,只须要 300+ 行代码即可构建残缺的通用 Wasm-eBPF 运行组件,并反对大多数的 eBPF 应用场景 — 任何人用任何支流 Wasm 运行时,或者任何 eBPF 用户态库,以及任何编程语言,都能够轻松增加对应的虚拟机反对,并复用咱们的工具链轻松实现 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 虚拟机也提供了一套相似于零碎调用的机制,借助 eBPF 和用户态通信的机制,Wasm 虚拟机和用户态利用也能够取得这套“零碎调用”的残缺使用权,一方面能可编程地扩大传统的零碎调用的能力,另一方面实现更高效的可编程 IO 解决。

正如上图所示,当今的 Linux 内核正在向一个新的内核模型演变:用户定义的应用程序能够在内核态和用户态同时执行,用户态通过传统的零碎调用拜访系统资源,内核态则通过 BPF Helper Calls 和零碎的各个局部实现交互。截止 2023 年初,内核中的 eBPF 虚拟机中曾经有 220 多个 Helper 零碎接口,涵盖了十分多的利用场景。

值得注意的是,BPF Helper Call 和零碎调用二者并不是竞争关系,它们的编程模型和有性能劣势的场景齐全不同,也不会齐全代替对方。对 Wasm 和 Wasi 相干生态来说,状况也相似,专门设计的 wasi 接口须要经验一个漫长的标准化过程,但可能在特定场景能为用户态利用获取更佳的性能和可移植性保障,而 eBPF 在保障沙箱实质和可移植性的前提下,能够提供一个疾速灵便的扩大零碎接口的计划。

目前的 eBPF 依然处于晚期阶段,然而借助以后 eBPF 提供的内核接口和用户态交互的能力,经由 Wasm-bpf 的零碎接口转换,Wasm 虚拟机中的利用曾经简直有能力获取内核以及用户态任意一个函数调用的数据和返回值(kprobe,uprobe…);以很低的代价收集和了解所有零碎调用,并获取所有网络操作的数据包和套接字级别的数据(tracepoint,socket…);在网络包解决解决方案中增加额定的协定分析器,并轻松地编程任何转发逻辑(XDP,TC…),以满足一直变动的需要,而无需来到 Linux 内核的数据包解决环境。

不仅如此,eBPF 还有能力往用户空间任意过程的任意地址写入数据(bpf_probe_write_user[5]),有限度地批改内核函数的返回值(bpf_override_return[6]),甚至在内核态间接执行某些零碎调用 [7];所幸的是,eBPF 在加载进内核之前对字节码会进行严格的安全检查,确保没有内存越界等操作,同时,许多可能会扩充攻击面、带来平安危险的性能都是须要在编译内核时明确抉择启用能力应用的;在 Wasm 虚拟机将字节码加载进内核之前,也能够明确抉择启用或者禁用某些 eBPF 性能,以确保沙箱的安全性。

所有的这些场景都不须要来到 Wasm 轻量级容器:不像传统的应用 Wasm 作为数据处理或者管制插件的利用中,这些步骤由 Wasm 虚拟机外的逻辑实现,当初能够在 Wasm 轻量级容器中实现对 eBPF 以及 eBPF 能拜访的简直所有系统资源,残缺的管制和交互,甚至实时生成 eBPF 代码扭转内核的行为逻辑,实现整个零碎从用户态扩大到内核态的可编程性。

用户空间和 eBPF 程序的交互流程

eBPF 程序是以函数为单位的、事件驱动的,当内核或用户空间应用程序通过某个 hook 点时就会运行特定的 eBPF 程序。要应用一个 eBPF 程序,首先咱们须要应用 clang/LLVM 工具链将对应的源代码编译为 bpf 字节码,其中蕴含对应的数据结构定义、maps 和 progs 定义,progs 即程序段,maps 能够用来存储数据或者和用户空间实现双向通信。之后,咱们能够借助用户态的开发框架和加载框架,实现残缺的 eBPF 利用。

通常的用户态 eBPF 开发框架

对于一个残缺的 eBPF 利用,通常须要蕴含用户态和内核态两局部:

  • 用户态程序须要通过一系列零碎调用跟内核进行交互(次要是 bpf 零碎调用),创立对应的 map 以在内核态保留数据或和用户态通信,依据配置动静抉择加载不同的程序段,动静批改字节码或配置 eBPF 程序的参数,将对应的字节码信息加载进内核,通过验证器确保安全性,并通过 maps 和内核之间实现双向通信,通过 ring buffer / perf buffer 之类的机制从内核态向用户态传递数据(或者反之)。
  • 内核态次要负责具体的计算逻辑与数据收集。

在用户态 Wasm-eBPF 零碎接口之上定义的全新 eBPF 开发框架

这个我的项目实质上能够说是心愿把 Wasm 沙箱当做在操作系统之上建设的另一个用户态运行空间,让 Wasm 利用在沙箱中实现和通常用户态中运行的 eBPF 利用一样的编程模型和执行逻辑。Wasm-bpf 会须要一个在 host(沙箱内部)构建的运行时模块,以及一些在沙箱外部被编译为 Wasm 字节码的运行时库来提供残缺的反对。

要实现齐备的开发模型,咱们须要:

  • 一个 Wasm 模块能够对应多个 eBPF 程序;
  • 一个 eBPF 程序实例也能够被多个 Wasm 模块所共用;
  • 能够将 eBPF 程序从 Wasm 沙箱中动静加载进内核、抉择所需的挂载点挂载、卸载,管制多个 eBPF 字节码对象的残缺生命周期,并反对大多数的 eBPF 程序类型;
  • 能够通过多种类型的 Maps 和内核双向通信,反对大多数的 Maps 类型;
  • 通过 ring buffer 和 perf event polling 从内核态向用户态高效发送信息(对于 ring buffer 来说,也能够反之);
  • 简直能够适配于所有的应用 eBPF 程序的利用场景,并能够随着内核性能的增加一直演变和扩大,同时不须要变动 Wasm 虚拟机的零碎接口。

这就是目前 Wasm-bpf 我的项目所做的工作。咱们也提出了一个新的 WASI 的 Proposal: WASI-eBPF[7].

在 Wasm-bpf 我的项目中,所有 Wasm 和 eBPF 虚拟机之间的通信都无需通过序列化、反序列化机制,通过工具链中代码生成技术和 BTF(BPF 类型格局 [12])信息的反对,咱们能够实现在 eBPF 和 Wasm 之间可能不同的构造体内存布局、不同的大小端机制、不同的指针宽度之间的正确通信,在运行时简直不会引入任何额定的开销;通过 eBPF Maps 通信的时候数据能够间接由内核态复制到 Wasm 虚拟机的内存中,防止屡次拷贝带来的额定损耗。同时,通过主动生成 skeleton(bpf 代码框架)和类型定义的形式,用户态程序的 eBPF-Wasm 开发体验也失去了十分大的改善。

得益于 libbpf 提供的 CO-RE(Compile-Once, Run Everywhere)技术,在不同内核版本之间移植 eBPF 字节码对象,也不须要引入额定的从新编译流程,运行时也没有任何的 LLVM/Clang 依赖 [12]。

通常一个编译好的 eBPF-Wasm 模块只有大概 90Kb,在不到 100ms 内即能够实现动静加载进内核并执行的过程。咱们也在仓库中提供了几个例子,别离对应于可观测、网络、平安等多种场景。

感激华南理工大学赖晓铮副教授、西安邮电大学陈莉君传授团队和达坦科技王璞、施继成老师对 Wasm 和 eBPF 相结合的领导与帮忙,在接下来的工作中,咱们会和加入 2023 开源毕设之旅的同学们一起针对一些 Wasm-bpf 具体的利用场景,进行更深刻的钻研与探讨,并在下一篇 blog 中给出更具体的原理解析与性能剖析,以及对应的一些代码示例。

Wasm-bpf 编译工具链与运行时模块等目前由 eunomia-bpf 开源社区开发与保护,感激中科院软件所 PLCT 实验室对社区的大力支持和赞助,感激社区伙伴们的奉献。接下来,咱们也会在对应的 eBPF 和 Wasm 相干的工具链和运行时方面,进行更多的欠缺和摸索,并积极向上游社区反馈和奉献。

参考资料

  • [1] wasm-bpf Github 开源地址: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] Offensive BPF: Understanding and using bpf_probe_write_user https://embracethered.com/blo…
  • [6] 云原生平安攻防|应用 eBPF 逃逸容器技术剖析与实际:https://security.tencent.com/…
  • [7] kernel-versions.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] BPF BTF 详解:https://www.ebpf.top/post/ker…
  • [12] BPF 可移植性和 CO-RE(一次编译,到处运行):https://cloud.tencent.com/dev…
正文完
 0