當 WASM 遇見 eBPF:使用 WebAssembly 編寫、分發、載入執行 eBPF 程式 | 龍蜥技術
文/鄭昱笙 eBPF 技術探索 SIG Contributor、浙江大學學生
當今雲原生世界中兩個最熱門的輕量級程式碼執行沙箱/虛擬機器是 eBPF 和 WebAssembly。它們都執行從 C、C++ 和 Rust 等語言編譯的高效能位元組碼程式,並且都是跨平臺、可移植的。二者最大的區別在於:eBPF 在 Linux 核心中執行,而 WebAssembly 在使用者空間中執行。我們希望能做一些將二者相互融合的嘗試:使用 WASM 來編寫通用的 eBPF 程式,然後可以將其分發到任意不同版本、不同架構的 Linux 核心中,無需重新編譯即可執行。
WebAssembly vs eBPF
WebAssembly(縮寫 Wasm)是基於堆疊虛擬機器的二進位制指令格式。Wasm 是為了一個可移植的目標而設計的,可作為 C/C+/RUST 等高階語言的編譯目標,使客戶端和伺服器應用程式能夠在 Web 上部署。WASM 的執行時有多種實現,包括瀏覽器和獨立的系統,它可以用於影片和音訊編解碼器、圖形和 3D、多媒體和遊戲、密碼計算或行動式語言實現等應用。
儘管 WASM 是為了提高網頁中效能敏感模組表現而提出的位元組碼標準, 但是 WASM 卻不僅能用在瀏覽器(broswer)中, 也可以用在其他環境中。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,本質上是處於核心中的一個高效與靈活的虛擬機器元件,以一種安全的方式在許多核心 hook 點執行位元組碼。BPF 最初的目的是用於高效網路報文過濾,經過重新設計,eBPF 不再侷限於網路協議棧,已經成為核心頂級的子系統,演進為一個通用執行引擎。開發者可基於 eBPF 開發效能分析工具、軟體定義網路、安全等諸多場景。eBPF 有一些程式設計限制,需要經過驗證器確保其在核心應用場景中是安全的(例如,沒有無限迴圈、記憶體越界等),但這也意味著 eBPF 的程式設計模型不是圖靈完備的。相比之下,WebAssembly 是一種圖靈完備的語言,具有能夠打破沙盒和訪問原生 OS 庫的擴充套件 WASI (WebAssembly System Interface, WASM系統介面) ,同時 WASM 執行時可以安全地隔離並以接近原生的效能執行使用者空間程式碼。二者的領域主體上有不少差異,但也有不少相互重疊的地方。
有一些在 Linux 核心中執行 WebAssembly 的嘗試,然而基本上不太成功。eBPF 是這個應用場景下更好的選擇。但是 WebAssembly 程式可以處理許多類核心的任務,可以被 AOT 編譯成原生應用程式。來自 CNCF 的 WasmEdge Runtime 是一個很好的基於 LLVM 的雲原生 WebAssembly 編譯器。原生應用程式將所有沙箱檢查合併到原生庫中,這允許 WebAssembly 程式表現得像一個獨立的 unikernel “庫作業系統”。此外,這種 AOT 編譯的沙盒 WebAssembly 應用程式可以在微核心作業系統(如 seL4)上執行,並且可以接管許多“核心級”任務[1]。
雖然 WebAssembly 可以下降到核心級別,但 eBPF 也可以上升到應用程式級別。在 sidecar 代理中,Envoy Proxy 開創了使用 Wasm 作為擴充套件機制對資料平面進行程式設計的方法。開發人員可以用 C、C++、Rust、AssemblyScript、Swift 和 TinyGo 等語言編寫特定應用的代理邏輯,並將該模組編譯到 Wasm 中。透過 proxy-Wasm 標準,代理可以在 Wasmtime 和 WasmEdge 等高效能執行機制中執行那些 Wasm 外掛[2]。
儘管目前有不少應用程式同時使用了二者,但大多數時候這兩個虛擬機器是相互獨立並且沒有交集的:例如在可觀測性應用中,透過 eBPF 探針獲取資料,獲取資料之後在使用者態引入 WASM 外掛模組,進行可配置的資料處理。WASM 模組和 eBPF 程式的分發、執行、載入、控制相互獨立,僅僅存在資料流的關聯。
我們的一次嘗試
一般來說,一個完整的 eBPF 應用程式分為使用者空間程式和核心程式兩部分:
-
使用者空間程式負責載入 BPF 位元組碼至核心,或負責讀取核心回傳的統計資訊或者事件詳情,進行相關的資料處理和控制。
-
核心中的 BPF 位元組碼負責在核心中執行特定事件,如需要也會將執行的結果透過 maps 或者 perf-event 事件傳送至使用者空間。
使用者態程式可以在載入 eBPF 程式前控制一些 eBPF 程式的引數和變數,以及掛載點;也可以透過 map 等等方式進行使用者態和核心態之間的雙向通訊。通常來說使用者態的 eBPF 程式可以基於 libbpf 庫進行開發,來控制核心態 eBPF 程式的裝載和執行。那麼,如果將使用者態的所有控制和資料處理邏輯全部移到 WASM 虛擬機器中,透過 WASM module 打包和分發 eBPF 位元組碼,同時在 WASM 虛擬機器內部控制整個 eBPF 程式的載入和執行,也許我們就可以將二者的優勢結合起來,讓任意 eBPF 程式能有如下特性:
-
可移植:讓 eBPF 工具和應用完全平臺無關、可移植,不需要進行重新編譯即可以跨平臺分發。
-
隔離性:藉助 WASM 的可靠性和隔離性,讓 eBPF 程式的載入和執行、以及使用者態的資料處理流程更加安全可靠;事實上一個 eBPF 應用的使用者態控制程式碼通常遠遠多於核心態。
-
包管理:藉助 WASM 的生態和工具鏈,完成 eBPF 程式或工具的分發、管理、載入等工作,目前 eBPF 程式或工具生態可能缺乏一個通用的包管理或外掛管理系統。
-
跨語言:目前 eBPF 程式由多種使用者態語言開發(如 Go++等),超過 30 種程式語言可以被編譯成 WebAssembly 模組,允許各種背景的開發人員(C、Go、Rust、Java、TypeScript 等)用他們選擇的語言編寫 eBPF 的使用者態程式,而不需要學習新的語言。
-
敏捷性:對於大型的 eBPF 應用程式,可以使用 WASM 作為外掛擴充套件平臺:擴充套件程式可以在執行時直接從控制平面交付和重新載入。這不僅意味著每個人都可以使用官方和未經修改的應用程式來載入自定義擴充套件,而且任何 eBPF 程式的錯誤修復和/或更新都可以在執行時推送和/或測試,而不需要更新和/或重新部署一個新的二進位制。
-
輕量級:WebAssembly 微服務消耗 1% 的資源,與 Linux 容器應用相比,冷啟動的時間是 1%:我們也許可以藉此實現 eBPF as a service,讓 eBPF 程式的載入和執行變得更加輕量級、快速、簡便易行。
eunomia-bpf 是 eBPF 技術探索 SIG [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 會自動從網頁上下載並載入 sigsnoop/app.wasm 這個 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 映象的形式併發布到了 docker hub 上,可以直接開箱即用。此時動態載入執行的只有核心態的 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 程式和資料處理的程式碼,會將根據 signal number 將訊號事件的英文名稱新增到事件中:
最後使用容器映象即可一行命令完成 WebAssembly/eBPF 程式的編譯和打包,使用 ecli 即可一鍵執行:
由於我們基於一次編譯、到處執行的 libbpf 框架完成載入和啟動 eBPF 程式的操作,因此編譯和執行兩個步驟是完全分離的,可以透過網路或任意方式直接進行 eBPF 程式的分發和部署,不依賴於特定核心版本。藉助 WebAssembly 的輕量級特性,eBPF 程式的啟動速度也比通常的使用映象形式分發的 libbpf 程式快上不少,通常只需不到 100 ms 的時間即可完成,比起使用 BCC 部署啟動時,使用 LLVM、Clang 編譯執行消耗的時間和大量資源,更是有了質的飛躍。
上面提及的示例程式的完整程式碼,可以參考這裡[6]。
演示影片
我們也有一個在 B 站上的演示影片,演示瞭如何從 bcc/libbpf-tools 中移植一個 eBPF 工具程式到 eunomia-bpf 中,並且使用 WASM 或 JSON 檔案來分發、載入 eBPF 程式:
我們是如何做到的
ecli 是基於我們底層的 eunomia-bpf 庫和執行時實現的一個簡單的命令列工具。我們的專案架構如下圖所示:
arch
ecli 工具基於 ewasm 庫實現,ewasm 庫包含一個 WAMR(wasm-micro-runtime) 執行時,以及基於 libbpf 庫構建的 eBPF 動態裝載模組。大致來說,我們在 WASM 執行時和使用者態的 libbpf 中間多加了一層抽象層(eunomia-bpf 庫),使得一次編譯、到處執行的 eBPF 程式碼可以從 JSON 物件中動態載入。JSON 物件會在編譯時被包含在 WASM 模組中,因此在執行時,我們可以透過解析 JSON 物件來獲取 eBPF 程式的資訊,然後動態載入 eBPF 程式。
使用 WASM 或 JSON 編譯分發 eBPF 程式的流程圖大致如下:
flow
大致來說,整個 eBPF 程式的編寫和載入分為四個部分:
-
用 eunomia-cc 工具鏈將核心的 eBPF 程式碼骨架和位元組碼編譯為 JSON 格式。
-
在使用者態開發的高階語言(例如 C 語言)中嵌入 JSON 資料,並提供一些 API 用於操作 JSON 形態的 eBPF 程式骨架。
-
將使用者態程式和 JSON 資料一起編譯為 WASM 位元組碼並打包為 WASM 模組,然後在目標機器上載入並執行 WASM 程式。
-
從 WASM 模組中載入內嵌的 JSON 資料,用 eunomia-bpf 庫動態裝載和配置 eBPF 程式骨架。
我們需要完成的僅僅是少量的 native API 和 WASM 執行時的繫結,並且在 WASM 程式碼中處理 JSON 資料。你可以在一個單一的 WASM 模組中擁有多個 eBPF 程式。如果不使用我們提供的 WASM 執行時,或者想要使用其他語言進行使用者態的 eBPF 輔助程式碼的開發,在我們提供的 eunomia-bpf 庫基礎上完成一些 WebaAssembly 的繫結即可。
另外,對於 eunomia-bpf 庫而言,不需要 WASM 模組和執行時同樣可以啟動和動態載入 eBPF 程式,不過此時動態載入執行的就只是核心態的 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 的核心態編譯和執行完全分離的功能,讓使用 Coolbpf API 開發 eBPF 的程式,在遠端編譯一次過後可以在任意核心版本和架構上直接使用,在部署時無需再次連線遠端伺服器;也可以將編譯完成的 eBPF 程式作為 Go、Python、Rust 等語言的開發包直接使用,讓開發者能輕鬆獲得 eBPF 程式上報的資訊,而完全不需要再次進行任何 eBPF 程式的編譯過程。
SIG 社群孵化於高校的 Linux Microscope (LMP) 專案[10]中,也已經有一些基於 eunomia-bpf 提供通用的、規範化、可以隨時下載執行的 eBPF 程式或工具庫的計劃,目前還在繼續完善的階段。
參考資料
參考連結【1】~【10】可移步龍蜥公眾號(OpenAnolis龍蜥)2022年10月13日相同推送檢視。
—— 完 ——
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70004278/viewspace-2918458/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從編譯到可執行,eBPF 加速容器網路的原理分析 | 龍蜥技術編譯eBPF
- eBPF 雙子座:天使 or 惡魔?| 龍蜥技術eBPF
- eBPF SIG年度動態: eBPF和Wasm深度融合、參與7場活動及2023展望 | 龍蜥 SIGeBPFASM
- SysAK 應用抖動診斷篇—— eBPF又立功了! | 龍蜥技術eBPF
- eBPF編寫避坑指南eBPF
- 龍蜥社群聯合浪潮資訊釋出《eBPF技術實踐白皮書》(附下載連結)eBPF
- 用 Rust 編寫 eBPF/XDP 負載均衡器RusteBPF負載
- eBPF 執行原理和流程eBPF
- 【eBPF-01】初見:基於 BCC 框架的第一個 eBPF 程式eBPF框架
- eBPF 和 WebAssembly:哪個雲原生VM更好?eBPFWeb
- 【Learning eBPF-3】一個 eBPF 程式的深入剖析eBPF
- 只需 5 分鐘,教你如何編寫並執行一個 Rust + WebAssembly 程式RustWeb
- 【Learning eBPF-2】eBPF 的“Hello world”eBPF
- ebpf發展簡史eBPF
- 圓滿落幕!回顧 eBPF 技術的發展與挑戰eBPF
- 不容錯過的技術盛宴,4場全是 eBPF 技術乾貨,今天見 | 第 44-47 期eBPF
- eBPF-AntiRootkiteBPF
- 入門即享受!coolbpf 硬核提升 BPF 開發效率 | 龍蜥技術
- eBPF in kubernetes 實戰eBPF
- Netflix 如何使用eBPF流日誌進行網路洞察?eBPF
- 轉載 ebpf sockmap/redirection 提升 socket 效能(2020)eBPF
- 【eBPF-02】入門:基於 BCC 框架的程式進階eBPF框架
- Rust如何開發eBPF應用?(一)RusteBPF
- 跨語言程式設計的探索 | 龍蜥技術程式設計
- 【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?eBPF
- 技術門檻高?來看 Intel 機密計算技術在龍蜥社群的實踐 | 龍蜥技術Intel
- 【Learning eBPF-0】引言eBPF
- 使用 eBPF 零程式碼修改繪製全景應用拓撲eBPF
- 在kubernetes上執行WASM負載ASM負載
- 使用 Rust + WebAssembly 編寫 crc32RustWeb
- 香橙派編譯linux核心支援ebpf和虛擬WIFI編譯LinuxeBPFWiFi
- ebpf-go 初體驗eBPFGo
- eBPF HashMap 與 padding 的坑eBPFHashMappadding
- 全面介紹eBPF-概念eBPF
- 聊聊最近很火的eBPFeBPF
- Kubernetes v1.28.2 & Calico eBPFeBPF
- 用eBPF/XDP來替代LVSeBPF
- Python語言編寫/分投趣系統技術開發程式碼示例Python