BumbleBee: 如絲般順滑構(gòu)建、交付和運(yùn)行 eBPF 程序

1. 前言
幾天前,Solo.io[1] 公司在官網(wǎng)博客宣布了開(kāi)源了一個(gè)名稱為 BumbleBee[2] 的新項(xiàng)目。該項(xiàng)目專注于簡(jiǎn)化構(gòu)建 eBPF 工具的門檻和優(yōu)化使用體驗(yàn),通過(guò)將 eBPF 程序打包成 OCI 鏡像[3],帶來(lái)了與使用 Docker 一致的體驗(yàn)的構(gòu)建、分發(fā)和運(yùn)行 eBPF 程序。
BumbleBee 目的是讓我們專注于編寫 eBPF 代碼,其負(fù)責(zé)自動(dòng)生成與 eBPF 程序相關(guān)的用戶空間的代碼功能,包括加載 eBPF 程序和將 eBPF 程序的數(shù)據(jù)作為日志、指標(biāo)和直方圖進(jìn)行展示。
那么我們?yōu)槭裁葱枰?BumbleBee 項(xiàng)目來(lái)管理 eBPF 程序呢?這需要從 eBPF 技術(shù)的特點(diǎn)講起。
2. 構(gòu)建和分發(fā) eBPF 工具的挑戰(zhàn)
eBPF 技術(shù)被稱之為近 50 年來(lái)操作系統(tǒng)最大的變革,解決了 Linux 內(nèi)核在上游開(kāi)發(fā)、合并和發(fā)行功能緩慢的窘境。eBPF 技術(shù)為內(nèi)核提供了不通過(guò)上游實(shí)現(xiàn)內(nèi)核的定制功能的能力,當(dāng)前已經(jīng)在可觀測(cè)、網(wǎng)絡(luò)和安全等多個(gè)領(lǐng)域得到了廣泛的應(yīng)用,尤其是在云原生的技術(shù)潮流中,eBPF 技術(shù)發(fā)揮的能力也越來(lái)越重要,諸如當(dāng)前風(fēng)頭正盛的 Cilium 項(xiàng)目。
但是開(kāi)發(fā)、構(gòu)建和分發(fā) eBPF 一直以來(lái)都是一個(gè)高門檻的工作,社區(qū)先后推出了 BCC、BPFTrace 等前端綁定工作,大大降低了編寫和使用 eBPF 技術(shù)的門檻,但是這些工具的源碼交付方式,需要運(yùn)行 eBPF 程序的 Linux 系統(tǒng)上安裝配套的編譯環(huán)境,對(duì)于分發(fā)帶來(lái)了不少的麻煩,同時(shí)將內(nèi)核適配的問(wèn)題在運(yùn)行時(shí)才能驗(yàn)證,也不利于提前發(fā)現(xiàn)和解決問(wèn)題。
近年來(lái),為解決不同內(nèi)核版本中 eBPF 程序的分發(fā)和運(yùn)行問(wèn)題,社區(qū)基于 BTF 技術(shù)推出了 CO-RE 功能(“一次編譯,到處運(yùn)行”),一定程度上實(shí)現(xiàn)了通過(guò) eBPF 二進(jìn)制字節(jié)碼分發(fā),同時(shí)也解決了運(yùn)行在不同內(nèi)核上的移植問(wèn)題,但是對(duì)于如何打包和分發(fā) eBPF 二進(jìn)制代碼還未有統(tǒng)一簡(jiǎn)潔的方式。除了 eBPF 程序,目前我們還需要編寫用于加載 eBPF 程序和用于讀取 eBPF 程序產(chǎn)生數(shù)據(jù)的各種代碼,這往往涉及到通過(guò)源碼復(fù)制粘貼來(lái)解決部分問(wèn)題。
此外,libbpf-bootstrap[4] 通過(guò) bpftool 工具生成相關(guān)的腳手架代碼,一定程度上解決了通用代碼重復(fù)編寫的問(wèn)題,但是對(duì)于構(gòu)建、分發(fā)和運(yùn)行 eBPF 程序上提供的幫助有限。
3. BumbleBee 簡(jiǎn)介
BumbleBee 項(xiàng)目正是 Solo 公司在企業(yè)服務(wù)網(wǎng)格 Gloo-Mesh[5] 項(xiàng)目中為方便應(yīng)用 eBPF 技術(shù)而誕生中,其用于解決在構(gòu)建、分發(fā)和運(yùn)行 eBPF 程序遇到的重復(fù)性挑戰(zhàn),
當(dāng)前該項(xiàng)目還在早期(當(dāng)前版本 0.0.9),提供的功能場(chǎng)景(Network 和 FileSystem)有限,但是基于特定模板能力來(lái)構(gòu)建 OCI 鏡像的思路,為我們?cè)诠芾?eBPF 程序方面提供了一種高效簡(jiǎn)潔的實(shí)現(xiàn),值得我們關(guān)注。
使用 BumbleBee 工具的前置依賴:運(yùn)行的 eBPF 的操作系統(tǒng)開(kāi)啟了 BTF 支持,編寫的 eBPF 代碼也需要使用 CO-RE 相關(guān)函數(shù),關(guān)于 CO-RE 相關(guān)的技術(shù)可以參考這里[6]。
BumbleBee 提供了與 Docker 一致的體驗(yàn)感覺(jué)。下圖是 Docker 的高層次示意圖,BumbleBee 工具完全參考了這個(gè)流程。

3.1 構(gòu)建
BumbleBee 打造 "恰到好處" 的 eBPF 工具鏈,將 eBPF 程序的構(gòu)建過(guò)程自動(dòng)化,讓你專注于代碼本身。BumbleBee 的 eBPF 代碼打包成一個(gè) OCI 標(biāo)準(zhǔn)鏡像,這樣就可以在基礎(chǔ)設(shè)施中進(jìn)行分發(fā)。
下述命令可實(shí)現(xiàn)將 eBPF 程序 probe.c 的直接編譯和打包成鏡像 my_probe:v1 。
$?bee?build?probe.c?username/my_probe:v1
3.2 發(fā)布
利用 BTF 和 OCI 打包能力,BumbleBee 編寫的 eBPF 代碼是可移植的,并且可以嵌入到現(xiàn)有的發(fā)布工作流程中。通過(guò)將 eBPF 代碼構(gòu)建的鏡像,推送到任何符合 OCI 標(biāo)準(zhǔn)的鏡像倉(cāng)庫(kù),就可以實(shí)現(xiàn)發(fā)布給其他用戶使用。
下述命令實(shí)現(xiàn)了將鏡像發(fā)布至鏡像倉(cāng)庫(kù)的功能,使用時(shí)可直接使用 bee run 基于鏡像運(yùn)行。
#?推送
$?bee?push?username/my_probe:v1
#?拉取
$?bee?pull?username/my_probe:v1
3.3 運(yùn)行
使用 BumbleBee 提供的 CLI 界面和保存在鏡像倉(cāng)庫(kù)中的鏡像,我們可快速在其他地方運(yùn)行。BumbleBee 不但構(gòu)建了用戶空間代碼,而且可以利用 eBPF map,來(lái)展示日志、指標(biāo)和柱狀圖信息。BumbleBee 使用了 BTF 格式自審能力,獲知到需要顯示哪些數(shù)據(jù)類型。
$?bee?run?my_probe:v1
下面讓我們通過(guò)一個(gè)完整的樣例,來(lái)體驗(yàn) BumbleBee 帶給我們管理 eBPF 程序的便利。
4. 完整體驗(yàn)
4.1 bee 安裝
首先我們需要一個(gè)運(yùn)行支持 BTF 內(nèi)核的 Linux 操作系統(tǒng),這里推薦直接使用 ubuntu 2110 版本,搭載的內(nèi)核已經(jīng)默認(rèn)支持了 BTF。如果你選擇使用 Vagrant 來(lái)管理虛擬機(jī),BumbleBee 倉(cāng)庫(kù)中提供的 Vagrantfile[7] 文件可以直接使用。或者你可以使用 mulipass[8] 工具直接啟動(dòng)一個(gè) ubuntu 2110 版本的系統(tǒng)。
這里使用倉(cāng)庫(kù)提供的腳本安裝,當(dāng)然也可以直接通過(guò) git clone 倉(cāng)庫(kù)的方式進(jìn)行。
為了快速體驗(yàn),避免某些場(chǎng)景中的權(quán)限問(wèn)題,這里建議直接使用 root 用戶進(jìn)行安裝。
ubuntu@ubuntu21-10:~#?curl?-sL?https://run.solo.io/bee/install?|?BUMBLEBEE_VERSION=v0.0.9?sh
Attempting?to?download?bee?version?v0.0.9
Downloading?bee-linux-amd64...
Download?complete!,?validating?checksum...
Checksum?valid.
bee?was?successfully?installed???
Add?the?bumblebee?CLI?to?your?path?with:
??export?PATH=$HOME/.bumblebee/bin:$PATH
Now?run:
??bee?init?????#?Initialize?simple?eBPF?program?to?run?with?bee
Please?see?visit?the?bumblebee?website?for?more?info:??https://github.com/solo-io/bumblebee
4.2 Bee init 生成 eBPF 程序腳手架
Bee init 命令可通過(guò)問(wèn)題向?qū)J缴?eBPF 代碼腳手架,功能與 libbpf-bootstrap 有些類似,但是通過(guò)向?qū)У姆绞竭M(jìn)行更加容易上手。
$?export?PATH=$HOME/.bumblebee/bin:$PATH
#?ebpf-test?&&?cd?ebpf-test
#?bee?init
Use?the?arrow?keys?to?navigate:?↓?↑?→?←
??What?language?do?you?wish?to?use?for?the?filter:?#?步驟?選擇編寫?eBPF?代碼的語(yǔ)言
????C???#?當(dāng)前僅支持?C,Rust?可能在未來(lái)支持
---------------------------------------------??#?步驟?2?選擇?eBPF?程序類型
?INFO??Selected?Language:?C
Use?the?arrow?keys?to?navigate:?↓?↑?→?←
??What?type?of?program?to?initialize:
????Network????????#?選擇編寫?eBPF?程序的類型,當(dāng)前支持?Network?和?File?System
????File?system????#?生成的模板分別對(duì)應(yīng)于?tcp_connet?和?open?函數(shù)
---------------------------------------------?#?步驟?3??選擇?map?類型
?INFO??Selected?Language:?C
?INFO??Selected?Program?Type:?Network
Use?the?arrow?keys?to?navigate:?↓?↑?→?←
??What?type?of?map?should?we?initialize:
????RingBuffer
????HashMap
?---------------------------------------------?#?步驟?4?選擇?map?導(dǎo)出類型
?INFO??Selected?Language:?C
?INFO??Selected?Program?Type:?Network
?INFO??Selected?Map?Type:?HashMap
Use?the?arrow?keys?to?navigate:?↓?↑?→?←
??What?type?of?output?would?you?like?from?your?map:
????print????#?map?數(shù)據(jù)的展現(xiàn)方式,日志打印、計(jì)數(shù)或者指標(biāo)導(dǎo)出
????counter
????gauge
?---------------------------------------------??#?步驟?5?eBPF?程序保存文件名
?INFO??Selected?Language:?C
?INFO??Selected?Program?Type:?Network
?INFO??Selected?Map?Type:?HashMap
?INFO??Selected?Output?Type:?print
??BPF?Program?File?Location:?probe.c
?----------------------------------------------?#?最終完成整個(gè)代碼生成向?qū)?/span>
?INFO??Selected?Language:?C
?INFO??Selected?Program?Type:?Network
?INFO??Selected?Map?Type:?HashMap
?INFO??Selected?Output?Type:?print
?INFO??Selected?Output?Type:?BPF?Program?File?Location?probe.c
?SUCCESS??Successfully?wrote?skeleton?BPF?program
#?ls?-hl
total?4.0K
-rw-rw-r--?1?ubuntu?ubuntu?2.0K?Feb?11?11:33?probe.c
通過(guò) init 命令生成的 probe.c 文件格式大體如下:
#include?"vmlinux.h"
#include?"bpf/bpf_helpers.h"
#include?"bpf/bpf_core_read.h"
#include?"bpf/bpf_tracing.h"
#include?"solo_types.h"
//?1.?Change?the?license?if?necessary
char?__license[]?SEC("license")?=?"Dual?MIT/GPL";
struct?event_t?{
?//?2.?Add?ringbuf?struct?data?here.
}?__attribute__((packed));
//?This?is?the?definition?for?the?global?map?which?both?our
//?bpf?program?and?user?space?program?can?access.
//?More?info?and?map?types?can?be?found?here:?https://www.man7.org/linux/man-pages/man2/bpf.2.html
struct?{
?__uint(max_entries,?1?<24);
?__uint(type,?BPF_MAP_TYPE_RINGBUF);
?__type(value,?struct?event_t);
}?events?SEC(".maps.print");
SEC("kprobe/tcp_v4_connect")
int?BPF_KPROBE(tcp_v4_connect,?struct?sock?*sk)
{
?//?Init?event?pointer
?struct?event_t?*event;
?//?Reserve?a?spot?in?the?ringbuffer?for?our?event
?event?=?bpf_ringbuf_reserve(&events,?sizeof(struct?event_t),?0);
?if?(!event)?{
??return?0;
?}
?//?3.?set?data?for?our?event,
?//?For?example:
?//?event->pid?=?bpf_get_current_pid_tgid();
?bpf_ringbuf_submit(event,?0);
?return?0;
}
基于生成的代碼模板,我們需要填寫自己的邏輯,這里不是重點(diǎn),先略過(guò)相關(guān)代碼,完整代碼可在官方開(kāi)始文檔[9]中查看。
4.3 構(gòu)建 eBPF 程序
構(gòu)建過(guò)程需要使用 Docker 或者類型 Docker 的容器引擎,需要提前進(jìn)行安裝。
#?apt??install?docker.io??#?安裝?docker
#?bee?build?probe.c?my_probe:v1
?SUCCESS??Successfully?compiled?"probe.c"?and?wrote?it?to?"probe.o"
?SUCCESS??Saved?BPF?OCI?image?to?my_probe:v1
整個(gè)構(gòu)建過(guò)程中我們不需要再涉及 clang 等相關(guān)編譯命令,只需要通過(guò) bee build 命令輸入 eBPF 程序文件名和期望生成的鏡像即可,編譯完成后,eBPF 程序的二進(jìn)制字節(jié)碼 probe.o 會(huì)自動(dòng)添加到鏡像 my_probe:v1 中,我們可以使用 bee tag 完成鏡像倉(cāng)庫(kù)的重新定義。
4.4 發(fā)布 eBPF 程序
我們可以通過(guò) bee tag 和 push 子命令完成進(jìn)行鏡像倉(cāng)庫(kù)的發(fā)布工作。
#?bee?tag?my_probe:v1?dwh0403/my_probe:v1
#?bee?login
#?bee?push?dwh0403/my_probe:v1
看一下上述的幾條命令,是不是有些似曾相識(shí)的感覺(jué)?
4.5 運(yùn)行 eBPF 程序
構(gòu)建鏡像后,在本地可直接通過(guò) bee run 來(lái)運(yùn)行,運(yùn)行后 bee 會(huì)自動(dòng)啟動(dòng) TUI 界面,來(lái)展示我們編寫 eBPF 程序中的 map 內(nèi)容,自動(dòng)生成的 map 名字有些特殊后綴用于 bee TUI 用戶空間的程序來(lái)讀取對(duì)應(yīng) map 中數(shù)據(jù)進(jìn)行展示,比如生成代碼模板中的SEC(".maps.print"),表示該 map 用于打印。
#?bee?run?my_probe:v1
?SUCCESS??Fetching?program?from?registry:?my_probe:v1
?SUCCESS??Loading?BPF?program?and?maps?into?Kernel
?SUCCESS??Linking?BPF?functions?to?associated?probe/tracepoint
?INFO??Rendering?TUI..

5. 總結(jié)
至此,我們完成了整個(gè)項(xiàng)目功能的體驗(yàn),bee init 工具可通過(guò)向?qū)J綆椭覀兩?eBPF 代碼框架,盡管功能還有些單薄,但是對(duì)于我們特定場(chǎng)景的使用不失是一種快速便捷的方式。
bee build/push/run 等子命令,將編譯的命令、打包鏡像、發(fā)布鏡像和運(yùn)行鏡像的等諸多步驟進(jìn)行了極大的精簡(jiǎn),非常易用,極大地降低了構(gòu)建、發(fā)布和運(yùn)行 eBPF 程序的重復(fù)成本,不得不為作者的思路點(diǎn)贊。
由于通過(guò) bee 生成的工具基于特定場(chǎng)景,功能豐富度還有限,對(duì)于編寫復(fù)雜情況下的 eBPF 程序和功能豐富的用戶空間程序還不能適用,但是其構(gòu)建、發(fā)布和運(yùn)行的整體思路(甚至部分基礎(chǔ)功能)卻是我們可以直接使用或者借鑒的。
6. 相關(guān)資料
Tutorial[10] Solo.io 開(kāi)源 BumbleBee,用類 Docker 的體驗(yàn)使用 eBPF[11] BumbleBee: Build, Ship, Run eBPF tools[12] eCHO episode 33: Bumblebee[13]
參考資料
Solo.io: https://www.solo.io/
[2]BumbleBee: https://bumblebee.io/
[3]OCI 鏡像: https://github.com/opencontainers/image-spec
[4]libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
[5]企業(yè)服務(wù)網(wǎng)格 Gloo-Mesh: https://www.solo.io/products/gloo-mesh
[6]這里: https://www.ebpf.top/categories/BPF-CORE/
[7]Vagrantfile: https://github.com/solo-io/bumblebee/blob/main/Vagrantfile
[8]mulipass: https://multipass.run/
[9]開(kāi)始文檔: https://github.com/solo-io/bumblebee/blob/main/docs/getting_started.md
[10]Tutorial: https://github.com/solo-io/bumblebee/blob/main/docs/getting_started.md
[11]Solo.io 開(kāi)源 BumbleBee,用類 Docker 的體驗(yàn)使用 eBPF: https://www.infoq.cn/news/GqMEBjGZZX4G8mcfwSM3
[12]BumbleBee: Build, Ship, Run eBPF tools: https://www.solo.io/blog/solo-announces-bumblebee/
[13]eCHO episode 33: Bumblebee: https://www.youtube.com/watch?v=AYLpK5zHzCo
