eBPF 技術報告(中)

本文主要內容來自於 《What is eBPF?》開源電子書,英文版本可參見 https://isovalent.com/ebpf

上篇參見這裏:eBPF 技術報告(上),本篇主要介紹了 3. eBPF 程序以及 4. eBPF 程序自身編寫的一些複雜性

3. eBPF 程序介紹

在本章中,我們會介紹編寫 eBPF 代碼所涉及的內容,包括內核中運行的 eBPF 程序,及與之交互的用戶空間代碼。

3.1 內核和用戶空間代碼

首先需要考慮的是,你可以使用哪些編程語言來編寫 eBPF 程序?eBPF 本質是字節碼形式加載到內核的,字節碼本身沒有任何平臺依賴,手動使用類似彙編的方式編寫字節碼是可能的。但是考慮易用性和效率,我們通常使用高級語言編寫,藉助編譯器將其編譯成 eBPF 字節碼。

現階段 eBPF 程序不能用任意高級語言編寫,當前可選擇的是 C 和 Rust。這是因爲將高級語言編譯成 eBPF 字節碼需要編譯器的支持,另外一些語言自身也不適合編寫內核中的程序,比如 Go/Java 等語言的內存管理機制(垃圾回收)。目前 eBPF 程序的編譯還是以 C 語言爲主。

用戶空間的程序主要負責加載 eBPF 字節碼至內核,同時還可能負責讀取內核中 eBPF 程序發送的數據和寫入用戶空間的配置數據至 eBPF 內核中使用的 map 結構中。

eBPF 工具的用戶空間部分至少在理論上可以用任何語言編寫,目前具有對應庫支持語言有:C、Go、Rust 和 Python。用戶空間程序語言的選擇更加複雜,因爲並非所有語言都有支持 libbpf 的庫,而 libbpf 庫已成爲使 eBPF 程序在不同內核版本之間可移植的主流選擇。

當前 Go 編寫的 eBPF 用戶空間程序的庫,主要有兩種實現:基於 cgo 使用 libbpf 和純 Go 語言實現。

Java 能不能用於編寫用戶空間的程序?答案當然是可以,但由於用戶空間依賴一些底層的系統調用來完成數據交互,你可能需要使用 Java 基於底層調用庫(通常是 C)進行封裝。由於 Python 與 C 語言庫集成的方便性,所以 BCC 選擇了 Python 作爲前端綁定。

3.2 編寫程序附着到事件

eBPF 程序本身通常用 C 或 Rust 編寫並被編譯成目標文件。目標文件是一個標準的 ELF(可執行和可鏈接格式)文件,可使用 readelf 等工具進行檢查,它包含程序字節碼和 map 數據結構的定義,當然啓用了 BTF 也包括相關的信息。如圖 3-1 所示,如果 eBPF 程序被在前面章節遇到的驗證器所允許,用戶空間程序會讀取此文件並將其加載到內核中。

fig3-1.png

圖 3-1 用戶空間的應用程序使用 bpf() 系統調用,從 ELF 文件加載 eBPF 程序到內核

eBPF 程序加載到內核中,就必須附着到一個事件上。每當事件發生時,相關的 eBPF 程序就會被觸發執行。以下是我們常見的 eBPF 可附着的事件:

進入或退出函數

你可以附着一個 eBPF 程序,在進入或退出內核函數時觸發。當前許多 eBPF 示例都使用 kprobes(附着到內核函數入口點)和 kretprobes(函數退出)的機制。在最近的內核版本中,有一個更有效的替代方案,稱爲 fentry/fexit

fexit 機制觸發的 eBPF 程序中可同時獲取到入口參數和返回值,而 kretprobes 則只能獲取到程序的返回值,需要額外的 map 保存函數入參(比如通過 tid 進行關聯)。

kprobes/kretprobes 這種跟蹤機制一般被稱作動態跟蹤,基於跟蹤函數的簽名(包括函數名、參數以及參數對應的定義),其可能隨着內核版本的演進被調整或者被移除。儘管動態機制可跟蹤的函數數量巨大,但是可能會導致 eBPF 失去了多內核版本的移植性。但是基於 syscall 是相對穩定 ABI 接口。

另外也可以通過 uprobesuretprobes 將 eBPF 程序附着到用戶空間函數,這種機制需要具體的程序或庫作爲路徑,同時由於 eBPF 程序是運行在內核中,使用 uprobes/uretprobes 機制,還面臨通過 int3 中斷機制從用戶空間到內核空間的切換,但是性能開銷基本可忽略。

圖片來自於 Pixie[1]

Tracepoint

也可以將 eBPF 附着到內核中 tracepoint。/sys/kernel/debug/tracing/events 文件是系統上支持的事件列表。

Perf 事件

Perf 是一個用於收集性能數據的子系統。你可以將 eBPF 程序掛接到所有收集 perf 數據的地方,可運行 perf list 來查看。

Linux 安全模塊接口(Linux Security Module Interface)

LSM 接口允許在內核允許某些操作之前檢查安全策略。使用 eBPF,你可以將自定義程序附着到相同的檢查點,從而實現靈活、動態的安全策略和運行時安全工具的一些新方法。

網絡接口 XDP(eXpress Data Path)

XDP 允許將 eBPF 程序附着到網絡接口,每當收到數據包就會觸發 eBPF 執行。XDP 可以檢查甚至修改數據包,eBPF 程序的退出代碼用於通知內核如何處理該數據包:傳遞、丟棄或重定向。這可以構成一些非常有效的網絡功能的基礎。

套接字和其他網絡 Hook

你也可附着 eBPF 程序以在程序打開在網絡套接字上執行操作、以及在發送或接收消息時運行 eBPF 程序。在內核的網絡棧中還有稱爲 traffic controltc 的 Hook,eBPF 程序可以在初始數據包處理後運行。

有些功能可以單獨使用 eBPF 程序來實現,但在許多情況下,我們希望 eBPF 代碼從用戶空間應用程序接收信息或將數據傳遞給用戶空間應用程序。允許數據在 eBPF 程序和用戶空間之間或不同 eBPF 程序之間傳遞的機制稱爲 map。

3.3 eBPF Map

Map 功能的添加是 eBPF 首字母縮寫詞中 e 代表_擴展_的顯著標志之一,這是因爲 map 機制的引入將 eBPF 從純粹的內核運行擴展到了用戶空間,而且 map 可以實現內核空間與 eBPF 程序的雙向數據通信,這爲 eBPF 功能的擴展提供了簡單可行的機制。

Map 是 eBPF 程序定義的數據結構。Map 有多種不同類型的,但本質上都是鍵值存儲。eBPF 程序及用戶空間代碼可以讀取和寫入數據。Map 的常見用途包括:

由於內核中的 eBPF 程序與用戶空間代碼通過 map 通信,那麼如果 eBPF 程序與用戶空間代碼採用不同語言編寫的時候,就需要注意 map 數據結構共識的問題,這本質是與我們常見的服務端和客戶端代碼協議約定一致。

我們已經討論了 eBPF 工具的主要組成部分:在內核中運行的 eBPF 程序、加載程序並與之交互的用戶空間代碼,以及允許程序共享數據的 map。爲了更好理解,我們通過一個樣例進行演示。

3.4 Opensnoop 樣例

Opensnoop 工具可用於顯示進程打開的文件詳情,原始版本是 Brendan Gregg 最初在 BCC 項目中使用 Python 編寫的。當前,爲了更加輕量分發和跨內核版本可移植性,該工具基於 libbpf 進行了重寫,本例子中,作者展示的是 libbpf-tools 目錄下的較新版本,需要特別注意。

當運行 opensnoop 效果大體如下:

PID    COMM FD ERR PATH
93965  cat     3   0 /etc/ld.so.cache
93965  cat     3   0 /lib/x86_64-linux-gnu/libc.so.6
93965  cat     3   0 /usr/lib/locale/locale-archive
93965  cat     3   0 /usr/share/locale/locale.alias
...

每行輸出表明一個進程打開(或試圖打開)一個文件。這些列顯示進程 ID、正在運行的命令、文件描述符、任何錯誤代碼以及正在打開的文件的路徑。

Opensnoop 通過將 eBPF 程序附着到 open() 和 openat() 系統調用上工作,任何應用程序都必須進行這些調用以請求內核打開文件。

Opensnoop eBPF 代碼

eBPF 代碼是用 C 語言編寫的,位於文件 opensnoop.bpf.c[2] 中。在該文件頭部,你可以看到兩個 eBPF map 的定義 start 和 event(通過支持 BTF 方式定義):

    struct {
        __uint(type, BPF_MAP_TYPE_HASH);
        __uint(max_entries, 10240);
        __type(key, u32);
        __type(value, struct args_t);
    } start SEC(".maps");
    struct {
        __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
        __uint(key_size, sizeof(u32));
        __uint(value_size, sizeof(u32));
    } events SEC(".maps");

當創建 ELF 目標文件時,它包含每個 map 和要加載到內核中的每個程序的區塊(section),通過 SEC() 宏定義。

start map 用於在處理系統調用時臨時存儲系統調用的參數(包括打開文件的名稱)。events map 用於將事件信息從內核中的 eBPF 代碼傳遞到用戶空間可執行文件,如圖 3-2 所示。

圖 3-2 調用 open() 會觸發 eBPF 程序,然後將數據保存在 opensnoop 的 eBPF map 中

open 與 openat 處理的類似,這裏只展示 open 相關的內容:

SEC("tracepoint/syscalls/sys_enter_open")
int tracepoint__syscalls__sys_enter_open(struct trace_event_raw_sys_enter* ctx)
{
 u64 id = bpf_get_current_pid_tgid();
 /* use kernel terminology here for tgid/pid: */
 u32 tgid = id >> 32;
 u32 pid = id;

 /* store arg info for later lookup */
 if (trace_allowed(tgid, pid)) {
  struct args_t args = {};
  args.fname = (const char *)ctx->args[0];
  args.flags = (int)ctx->args[1];
  bpf_map_update_elem(&start, &pid, &args, 0);
 }
 return 0;
}

如你所見,函數採用指向 trace_event_raw_sys_enter 的結構的指針參數,該結構定義可在內核生成的 vmlinux 頭文件中找到。

函數使用 BPF 輔助函數來獲取到請求系統調用的進程的 ID:

    u64 id = bpf_get_current_pid_tgid();

後續代碼得到了文件名和傳遞給系統調用的標誌,並把它們放在一個叫做 args 的結構中:

    args.fname = (const char *)ctx->args[0];
             args.flags = (int)ctx->args[1];

該結構使用當前程序 ID 作爲 key 保存到 start map 中。

    bpf_map_update_elem(&start, &pid, &args, 0);

這就是 eBPF 程序在進入系統調用時所做的全部。但是在 opensnoop.bpf.c 中定義的另一對 eBPF 程序會在系統調用退出時觸發:

    SEC("tracepoint/syscalls/sys_exit_open")
    int tracepoint__syscalls__sys_exit_open

你有沒有注意到 eBPF 程序調用的所有函數都以靜態 always_inline 爲前綴?這指示編譯器將函數的指令內聯,這是因爲在舊版本內核中,不允許 BPF 程序跳轉到單獨的函數。較新的內核和 LLVM 版本中已經可以支持非內聯函數調用,但是設置 always_inline 可確保 BPF 驗證器保能夠更好工作。(現在還有 BPF 尾調用的概念,即執行從一個 BPF 程序跳轉到另一個。)

在 trace_exit() 函數中創建一個空的事件結構:

    struct event event = {};

這將填充有關即將結束的 open/openat 系統調用的信息,並通過 event map 發送到用戶空間。

在開始的 start hash_map 中應該有一個對應於當前進程 ID 的條目:

    ap = bpf_map_lookup_elem(&start, &pid);

這包含有關在 sys_enter_open(at) 調用期間先前寫入的文件名和 flags 的信息。flags 字段是一個直接存儲在結構體中的整數,所以直接從結構體中讀取就可以了:

    event.flags = ap->flags;

相反,文件名被寫入用戶空間內存中的一些字節數,驗證器需要確保此 eBPF 程序從內存中的該位置讀取該字節數是安全的。這是使用另一個輔助函數 bpf_probe_read_user_str() 完成的:

    bpf_probe_read_user_str(&event.fname, sizeof(event.fname),
                        ap->fname);

當前的命令行名稱(即進行 open(at) 系統調用的可執行文件的名稱)也被複制到事件結構中,使用另一個 BPF 輔助函數:

 bpf_get_current_comm(&event.comm, sizeof(event.comm));

event 結構被寫入事件 perf 緩衝區 map :

    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
                        &event, sizeof(event));

用戶空間代碼從該 map 中讀取事件信息。在我們討論這個問題之前,讓我們簡要地看一下 Makefile。

libbpf-tools Makefile (不關心可以跳過)

當構建 eBPF 代碼時,你會得到一個包含 eBPF 程序和 map 的二進制定義的目標文件。你還需要額外的一個用戶空間可執行文件,它將這些程序和 map 加載到內核中,並充當用戶接口。讓我們看看構建 opensnoop 的 Makefile,是如何創建 eBPF 目標文件和可執行文件的。

我們正在查看的 opensnoop 示例是諸多樣例工具之一,樣例工具共同使用一個 Makefile 構建,你可以在 libbpf-tools 目錄中找到該 Makefile。此文件中的所有內容並非都特別令人感興趣,但我想強調一些規則。第一個是使用 bpf.c 文件並使用 clang 編譯器創建 BPF 目標對象文件的規則:

$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(AR.. $(call msg,BPF,$@)
$(Q)$(CLANG) $(CFLAGS) -target bpf -D__TARGET_ARCH_$(ARCH) \
-I$(ARCH)$(INCLUDES) -c $(filter %.c,$^) -o $@ && \
    $(LLVM_STRIP) -g $@

因此,opensnoop.bpf.c 被編譯成 $(OUTPUT)/open snoop.bpf.o。這個對象文件包含將被載入內核的 eBPF 程序和 map。

另一條規則使用 bpftool gen skeleton 從 bpf.o 對象文件中包含的映射和程序定義創建一個腳手架頭文件。

因此,opensnoop.bpf.c 被編譯成 $(OUTPUT)/open snoop.bpf.o。該目標文件包含將加載到內核中的 eBPF 程序和 map。

另一個規則使用 bpftool gen 腳手架從該 bpf.o 目標文件中包含的 map 和程序定義創建腳手架頭文件:

$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton $< > $@

opensnoop.c 用戶空間代碼包含這個 opensnoop.skel.h 頭文件,以獲取它與內核中的 eBPF 程序共享的 map 的定義。這允許用戶空間和內核代碼瞭解存儲在這些 map 中的數據結構的佈局。

以下規則將用戶空間代碼從 opensnoop.c 編譯成一個名爲 $(OUTPUT)/opensnoop.o 的二進制對象:

$(OUTPUT)/%.o: %.c $(wildcard %.h) $(LIBBPF_OBJ) | $(OUTPUT) $(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@

最後,有一個規則使用 cc 將用戶空間應用程序對象(在我們的例子中,opensnoop.o)鏈接到一組可執行文件中:

$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) $(COMMON_OBJ) | $(OUT... $(call msg,BINARY,$@)
$(Q)(CC) $(CFLAGS) $^ $(LDFLAGS) -lelf -lz -o $@

現在你已經瞭解瞭如何分別生成 eBPF 和用戶空間程序,接着讓我們看看對應的用戶空間代碼。

Opensnoop 用戶空間代碼

用戶空間代碼在 opensnoop.c 文件中。文件的前半部分有 #include 指令(其中一個是自動生成的 opensnoop.skel.h 文件)、各種定義以及處理不同命令行選項的代碼,我們不會在這裏詳述。讓我們也略過諸如 print_event() 之類的函數,它將有關事件的信息寫入屏幕。從 eBPF 的角度來看,所有有趣的代碼都在 main() 函數中。

你將看到諸如 opensnoop__bpf_open()open snoop__bpf_load()opensnoop__bpf_attach() 之類的函數。這些都是在 bpftool gen 腳手架創建的自動生成代碼中定義的。自動生成的代碼處理在 eBPF 目標文件中定義的所有單獨的 eBPF 程序、map 和附着點。

一旦 opensnoop 啓動並運行,其工作就是監聽事件 perf 緩衝區並將事件中包含的信息寫入屏幕。首先,它打開與 perf 緩衝區關聯的文件描述符,並將 handle_event() 設置爲新事件到達時的調用的函數:

     pb = perf_buffer__new(bpf_map__fd(obj->maps.events),
         PERF_BUFFER_PAGES, handle_event, handle_lost_events,
         NULL, NULL);

然後程序輪詢緩衝區事件,直到達到時間限制,或者用戶中斷程序:

     while (!exiting) {
              err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
       ...
     }

傳遞給 handle_event() 的數據參數指向 eBPF 程序爲該事件寫入 map 的事件結構。用戶空間代碼可以獲取此信息,對其進行格式化並供用戶查看。

正如你所見,opensnoop 註冊了 eBPF 程序,每次任何應用程序調用 open() 或 openat() 系統調用時都會調用 eBPF 程序。這些在內核中運行的 eBPF 程序收集有關該系統調用的上下文的信息——可執行文件名稱和進程 ID——以及有關正在打開的文件的信息。此信息被寫入 map,用戶空間可以從中讀取它並將其顯示給用戶。

系統調用是一個穩定的內核接口,它們提供了一種非常強大的方式來觀察(虛擬)機器上發生的事情。除系統調用之外,還有很多其他穩定的接口用於附着 eBPF 程序,包括 LSM 和網絡棧中的各個掛載點。如果願意冒險或解決內核版本之間的更改,那麼你可以附着 eBPF 程序的地方範圍將會非常廣泛。

4. eBPF 自身的複雜性

現在你已經看到了一個 eBPF 編程示例,並瞭解它是如何工作的。雖然基本示例可以使 eBPF 看起來相對簡單,但也有一些複雜性使其具有挑戰性。以往編寫和分發 eBPF 程序相對困難的一個領域是內核兼容性。

4.1 跨內核版本的可移植性

eBPF 程序可以訪問內核數據結構,這些數據結構可能會隨着不同的內核版本而改變。結構本身是在構成 Linux 源代碼一部分的頭文件中定義的。過往,你必須針對與運行這些程序的內核兼容的正確頭文件集來編譯 eBPF 程序。

4.2 BCC 可移植性方法

爲了解決跨內核的可移植性,BCC)(BPF Compiler Collection)項目採用了在運行時在目標機器上編譯 eBPF 代碼的方法。這意味着編譯工具鏈需要安裝到代碼運行的每臺目標機器上,並且你必須等待編譯完成才能啓動工具。你還必須保證內核頭文件存在於文件系統上(但並非總是如此)。接着我們進入 BPF CO-RE。

4.3 CO-RE

CO-RE(一次編譯,到處運行)方法包含幾個元素:

BTF (BPF Type Format)

這是一種表達數據結構和函數簽名佈局的格式。現代 Linux 內核支持 BTF,因此你可以從正在運行的系統生成一個名爲 vmlinux.h 的頭文件,其中包含 BPF 程序可能需要的有關內核的所有數據結構信息。

libbpf, BPF 庫

一方面,libbpf 提供了加載 eBPF 程序和 map 到內核的函數。但它在可移植性方面也起着重要作用:依據 BTF 信息來調整 eBPF 代碼,以補償編譯時存在的數據結構與目標機器上的數據結構之間的任何差異。

編譯器支持

clang 編譯器得到了增強,因此當它編譯 eBPF 程序時,它包括所謂的 BTF 重定向(relocations),這是 libbpf 在加載 BPF 程序和 map 到內核時用來知道要調整哪些內容。

可選的 BPF 腳手架

可以使用 bpftool gen skeleton 從編譯的 BPF 目標文件中自動生成腳手架,其中包含輔助函數,用戶空間代碼可以調用這些輔助函數來管理 BPF 程序的生命週期——將其加載到內核中,附着到事件等等。這些函數是更高級別的抽象,對開發人員來說比直接使用 libbpf 更方便。

有關 CO-RE 的更詳細說明,請閱讀 Andrii Nakryiko 詳細的講解 [3]。

vmlinux 文件形式的 BTF 信息自 5.4 版起已包含在 Linux 內核中, 但也可以爲較舊的內核生成 libbpf 可以使用的原始 BTF 數據。BTF Hub[4] 上有關於如何生成 BTF 文件的信息,以及用於各種 Linux 發行版的文件存檔。

BPF CO-RE 方法使 eBPF 程序員比過去更容易讓他們的代碼在任何 Linux 發行版上運行——或者至少在任何新的 Linux 發行版上運行,以支持程序使用的任何 eBPF 功能集。但這並不能使 eBPF 編程輕鬆自如:其本質上仍然是內核編程。

4.4 Linux 內核知識

很快你會認識到,你仍然需要關於 Linux 內核的領域知識才能編寫更高級的工具。你需要了解可訪問 eBPF 代碼的上下文相關的數據結構。並非每個應用程序開發人員都有解析網絡數據包、訪問套接字緩衝區或處理系統調用參數的經驗。

內核將如何響應 eBPF 代碼的行爲?正如你在第 2 章中所瞭解的,內核由數百萬行代碼組成。它的文檔可能很少,因此你可能會發現自己必須閱讀內核源代碼才能弄清楚某些東西是如何工作的。

你還需要弄清楚 eBPF 代碼應該附着到哪些事件上。如果可以選擇將 kprobe 附着到整個內核中的任何函數入口點,這可能不是一個容易的決定。在某些情況下,這很簡單,例如你想訪問傳入的網絡數據包,那麼在合適的網絡接口進行 XDP 掛鉤是一個明顯的選擇。如果你想提供對特定內核事件的可觀察性,在內核代碼中找到適當的附着點可能並不難。

但在其他情況下,選擇可能並不明確。例如,僅使用 kprobes 掛鉤構成內核系統調用接口的函數的工具可能會受到稱爲 time-of-check to time-of-use (TOCTTOU) 的安全漏洞的影響。攻擊者有一個很小的機會窗口,他們可以在 eBPF 代碼讀取參數之後,但在被複制到內核內存之前更改系統調用的參數。Rex Guo 和 Junyuan Zeng 在 DEF CON 29 上對此進行了精彩的演示 [5]。一些最廣泛使用的 eBPF 工具是以非常初級的方式編寫的,並且容易受到這種攻擊。這並不是一個容易的利用,並且有一些方法可以減輕這些攻擊,但是如果你要保護高度敏感的數據免受老練的對手的攻擊,請深入瞭解你使用的工具是否會受到影響。

你已經看到了 BPF CO-RE 如何使 eBPF 程序在不同的內核版本上工作,但它只考慮了數據結構佈局的變化,而不考慮內核行爲的更廣泛的變化。例如,如果想將 eBPF 程序附着到內核中的特定函數或跟蹤點,你可能需要一個計劃 B,如果該函數或跟蹤點不存在於不同的內核版本中該怎麼辦。

4.5 多 eBPF 程序協調

現在可用的許多基於 eBPF 的工具都提供了一套可觀察性功能,通過將 eBPF 程序掛鉤到一組內核事件來實現。其中大部分是由 Brendan Gregg 和其他人在 BCC 和 bpftrace 工具中所研發。當前工具(通常是商業的)可能會提供更漂亮的圖形和 UI,但他們利用的 eBPF 程序高度基於這些原來的工具。

當你想要編寫協調不同類型事件之間交互的代碼時,事情會變得相當複雜。例如,Cilium 通過內核的網絡棧在多個點查看網絡數據包,並根據來自 Kubernetes CNI(容器網絡接口)的有關 Kubernetes Pod 的信息來處理流量。構建這個系統需要 Cilium 開發人員深入瞭解內核如何處理網絡流量,以及 “Pod” 和 “Container” 的用戶空間概念如何映射到 cgroup 和 namespace 等內核概念。在實踐中,幾個 Cilium 維護者也是內核開發人員,致力於增強 eBPF 和網絡支持。

底線是,儘管 eBPF 提供了一個非常高效和強大的平臺來連接內核,但對於沒有豐富內核經驗的普通開發人員來說,這不是一件容易的事。如果你有興趣親身體驗 eBPF 編程,我強烈建議你將其作爲練習學習;在這一領域積累經驗可能非常有價值,因爲它必將在未來幾年繼續成爲廣受歡迎的專業技能。但實際上,大多數組織不太可能在內部構建大量定製的 eBPF 工具,而是會利用來自專業 eBPF 社區的項目和產品。

讓我們繼續思考爲什麼這些基於 eBPF 的項目和產品在雲原生環境中能夠展示強大的能力。

參考資料

[1] Pixie: https://blog.px.dev/ebpf-http-tracing/

[2] opensnoop.bpf.c: https://github.com/iovisor/bcc/blob/master/libbpf-tools/opensnoop.bpf.c

[3] 講解: https://nakryiko.com/posts/bpf-portability-and-co-re/

[4] BTF Hub: https://github.com/aquasecurity/btfhub

[5] 演示: https://www.youtube.com/watch?v=yaAdM8pWKG8

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nwzCtsCLiHXZdhqFRmFsYg