本書中, 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 ,需要一些簡單核心知識的儲備。
簡單整理一下書中涉及的知識點:
核心
是應用程式和硬體的中間層。這是因為應用程式所處的使用者空間
,不能直接訪問硬體。- 核心向使用者空間開放
系統呼叫
介面,來執行操作硬體的高階行為。 - 硬體訪問包括:讀寫檔案、收發網路包、訪問記憶體等。
- 核心還有一個作用是協作
程序
,來支援多個應用程式的同時執行。
- 開發應用程式時,我們不會直接使用原生的
系統呼叫
,而是使用對應高階語言
提供的標準庫。 - 可以使用
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 版本的核心。
追求穩定,才是企業應用機的首要需求。
書裡給出了一個段子——向核心中新增一個新特性,如下圖。
1.7 核心模組
為了避免修改一個核心功能就要等待好幾年,你可以透過另一種技術暫時實現這個功能。
這種技術就是核心模組。
你可以寫一個核心模組,透過動態載入和解除安裝的方式,抓取當前程序(是的,應用程式執行起來就是程序了)開啟的檔案列表。這就又回到了我們最初提出的問題 —— 你需要對核心流程和核心結構體十分百分千分熟悉。否則,當你寫的這個東西若是有 bug,一旦被載入到核心,就會引起系統崩潰。
為了增加一個功能把系統搞崩了,不值得。
安全執行,不僅僅要求不崩潰,而且要求你寫的東西沒有漏洞,不會被駭客抓到攻擊的機會。
重頭戲來了——
eBPF 提供了一種與眾不同的安全校驗方法,那就是 eBPF Verifier
(又叫 eBPF 驗證器)。這個驗證器用於 eBPF 程式執行前的安全驗證,避免空指標、死迴圈等不安全情況的出現。
1.8 eBPF 程式的動態載入
eBPF 最大的特點就是動態載入。當你把一個 eBPF 程式載入到核心中時,它會立刻生效;當你終止其執行時,它的檢測功能也會立刻終止。就是這樣可動態熱插拔的效果,歎為觀止。你甚至都不需要重啟機器。
例如,你寫了一個 eBPF 程式來監控開啟檔案。當你把它載入核心,它會一直潛伏在那裡,——儘管當前沒有開啟的檔案(只是舉例,因為開啟檔案操作太常見了),而一旦有檔案開啟事件觸發,它馬上會按照你設定的邏輯開始工作。這種超級能力,你可以立刻知曉當前機器上這一個事件點發生的所有前因後果。有了 eBPF,你就有了上帝之手。
書中給出了另一張圖,使用 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 是用來集中觀測它們效能表現和負載的最好手段。如圖所示:
對於容器部署來說,能夠集中觀測所有容器,同時兼備動態載入。基於這兩點,eBPF 也成為雲原生的超能力,實至名歸。
接下來是與 sidecar
模型的對比。
書裡在這部分並沒有對 sidecar
給出一個較清晰的解釋,我這裡簡單擴充套件一下。(以下內容摘錄自知乎回答 Sidecar 架構模式 - 知乎 (zhihu.com))
sidecar
,直譯為側車,如圖:
類似於小鬼子摩托車,sidecar
模式就是指在原來的業務邏輯上再新加一個抽象層。這種模式很好的印證了那個計算機的名言:
“電腦科學領域的任何問題都可以透過增加一個簡介的中間層來解決。”
“Any problem in computer science can be solved by another layer of indirection.”
sidecar 模式在不改變主應用的情況下,會起來一個輔助應用,來輔助主應用做一些基礎性的甚至是額外的工作。這個輔助應用不一定屬於應用程式的一部分,而只是與應用相連線。這就像是挎鬥摩托車,每個摩托車都有自己獨立的輔助部分,它隨著主應用啟動或停止。因為 sidecar 其實是一個獨立的服務,我們可以在上面做很多東西,例如 sidecar 之間相互通訊、或者透過統一的節點控制 sidecar ,從而達到 Service Mesh。
現在我們回到書中內容。這種 sidecar
模式主要應用於向 k8s
容器中的應用新增日誌、記錄、跟蹤、安全性等功能。
我們暫時忽略原理。這種 sidecar
模式有以下幾個缺點:
- 應用程式的
pod
必須重啟才能新增sidecar
。 - 必須修改應用程式的
yaml
檔案。 - 若一個
pod
包含多個容器,這些容器的就緒時間不可預測。 - 由於
sidecar
作用在應用層,所以一些網路應用在收發網路包時必須透過完整的核心協議棧,效率大大降低。以Service mesh proxy
(基於sidecar
實現的服務窗格應用)為例,下圖解釋了這個場景:
自然,eBPF 可以很好的解決這些問題。
- eBPF 無需重啟即可生效。
- eBPF 對使用者程式無侵入性。
- eBPF 是及時的,效能很高。
- eBPF 作用於核心態,不用走核心協議棧。
另外,由於 eBPF 洞察一切,合理應用,一些駭客程式也就無所遁形了。我們將在第 8 章詳細討論安全守護主動丟棄網路包的能力。
1.11 小結
這一章主要從概念上介紹了 eBPF 這種核心超能力。它給了我們一種新的方式去改變核心的行為、追蹤核心的動作、監視應用的執行。它支援動態載入,立即生效,對系統和應用的侵入很小。
下一章,我們將深入細節,透過一個 eBPF 程式,來了解它的組成。且聽下回分解。