今天,我們來了解下 linux 系統的革命性通用執行引擎-eBPF,之所以聊著玩意,因為它確實牛逼,作為一項底層技術,在現在的云原生生態領域中起著舉足輕重的作用。截至目前,業界使用范圍最廣的 K8S CNI 網絡方案 Calico 已宣布支持 eBPF,而作為第一個實現了Kube-Proxy 所有功能的 K8S 網絡方案——Cilium 也是基于 eBPF 技術。因此,只有了解其底層機制,才能有助于更好、更易地融入容器生態中。
作為一種顛覆性技術,eBPF 最早出現在 3.18 內核中,eBPF 新的設計針對現代硬件進行了優化,所以 eBPF 生成的指令集比舊的 BPF 解釋器生成的機器碼執行得更快。擴展版本也增加了虛擬機中的寄存器數量,將原有的 2 個 32 位寄存器增加到 10 個 64 位寄存器。由于寄存器數量和寬度的增加,開發人員可以使用函數參數自由交換更多的信息,編寫更復雜的程序。總之,這些改進使得 eBPF 版本的速度比原來的 BPF 提高了 4 倍。
基于原本的想法,eBPF 實現的最初目標是優化處理網絡過濾器的內部 BPF 指令集。然而,作為 BPF 技術的轉折點,eBPF 已經開始擴展至用戶空間。使得 eBPF 不再局限于網絡棧,已經成為內核頂級的子系統。eBPF 程序架構強調安全性和穩定性,看上去更像內核模塊,但與內核模塊不同,eBPF 程序不需要重新編譯內核,并且可以確保 eBPF 程序運行完成,而不會造成系統的崩潰。
具體參考如下示意圖:

eBPF 是一套通用執行引擎,提供了可基于系統或程序事件高效安全執行特定代碼的通用能力,通用能力的使用者不再局限于內核開發者,除此之外,eBPF 可由執行字節碼指令、存儲對象和 Helper 幫助函數組成,字節碼指令在內核執行前必須通過 BPF 驗證器 Verfier 的驗證,同時在啟用 BPF JIT 模式的內核中,會直接將字節碼指令轉成內核可執行的本地指令運行。
同時,eBPF 也逐漸在觀測(跟蹤、性能調優等)、安全和網絡等領域發揮重要的角色。Facebook、NetFlix 、CloudFlare 等知名互聯網公司內部廣泛采用基于 eBPF 技術的各種程序用于性能分析、問題排查、負載均衡、DDoS 攻擊預防等等,據相關信息顯示,在 Facebook 的機器上內置一系列基于 eBPF 的相關工具集。
相對于系統的性能分析和觀測,eBPF 技術在網絡技術中的表現,更為搶眼,BPF 技術與 XDP(eXpress Data Path) 和 TC(Traffic Control) 組合可以實現功能更加強大的網絡功能,更可為 SDN 軟件定義網絡提供基礎支撐。XDP 只作用與網絡包的 Ingress 層面,BPF 鉤子位于網絡驅動中盡可能早的位置,無需進行原始包的復制就可以實現最佳的數據包處理性能,掛載的 BPF 程序是運行過濾的理想選擇,可用于丟棄惡意或非預期的流量、進行 DDOS 攻擊保護等場景;而 TC Ingress 比 XDP 技術處于更高層次的位置,BPF 程序在 L3 層之前運行,可以訪問到與數據包相關的大部分元數據,是本地節點處理的理想的地方,可以用于流量監控或者 L3/L4 的端點策略控制,同時配合 TC egress 則可實現對于容器環境下更高維度和級別的網絡結構。關于 XDP 技術架構,可參考如下結構示意圖:

基于 Linux 系統生態體系,eBPF 有著得天獨厚的優勢,高效、生產安全且內核中內置,特別的可以在內核中完成數據分析聚合比如直方圖,與將數據發送到用戶空間分析聚合相比,能夠節省大量的數據復制傳遞帶來的 CPU 消耗。在解析 eBPF 之前,首先,我們先看下BPF 架構示意圖,具體如下所示:

接下來基于上述架構圖,我們可以清晰的看到,BPF 主要工作在內核層,其本質是類 Unix 系統上數據鏈路層的一種原始接口,提供原始鏈路層封包的收發。BPF 在數據包過濾上引入了兩大革新:
- 全新的虛擬機 (VM) 設計模型,能夠有效地工作在基于寄存器結構的 CPU 之上
- 應用程序使用緩存只復制與過濾數據包相關的數據,不會復制數據包的所有信息。這樣可以最大程度地減少BPF 處理的數據量
基于這些巨大的改進,目前,幾乎所有的 (類)Unix 系統都選擇采用 BPF 作為網絡數據包過濾技術,直到今天,許多 Unix 內核的派生系統中(包括 Linux 內核)仍基于此實現方式。舉個簡單的示例,Linux 操作系統上的 Tcpdump 底層采用的就是 BPF 作為包過濾技術。
接下來,我們再了解下 eBPF 的整體架構,具體如下圖所示:

基于上述架構圖,我們可以看到,整個 eBPF 主要分為 2 部分組件:User Program (用戶空間程序)和 Kernel (內核程序)。針對兩部分組件,簡要介紹如下:
User Program (用戶空間程序):負責加載 BPF 字節碼至內核,基于特殊場景需求,也可能需要負責讀取內核回傳的統計信息或者事件詳情。
Kernel (內核程序):內核中的 BPF 字節碼負責在內核中執行特定事件,基于特定場景需要,也會將執行的結果通過 Maps 或者 Perf-Event 事件發送至用戶空間。
在此架構參考示意圖中,用戶空間程序與內核 BPF 字節碼程序可以基于 Map 結構實現雙向通信,這為內核中運行的 BPF 字節碼程序提供了更加靈活的控制。
我們再看下 User Program (用戶空間程序)與 Kernel (內核程序)的 BPF 字節碼交互的流程,具體如下所示:
1、在User Program (用戶空間程序)中,基于LLVM 或者 GCC 工具將編寫的 BPF 代碼程序編譯成 BPF 字節碼
2、使用加載程序 Loader 將字節碼加載至內核,內核使用驗證器(Verfier) 組件保證執行字節碼的安全性,以避免對內核造成災難,在確認字節碼安全后將其加載對應的內核模塊執行;BPF 觀測技術相關的程序類型可能是
Kprobes/Uprobes/Tracepoint/Perf_events 中的一個或多個。
針對 BPF 相關的程序類型進行簡要解析,具體如下:
Kprobes:實現內核中動態跟蹤。Kprobes 可以跟蹤到 Linux 內核中的導出函數入口或返回點,但是不是穩定 ABI 接口,可能會因為內核版本變化導致,導致跟蹤失效。
Uprobes:用戶級別的動態跟蹤。與 Kprobes 類似,只是跟蹤用戶程序中的函數。
Tracepoints:內核中靜態跟蹤。tracepoints 是內核開發人員維護的跟蹤點,能夠提供穩定的 ABI 接口,但是由于是研發人員維護,數量和場景可能受限。
Perf_events:定時采樣和 PMC。
3、內核中運行的 BPF 字節碼程序可以使用兩種方式將測量數據回傳至用戶空間,具體,Maps 方式可用于將內核中實現的統計摘要信息(比如測量延遲、堆棧信息)等回傳至用戶空間;Perf-event 則用于將內核采集的事件實時發送至用戶空間,用戶空間程序實時讀取分析。
從本質上來講,eBPF 催生了一種全新的軟件開發方式。基于這種方式,我們不僅能夠對內核行為進行編程,而且依據場景需求還能編寫跨多個子系統的處理邏輯,而傳統上這些子系統是完全獨立、 無法用一套邏輯來處理的。
當前,市面上eBPF 相關的知名的開源項目包括但不限于以下:
1、Facebook 高性能 4 層負載均衡器 Katran。
2、Cilium 為下一代微服務 ServiceMesh 所打造的具備 API 感知和安全高效的容器網絡方案,底層主要使用 XDP 和 TC 等相關技術。
3、CloudFlare 公司開源的 eBPF Exporter 和 bpf-tools:eBPF Exporter 將 eBPF 技術與監控 Prometheus 緊密結合起來,而bpf-tools 可用于網絡問題分析和排查。
4、IO Visor 項目開源的 BCC、 BPFTrace 和 Kubectl-Trace:BCC 提供了更高階的抽象,可以讓用戶采用 Python、C++ 和 Lua 等高級語言快速開發 BPF 程序;BPFTrace 采用類似于 awk 語言快速編寫 eBPF 程序;Kubectl-Trace 則提供了在 kubernetes 集群中使用 BPF 程序調試的方便操作。
由于 eBPF 還在快速發展期,內核中的功能也日趨增強及完善,因此,在實際的業務場景中,我們一般推薦基于 Linux 4.4+ (4.9 以上效能會更佳) 內核的來使用 eBPF。部分 Linux Event 和 BPF 版本支持見下圖:
除上述較為知名的 eBPF 相關的開源項目外,還有越來越多的新興項目如雨后脆筍一樣開始蓬勃發展,并逐步在各種社區布局、開發以及優化完善,成為一股暖流,沖向廣闊的市場。
接下來,我們針對 eBPF 所涉及的各方面進行簡要解析,主要從網絡、安全、性能追蹤以及觀測及監控等4個維度進行,具體如下所示。
網絡
其實,剛才前面針對網絡這部分已經有所描述,現在對其進行簡要概括,具體,eBPF 的兩大特色:可編程和高性能,使它能滿足所有的網絡包處理需求。可編程意味著無需離開內核中的包處理上下文,就能添加額外的協議解析器或任何轉發邏輯, 以滿足不斷變化的需求。高性能的 JIT 編譯器使 eBPF 程序能達到幾乎與原生編譯的內核態代碼一樣的執行性能。
安全
eBPF 能夠觀測和理解所有的系統調用的能力,以及在 Packet 層和 Socket 層審視所有的網絡操作的能力,基于此兩者相結合,為系統安全提供了革命性的新思路。在此革命未發生之前,傳統模式是基于系統調用過濾、網絡層過濾和進程上下文跟蹤是在完全獨立的系統中完成的,而 eBPF 的出現則統一了可觀測性和各層面的控制能力,使得我們有更加豐富的上下文和更精細的控制能力, 因而能創建更加安全的系統。
性能追蹤
eBPF 程序能夠加載到 Trace points、內核及用戶空間應用程序中的 Probe points, 這種能力使我們對應用程序的運行時行為(Runtime Behavior)和系統本身 (System Itself)提供了史無前例的可觀測性。應用端和系統端的這種觀測能力相結合, 能在排查系統性能問題時提供強大的能力和獨特的信息。BPF 使用了很多高級數據結構, 因此能非常高效地導出有意義的可觀測數據,而不是像很多同類系統一樣導出海量的原始采樣數據。
觀測及監控
相比于操作系統提供的靜態計數器(Counters、Gauges),eBPF 能在內核中收集和聚合自定義 Metric, 并能從不同數據源來生成可觀測數據。這既擴展了可觀測性的深度,也顯著減少了整體系統開銷, 因為現在可以選擇只收集需要的數據,并且后者是直方圖或類似的格式,而非原始采樣數據。
上面講述了 eBPF 的相關特性以及優點,最后,我們再了解下在基于當前的技術以及業務場景下,eBPF 應用的局限性,具體如下:
1、現有的環境下,eBPF 程序不能調用任意的內核參數,只限于內核模塊中列出的 BPF Helper 函數。
2、eBPF 字節碼大小最初被限制為 4096 條指令,截止到內核 Linux 5.8 版本, 當前已將放寬至 100 萬指令(
BPF_COMPLEXITY_LIMIT_INSNS),可參考源碼所示:include/linux/bpf.h,對于無權限的BPF程序,仍然保留 4096 條限制 ( BPF_MAXINSNS );新版本的 eBPF 也支持了多個 eBPF 程序級聯調用,雖然傳遞信息存在某些限制,但是可以通過組合實現更加強大的功能。
3、eBPF 堆棧大小被限制在 MAX_BPF_STACK,截止到內核 Linux 5.8 版本,被設置為 512;可參考源碼所示: include/linux/filter.h,這個限制特別是在棧上存儲多個字符串緩沖區時:一個char[256]緩沖區會消耗這個棧的一半。目前沒有計劃增加這個限制,解決方法是改用 bpf 映射存儲,它實際上是無限的。
4、eBPF 程序不允許包含無法到達的指令,防止加載無效代碼,延遲程序的終止。
5、eBPF 程序中循環次數限制且必須在有限時間內結束,這主要是用來防止在 kprobes 中插入任意的循環,導致鎖住整個系統;解決辦法包括展開循環,并為需要循環的常見用途添加輔助函數。Linux 5.3 在 BPF 中包含了對有界循環的支持,它有一個可驗證的運行時間上限。
綜上所述,雖然 eBPF 技術在當前的環境下影響力強大,但是為了保證內核的處理安全和及時響應,內核中的 eBPF 技術也被進行著諸多的限制,或許,隨著技術的發展和內核演進,基于 eBPF,我們可能會找出一個更為性價比的綜合解決方案。
- EOF -