【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

_hong發表於2024-03-29

本書中, eBPF 被稱為一種 革命性的 核心技術,被廣泛應用於網路觀測安全工具中。

這種技術允許你在不重新編譯核心的情況下,使能你的自定義工具,與核心資料進行互動。聽起來很厲害。

1.1 追蹤溯源,伯克利包過濾器

eBPF 的祖宗就是伯克利包過濾器,英文名:The Berkeley Packet Filter,簡稱 BPF。Berkeley 是一個實驗室,全稱為:勞瑞斯·伯克利國家實驗室(Lawrence Berkeley National Laboratory )。1993 年,伯克利實驗室的兩位大哥 Steven McCanne 和 Van Jacobson 聯名發表了一篇文章《The BSD Packet Filter: A New Architecture for User-level Packet Capture》 ,BPF 就此問世。

這個 BPF 很純粹,就是用來決策網路包的接受和拒絕的。若收到的資料包是一個 IP 資料包,返回true(代表接受),否則返回false(代表拒絕)。主要程式碼就 4 行:

ldh		[12]
jeq		#ETHERTYPE IP, L1, L2
L1: 	ret 	#TRUE
L2: 	ret 	#0

這就是 eBPF 程式的一個基本核心思路。

1997 年,當時的 BPF(那時還不叫 eBPF)被引入核心(v2.1.75),成功應用於經典的 tcpdump 工具中。

tcpdump:網路抓包工具,用於捕獲和分析網路資料包。

2012年,核心(v3.5)引入了seccomp-bpf,開啟了 BPF 程式控制使用者空間系統呼叫的時代(第10 章會詳細介紹)。書中評價:”這使得 BPF 從簡單的包過濾器向如今強大的通用平臺邁出的第一步。“

從這時開始,即使 BPF 依舊被稱為 “包過濾器”,似乎就不是很貼切了。但大家都這麼叫,whatever!

1.2 從 BPF 到 eBPF

核心(v3.18)版本在2014年,BPF 就被真正革新了。於是開始稱其為 eBPF(extended BPF)。BPF 鳥槍換炮,大放異彩。

【表1.2.1:eBPF 鳥槍換炮】

對比項 BPF eBPF 指引
指令集 32bit 64bit
maps 這是啥? 引入了maps,用於核心態和使用者態資料互動 第 2 章
系統呼叫 極其有限 增加bpf()系統呼叫,這是真正的綠色通道 第 4 章
輔助函式 喵喵喵? 開發 eBPF 工具的基礎輪子 第 2/6 章
驗證器 需要驗證啥? 只有被驗證為安全的 eBPF 程式才能被執行 第 6 章

1.3 向生產環境繼續演化

自 2005 年開始,核心中就引入了一種名為 “探針” 的技術(kprobe)。基於這個技術,你可以將 “探針” 預埋到核心程式碼的任意指令上。透過編寫一個核心模組,控制探針的註冊、釋放,來除錯或監測系統的效能。

探針技術,使 eBPF 的應用登上了一個新的高度。2015 年,kprobe 被加入到 eBPF 中。與此同時,若干個 hook 點被加入到核心網路協議棧中,拓寬了 eBPF 處理網路的能力。

2016 年,Brendan Gregg 大神將 eBPF 運用的爐火純青,迅速出圈,在基礎設施和系統運營領域可謂無人不曉。他曾評價道:“ eBPF 為 Linux 帶來了超能力。”

同年,著名的 Cilium 專案問世了。【待補充】

2017 年,另一個名為 Katran 的開源專案(eBPF + XDP,用於負載均衡)被研發,並在臉書(Facebook.com)大規模應用。展示了 eBPF 的極高的上限。

終於,在 2018 年,eBPF 成為 Linux 的一個子系統,維護者為:Daniel Borkmann、Alexei Starovoitov 和後來加入的 Andrii Nakryiko。也是在這一年,BTF (BPF型別格式)這個核心概念被提出,為 eBPF 的可移植性,提供了一種方案。

LSM BPF 在 2020 年被引入核心。從此,BPF 程式可以附加在 LSM(核心安全模組)鉤子上,用於安全訪問控制了。

總結:經過多年的革新,eBPF 終於有能力站在一個全新的高度,賦予開發者觸碰核心指令的超能力。曾經,BPF 程式一度被限制在 4096 條指令內,但如今,eBPF 支援的指令數已經達到 100w,並且支援尾呼叫和函式呼叫等特性(見第 2/3 章)。

回首過去,向這一代貢獻於 eBPF 事業的開發者們致敬!

1.4 取名困難症

如今,eBPF 的作用已經遠超它初創之時的 “包過濾器” 了,再去糾結現在的 eBPF 應該叫什麼已經沒有意義了。就姑且這樣稱之,因為取名字實在是太困難了。

  • eBPF 互動的系統呼叫,叫 bpf()
  • eBPF 輔助函式開頭字首為 bpf_
  • BPF程式型別(你看吧,這裡書中又用的 BPF。所以不要糾結加不加 e 了,就是個代號)開頭字首為 BPF_PROG_TYPE

不過,在核心以外的領域,eBPF 這個名字更普遍一些。

1.5 Linux 核心

若想理解 eBPF ,需要一些簡單核心知識的儲備。

簡單整理一下書中涉及的知識點:

  • 核心是應用程式和硬體的中間層。這是因為應用程式所處的使用者空間,不能直接訪問硬體。
  • 核心向使用者空間開放系統呼叫介面,來執行操作硬體的高階行為。
  • 硬體訪問包括:讀寫檔案、收發網路包、訪問記憶體等。
  • 核心還有一個作用是協作程序,來支援多個應用程式的同時執行。
【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?
  • 開發應用程式時,我們不會直接使用原生的系統呼叫,而是使用對應高階語言提供的標準庫。
  • 可以使用 strace 工具來觀測核心系統呼叫。

應用程式的執行是極其依賴系統呼叫的。因此,如果我們對系統呼叫理解很深,那麼我們就會對應用程式的執行過程能夠具有非常清晰的把握。

但是,但是但是,如何觀測一個系統呼叫呢?舉一個例子:如果我想攔截開啟檔案的系統呼叫,從而能夠搞清楚一個應用程式執行時訪問了哪些檔案,我要怎麼做?簡單地找到核心系統呼叫對應的函式入口,然後增加列印輸出嗎???

1.6 為核心增加新功能

核心是極其龐大且複雜的。以核心 5.12 版本來說,就已經容納大約 28,800,000 行程式碼了。因此如果想為核心增加一個功能,需要你十分百分千分萬分瞭解核心原始碼。(如果你是核心的開發者,當我沒說)

此外,如果你想把你對核心的修改提交到核心上游主線中,那麼你的面前,將是一座比技術還高的山。Linux 是時下最流行的作業系統,它跑在全球數以億計的機器之上,其對穩定性的要求可想而知!Linux 是有圈子,有社群的。而你寫的程式碼,在保證穩定性的同時,需要兼具社群內大佬們的認可(想象一下 Linus 和一眾大佬默默的注視),讓他們認為你的程式碼值得合入到 Linux 最新的發行版中。2013 年的一篇文章《Will My Patch Make It? And How Fast? 》中給出了結論 —— 你的 patch 被接納的機率,僅有 1/3

退一萬步講,現在你已經實現了一個絕妙的方式來攔截檔案讀取的系統呼叫,社群裡的大佬們也都十分青睞你這份提交。那麼需要多久使用者才能使用到你的程式碼呢?

三個月?半年?一年?兩三年?或是永遠都用不上?

且看當前 kernel 最新發行版的版本:v6.7(截止 2024/01/08),而當前 REDL 8.8(紅帽8.8,2023/05釋出)依舊使用 4.18 版本的核心。

追求穩定,才是企業應用機的首要需求。

書裡給出了一個段子——向核心中新增一個新特性,如下圖。

【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

1.7 核心模組

為了避免修改一個核心功能就要等待好幾年,你可以透過另一種技術暫時實現這個功能。

這種技術就是核心模組。

你可以寫一個核心模組,透過動態載入和解除安裝的方式,抓取當前程序(是的,應用程式執行起來就是程序了)開啟的檔案列表。這就又回到了我們最初提出的問題 —— 你需要對核心流程和核心結構體十分百分千分熟悉。否則,當你寫的這個東西若是有 bug,一旦被載入到核心,就會引起系統崩潰。

為了增加一個功能把系統搞崩了,不值得。

安全執行,不僅僅要求不崩潰,而且要求你寫的東西沒有漏洞,不會被駭客抓到攻擊的機會。

重頭戲來了——

eBPF 提供了一種與眾不同的安全校驗方法,那就是 eBPF Verifier(又叫 eBPF 驗證器)。這個驗證器用於 eBPF 程式執行前的安全驗證,避免空指標、死迴圈等不安全情況的出現。

1.8 eBPF 程式的動態載入

eBPF 最大的特點就是動態載入。當你把一個 eBPF 程式載入到核心中時,它會立刻生效;當你終止其執行時,它的檢測功能也會立刻終止。就是這樣可動態熱插拔的效果,歎為觀止。你甚至都不需要重啟機器。

例如,你寫了一個 eBPF 程式來監控開啟檔案。當你把它載入核心,它會一直潛伏在那裡,——儘管當前沒有開啟的檔案(只是舉例,因為開啟檔案操作太常見了),而一旦有檔案開啟事件觸發,它馬上會按照你設定的邏輯開始工作。這種超級能力,你可以立刻知曉當前機器上這一個事件點發生的所有前因後果。有了 eBPF,你就有了上帝之手。

書中給出了另一張圖,使用 eBPF 後,段子變了。

【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

1.9 eBPF 程式之高效能

eBPF 不需要使用者態和核心態之間的切換,這是它高效能的原因之一。

一段 eBPF 一旦被載入和編譯(編譯器被稱為 JIT,第 3 章會講),它就同本地機器指令一般在 CPU 上執行。

2018 年的一篇論文《The eXpress data path: fast programmable packet pro‐cessing in the operating system kernel》提到,透過 XDP(eXpress Data Path,快速資料路徑,一種用於網路 eBPF 程式設計的 Hook 點) 實現路由轉發比常規的核心實現提升 2.5 倍的效能;而基於 XDP 的負載均衡相較於 IPVS 的效能提升為 4.3 倍。

對於 eBPF 在效能跟蹤和觀測中的應用來說,其一個突出的優勢在於,被觀測的事件和資料可以直接在核心中過濾,並僅選擇有價值的資訊傳遞給使用者空間。在某種程度上,資源利用率也得到了提升。

1.10 eBPF 與雲原生

如今依舊火熱的雲原生中也有 eBPF 的一席之地。

在雲原生環境下,使用者更傾向於將應用服務部署在容器中,裸機應用變得很少了。當然,容器部署和裸機部署歸根結底都是為了應用程式更好的執行。這些程式共享同一個機器的硬體資源和核心,因此 eBPF 是用來集中觀測它們效能表現和負載的最好手段。如圖所示:

【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

對於容器部署來說,能夠集中觀測所有容器,同時兼備動態載入。基於這兩點,eBPF 也成為雲原生的超能力,實至名歸。

接下來是與 sidecar 模型的對比。

書裡在這部分並沒有對 sidecar給出一個較清晰的解釋,我這裡簡單擴充套件一下。(以下內容摘錄自知乎回答 Sidecar 架構模式 - 知乎 (zhihu.com)

sidecar,直譯為側車,如圖:

【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

類似於小鬼子摩托車,sidecar 模式就是指在原來的業務邏輯上再新加一個抽象層。這種模式很好的印證了那個計算機的名言:

“電腦科學領域的任何問題都可以透過增加一個簡介的中間層來解決。”
“Any problem in computer science can be solved by another layer of indirection.”

sidecar 模式在不改變主應用的情況下,會起來一個輔助應用,來輔助主應用做一些基礎性的甚至是額外的工作。這個輔助應用不一定屬於應用程式的一部分,而只是與應用相連線。這就像是挎鬥摩托車,每個摩托車都有自己獨立的輔助部分,它隨著主應用啟動或停止。因為 sidecar 其實是一個獨立的服務,我們可以在上面做很多東西,例如 sidecar 之間相互通訊、或者透過統一的節點控制 sidecar ,從而達到 Service Mesh。

【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

現在我們回到書中內容。這種 sidecar模式主要應用於向 k8s 容器中的應用新增日誌、記錄、跟蹤、安全性等功能。

我們暫時忽略原理。這種 sidecar 模式有以下幾個缺點:

  • 應用程式的 pod 必須重啟才能新增 sidecar
  • 必須修改應用程式的 yaml 檔案。
  • 若一個 pod 包含多個容器,這些容器的就緒時間不可預測。
  • 由於 sidecar 作用在應用層,所以一些網路應用在收發網路包時必須透過完整的核心協議棧,效率大大降低。以 Service mesh proxy(基於 sidecar 實現的服務窗格應用)為例,下圖解釋了這個場景:
【Learning eBPF-1】什麼是 eBPF?為什麼它很吊?

自然,eBPF 可以很好的解決這些問題。

  • eBPF 無需重啟即可生效。
  • eBPF 對使用者程式無侵入性。
  • eBPF 是及時的,效能很高。
  • eBPF 作用於核心態,不用走核心協議棧。

另外,由於 eBPF 洞察一切,合理應用,一些駭客程式也就無所遁形了。我們將在第 8 章詳細討論安全守護主動丟棄網路包的能力。

1.11 小結

這一章主要從概念上介紹了 eBPF 這種核心超能力。它給了我們一種新的方式去改變核心的行為、追蹤核心的動作、監視應用的執行。它支援動態載入,立即生效,對系統和應用的侵入很小。

下一章,我們將深入細節,透過一個 eBPF 程式,來了解它的組成。且聽下回分解。

相關文章