可觀測性與追蹤

eBPF(擴充式柏克萊封包過濾器)

/ EE-bee-pee-EFF /

假設你想問作業系統核心一個它從來沒被設計來回答的問題——「每一次磁碟寫入花多久,依行程分組?」一般來說你辦不到,因為核心是一個封閉、特權的程式,你不能在它運行時就直接去改它。eBPF 是個令人意外的答案:它讓你把一支自己寫的、經過安全檢查的小程式「載入到」運行中的核心裡,把它掛到你關心的事件上,讓核心在那個事件每次發生時都執行它——不必重開機、不必重新編譯核心、也不冒當機的風險。

具體來說,eBPF 是內建在 Linux 核心裡的一台小型虛擬機:它有自己簡單的指令集(暫存器、一個很小的堆疊、以及一組固定的允許輔助呼叫)。你用受限的 C 子集(或某個更高階工具的語言)寫程式,編譯成 eBPF 位元碼,核心再把它載入。在核心願意執行它之前,一個叫驗證器(verifier)的元件會靜態地證明該程式安全且必定終止(見 eBPF 驗證器);只有通過後才被接受,並即時編譯成原生機器碼以求速度。程式被掛到一個鉤點(hook)——核心裡它該觸發的位置,例如一個網路封包抵達、一次系統呼叫進入、掛在任意核心函式上的 kprobe、或一個 tracepoint。鉤點觸發時,你的程式在核心上下文中執行,能讀取相關資料,並把結果塞進一個 BPF map——一個在核心端程式與讀取結果的使用者空間工具之間共享的鍵值儲存。

它之所以重要,是因為它把核心從一個黑盒子,變成了你能安全地即時插樁與擴充的東西——它撐起了一整個世代用於追蹤、網路與安全的正式環境工具。這裡該放兩個誠實的警告。第一,eBPF「不是」一般的核心程式設計:驗證器的安全限制很嚴,所以很多普通 C 能做的事,eBPF 程式根本不准做(歷史上沒有無界迴圈、堆疊有限、只能用核可的輔助函式)。第二,這個領域把 eBPF 用於可觀測性與追蹤;eBPF「也」被用於系統呼叫過濾與安全強制,但那個安全面向在別處談——在這裡,它是一面以讀為主、用來看進活系統內部的透鏡。

# 即時統計每個行程的 read() 系統呼叫次數,用 bpftrace 前端: $ bpftrace -e 'tracepoint:syscalls:sys_enter_read { @[comm] = count(); }' # @[bash]: 12 @[nginx]: 8841 @[postgres]: 412 # 這行指令編譯成 eBPF 位元碼,驗證器檢查它,核心在每一次 read() 上執行它

一支掛在 read() tracepoint 上的 eBPF 程式,把每個指令的呼叫次數計入一個 map——不必重建核心,在活系統上觀測。

這名字是歷史遺留:BPF 一開始是用來擷取網路封包的「柏克萊封包過濾器」;「擴充式 BPF」把那台核心內小機器一般化到核心各處的事件,所以「封包過濾器」這名字早已說明不了它現在做的大部分事。

又稱
extended Berkeley Packet FilterBPFin-kernel virtual machine核心內虛擬機擴充式 BPF