BumbleBee: 如絲般順滑構建、交付和運行 eBPF 程式
本文地址://www.ebpf.top/post/bumblebee
1. 前言
不久前,Solo.io 公司在官網部落格宣布了開源了一個名稱為 BumbleBee 的新項目。該項目專註於簡化構建 eBPF 工具的門檻和優化使用體驗,通過將 eBPF 程式打包成 OCI 鏡像,帶來了與使用 Docker 一致的體驗的構建、分發和運行 eBPF 程式。
BumbleBee 目的是讓我們專註於編寫 eBPF 程式碼,其負責自動生成與 eBPF 程式相關的用戶空間的程式碼功能,包括載入 eBPF 程式和將 eBPF 程式的數據作為日誌、指標和直方圖進行展示。
那麼我們為什麼需要 BumbleBee 項目來管理 eBPF 程式呢?這需要從 eBPF 技術的特點講起。
2. 構建和分發 eBPF 工具的挑戰
eBPF 技術被稱之為近 50 年來作業系統最大的變革,解決了 Linux 內核在上游開發、合併和發行功能緩慢的窘境。 eBPF 技術為內核提供了不通過上游實現內核的訂製功能的能力,當前已經在可觀測、網路和安全等多個領域得到了廣泛的應用,尤其是在雲原生的技術潮流中,eBPF 技術發揮的能力也越來越重要,諸如當前風頭正盛的 Cilium 項目。
但是開發、構建和分發 eBPF 一直以來都是一個高門檻的工作,社區先後推出了 BCC、BPFTrace 等前端綁定工作,大大降低了編寫和使用 eBPF 技術的門檻,但是這些工具的源碼交付方式,需要運行 eBPF 程式的 Linux 系統上安裝配套的編譯環境,對於分髮帶來了不少的麻煩,同時將內核適配的問題在運行時才能驗證,也不利於提前發現和解決問題。
近年來,為解決不同內核版本中 eBPF 程式的分發和運行問題,社區基於 BTF 技術推出了 CO-RE 功能(「一次編譯,到處運行」),一定程度上實現了通過 eBPF 二進位位元組碼分發,同時也解決了運行在不同內核上的移植問題,但是對於如何打包和分發 eBPF 二進位程式碼還未有統一簡潔的方式。除了 eBPF 程式,目前我們還需要編寫用於載入 eBPF 程式和用於讀取 eBPF 程式產生數據的各種程式碼,這往往涉及到通過源碼複製粘貼來解決部分問題。
此外,libbpf-bootstrap 通過 bpftool 工具生成相關的腳手架程式碼,一定程度上解決了通用程式碼重複編寫的問題,但是對於構建、分發和運行 eBPF 程式上提供的幫助有限。
3. BumbleBee 簡介
BumbleBee 項目正是 Solo 公司在企業服務網格 Gloo-Mesh 項目中為方便應用 eBPF 技術而誕生中,其用於解決在構建、分發和運行 eBPF 程式遇到的重複性挑戰,
當前該項目還在早期(當前版本 0.0.9),提供的功能場景(Network 和 FileSystem)有限,但是基於特定模板能力來構建 OCI 鏡像的思路,為我們在管理 eBPF 程式方面提供了一種高效簡潔的實現,值得我們關注。
使用 BumbleBee 工具的前置依賴: 運行的 eBPF 的作業系統開啟了 BTF 支援,編寫的 eBPF 程式碼也需要使用 CO-RE 相關函數,關於 CO-RE 相關的技術可以參考這裡。
BumbleBee 提供了與 Docker 一致的體驗感覺。下圖是 Docker 的高層次示意圖,BumbleBee 工具完全參考了這個流程。
3.1 構建
BumbleBee 打造 “恰到好處” 的 eBPF 工具鏈,將 eBPF 程式的構建過程自動化,讓你專註於程式碼本身。 BumbleBee 的 eBPF 程式碼打包成一個 OCI 標準鏡像,這樣就可以在基礎設施中進行分發。
下述命令可實現將 eBPF 程式 probe.c 的直接編譯和打包成鏡像 my_probe:v1 。
$ bee build probe.c username/my_probe:v1
3.2 發布
利用 BTF 和 OCI 打包能力,BumbleBee 編寫的 eBPF 程式碼是可移植的,並且可以嵌入到現有的發布工作流程中。 通過將 eBPF 程式碼構建的鏡像,推送到任何符合 OCI 標準的鏡像倉庫,就可以實現發布給其他用戶使用。
下述命令實現了將鏡像發布至鏡像倉庫的功能,使用時可直接使用 bee run 基於鏡像運行。
# 推送
$ bee push username/my_probe:v1
# 拉取
$ bee pull username/my_probe:v1
3.3 運行
使用 BumbleBee 提供的 CLI 介面和保存在鏡像倉庫中的鏡像,我們可快速在其他地方運行。 BumbleBee 不但構建了用戶空間程式碼,而且可以利用 eBPF map,來展示日誌、指標和柱狀圖資訊。 BumbleBee 使用了 BTF 格式自審能力,獲知到需要顯示哪些數據類型。
$ bee run my_probe:v1
下面讓我們通過一個完整的樣例,來體驗 BumbleBee 帶給我們管理 eBPF 程式的便利。
4. 完整體驗
4.1 bee 安裝
首先我們需要一個運行支援 BTF 內核的 Linux 作業系統,這裡推薦直接使用 ubuntu 2110 版本,搭載的內核已經默認支援了 BTF。如果你選擇使用 Vagrant 來管理虛擬機,BumbleBee 倉庫中提供的 Vagrantfile 文件可以直接使用。或者你可以使用 mulipass 工具直接啟動一個 ubuntu 2110 版本的系統。
這裡使用倉庫提供的腳本安裝,當然也可以直接通過 git clone 倉庫的方式進行。
為了快速體驗,避免某些場景中的許可權問題,這裡建議直接使用 root 用戶進行安裝。
ubuntu@ubuntu21-10:~# curl -sL //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: //github.com/solo-io/bumblebee
安裝完成後,bee 的主要命令如下:
# bee --help
Usage:
bee [command]
Available Commands:
build Build a BPF program, and save it to an OCI image representation.
completion generate the autocompletion script for the specified shell
describe Describe a BPF program via it's OCI ref
help Help about any command
init Initialize a sample BPF program
list
login Log in so you can push images to the remote server.
pull
push
run Run a BPF program file or OCI image.
tag
version
Flags:
-c, --config stringArray path to auth configs
--config-dir string Directory to bumblebee configuration (default "/root/.bumblebee")
-h, --help help for bee
--insecure allow connections to SSL registry without certs
-p, --password string registry password
--plain-http use plain http and not https
--storage string Directory to store OCI images locally (default "/root/.bumblebee/store")
-u, --username string registry username
-v, --verbose verbose output
Use "bee [command] --help" for more information about a command.
4.2 Bee init 生成 eBPF 程式腳手架
Bee init 命令可通過問題嚮導模式生成 eBPF 程式碼腳手架,功能與 libbpf-bootstrap 有些類似,但是通過嚮導的方式進行更加容易上手。
$ 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 程式碼的語言
▸ C # 當前僅支援 C,Rust 可能在未來支援
--------------------------------------------- # 步驟 2 選擇 eBPF 程式類型
INFO Selected Language: C
Use the arrow keys to navigate: ↓ ↑ → ←
? What type of program to initialize:
▸ Network # 選擇編寫 eBPF 程式的類型,當前支援 Network 和 File System
File system # 生成的模板分別對應於 tcp_connet 和 open 函數
--------------------------------------------- # 步驟 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 導出類型
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 數據的展現方式,日誌列印、計數或者指標導出
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
---------------------------------------------- # 最終完成整個程式碼生成嚮導
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
通過 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: //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;
}
基於生成的程式碼模板,我們需要填寫自己的邏輯,這裡不是重點,先略過相關程式碼,完整程式碼可在官方開始文檔中查看。
4.3 構建 eBPF 程式
構建過程需要使用 Docker 或者類型 Docker 的容器引擎,需要提前進行安裝。
# 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
整個構建過程中我們不需要再涉及 clang 等相關編譯命令,只需要通過 bee build 命令輸入 eBPF 程式文件名和期望生成的鏡像即可,編譯完成後,eBPF 程式的二進位位元組碼 probe.o 會自動添加到鏡像 my_probe:v1 中,我們可以使用 bee tag 完成鏡像倉庫的重新定義。
4.4 發布 eBPF 程式
我們可以通過 bee tag 和 push 子命令完成進行鏡像倉庫的發布工作。
# bee tag my_probe:v1 dwh0403/my_probe:v1
# bee login
# bee push dwh0403/my_probe:v1
看一下上述的幾條命令,是不是有些似曾相識的感覺?
4.5 運行 eBPF 程式
構建鏡像後,在本地可直接通過 bee run 來運行,運行後 bee 會自動啟動 TUI 介面,來展示我們編寫 eBPF 程式中的 map 內容,自動生成的 map 名字有些特殊後綴用於 bee TUI 用戶空間的程式來讀取對應 map 中數據進行展示,比如生成程式碼模板中的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. 總結
至此,我們完成了整個項目功能的體驗,bee init 工具可通過嚮導模式幫助我們生成 eBPF 程式碼框架,儘管功能還有些單薄,但是對於我們特定場景的使用不失是一種快速便捷的方式。
bee build/push/run 等子命令,將編譯的命令、打包鏡像、發布鏡像和運行鏡像的等諸多步驟進行了極大的精簡,非常易用,極大地降低了構建、發布和運行 eBPF 程式的重複成本,不得不為作者的思路點贊。
由於通過 bee 生成的工具基於特定場景,功能豐富度還有限,對於編寫複雜情況下的 eBPF 程式和功能豐富的用戶空間程式還不能適用,但是其構建、發布和運行的整體思路(甚至部分基礎功能)卻是我們可以直接使用或者借鑒的。
6. 參考資料
- Tutorial
- Solo.io 開源 BumbleBee,用類 Docker 的體驗使用 eBPF
- BumbleBee: Build, Ship, Run eBPF tools
- eCHO episode 33: Bumblebee