eBPF on Go

本篇內容是根據 2021 年 10 月份#201 eBPF and Go[1] 音頻錄製內容的整理與翻譯

eBPF(已有 7 年曆史)是一個可以在 Linux 內核中運行代碼的沙箱。它最初是一種構建防火牆的技術,隨着時間的推移不斷髮展,包含一系列新功能。

本期大家討論了 eBPF 的起源及其工作原理,並深入研究了一些實際用例。雖然 eBPF 程序本身不是用 Go(更像 C)編寫的,但我們將瞭解如何從 Go 代碼與 eBPF 程序進行通信。

過程中爲符合中文慣用表達有適當刪改, 版權歸原作者所有.

Mat Ryer:大家好,歡迎來到 Go Time。我是 Mat Ryer,今天我們要聊聊 eBPF。eBPF 是一項技術,它允許你在沙盒中安全地運行程序,而不需要更改內核、代碼或安裝模塊等。這通常是解決諸如網絡、安全或可觀察性問題的理想之地,因爲內核控制一切,它能看到一切…… 所以可以說它非常完美。但是,由於它是一個如此核心的組件,意味着它實際上很難更改。設想一下你自己的代碼,如果你有一個核心服務或其他系統的依賴項,你就會明白這種情況有多難更改;而當你無法更改某些東西的時候,你就無法在這裏進行創新。

這通常就是更改內核的故事---它基本上是不可行的,直到 eBPF 出現,似乎改變了規則。讓我們更多瞭解它吧,因爲我們現在就做一期關於它的節目。今天和我一起的有 Derek Parker[2]。你好,Derek!

Derek Parker:你好。

Mat Ryer:Derek,你創建了 Delve[3],對吧?你直接就創建了 Delve...

Derek Parker:是的,沒錯。

Mat Ryer:是的。而且你現在是 Red Hat 的高級軟件工程師,沒錯吧?

Derek Parker:是的,沒錯。

Mat Ryer:非常酷。歡迎來到 Go Time。我們還邀請到了 Grant Seltzer[4]。Grant 是 Aqua Security[5] 開源工程團隊的一員。

Grant Seltzer Richman:是的。

Mat Ryer:他住在紐約市布魯克林。那是個很酷的地方。感謝你加入我們,Grant。

Grant Seltzer Richman:謝謝你邀請我。

Mat Ryer:榮幸之至。榮幸是我們所有人的,一半是我的,一半是---Johnny Boursiquot 也在這。你好,Johnny。

Johnny Boursiquot:你好,兄弟。我在這兒準備問一些關於 eBPF 的愚蠢問題。

Mat Ryer:哦,很好,很好,這樣我就不用問了。

Johnny Boursiquot:我會讓你看起來很棒。我會問所有的---

Mat Ryer:是的。[笑] 我其實剛纔只是從 Wikipedia 上寫了這個介紹,兄弟。接下來的---今天我是冒牌貨。我不介意這些我不太懂的主題,因爲我真的可以深入探討,並且總能學到很多,尤其當我們邀請到像今天這樣的尊貴嘉賓時。

那麼,誰願意給我們講講 eBPF 的背景?它從何而來,究竟是什麼?

Grant Seltzer Richman正如你所說,eBPF 是一項技術,它允許你編寫一些小片段的代碼,然後把它們放入 Linux 內核中的特定位置,在某些鉤子觸發時運行。所以你可以把它看作是爲一個網絡服務註冊一個 webhook;同樣的,你也可以爲你的實際系統做這件事。這些小片段---你可以把它們看作是腳本;這些響應的事件可以是內核函數被調用,比如 Linux 內核源代碼中的某些事件。你可以將它們附加到函數上,諸如此類的事情…… 你可以將這些 eBPF 程序附加到網絡套接字上,讓它們對進出數據包作出響應。你還可以將它們附加到用戶空間函數上,所以如果你在運行一個編譯好的 Go 程序,一些服務,即使是長時間運行的服務,你可以將它們附加到實際的---其實你並不是附加到源代碼上,而是附加到你編譯後的二進制文件中的符號,這些符號對應於實際的函數。你可以讓 eBPF 程序對此作出響應。 這是 Derek 肯定可以闡述的東西。

Johnny Boursiquot:等一下---我說過我要問一些愚蠢的問題。讓我用一個對我來說更簡單的方式說你剛纔說的內容……

Grant Seltzer Richman:當然。

Johnny BoursiquoteBPF 就像---內核就像 HTML,而 eBPF 就像你的 JavaScript…… 當有人點擊 HTML 上的按鈕時,你的 JavaScript 可以對該事件的發生作出反應。

Grant Seltzer Richman:完全正確。這是很多人---我想這個類比最早是 Brendan Gregg[6] 提出的,但這是人們喜歡解釋 BPF 的經典方式。這個類比非常好。

Johnny Boursiquot:那它只是一個觀察工具嗎?你只能監聽事件,還是可以更改事情?

Grant Seltzer Richman:不,你實際上可以更改事情,你可以作出響應。當然有一些限制,因爲安全性肯定是一個問題…… 你不希望能夠隨意在運行的操作系統中放入任何東西,尤其是在生產環境中…… 但是的,你可以做很多事情。你可以採取行動,阻止某個進程的發生…… 在網絡路由的情況下,你可以按照自己的意願重新路由數據包……

Johnny Boursiquot:我的各種警覺感官都在作響,不過我們稍後再談……[笑]

Derek Parker:是的,我見過純 eBPF 的負載均衡實現,我覺得這非常酷,也非常有趣。而且關於 eBPF 程序本身的另一個有趣之處是---你基本上用 C 語言編寫它們,但這就像是簡化版的 C。與典型的 C 編譯器作鬥爭不同,你得和 BTF 驗證器 [7] 作鬥爭,它會抱怨 “你不能在 BPF 程序中使用循環。” 你需要非常小心地分配棧空間,因爲有非常嚴格的要求…… 因爲它必須是安全的,畢竟它運行在內核中;即使它是在沙盒中運行,程序仍然必須終止,所以你不能有循環等驗證器無法驗證程序是否會實際終止的情況。

因此,在編寫程序時,爲了繞過這些限制,你需要做一些有趣的事情。

Mat Ryer:這些事情是得到內核許可的嗎?這是內核明確允許的,還是某種對內核進行的操作?

Grant Seltzer Richman:BPF 確實存在於內核中。它是 Linux 內核的一部分虛擬機,伴隨所有系統發佈。在加載實際程序以及程序能做什麼方面,是有權限體系的。你必須有 root 訪問權限,或者加載 BPF 程序的進程要有特定的能力。

Mat Ryer:對。所以你不能對任何內核都這麼做。這是一項明確支持的技術。

Grant Seltzer Richman沒錯。你不能在 macOS 上這樣做。這是特定於 Linux 的。而且所有 Linux 發行版都支持它。還有一些我無法詳細說明的事情,不過微軟確實有在努力將 BPF 移植到 Windows 上。

Derek Parker:是的,我也聽說過很多,但我沒有在 Windows 上開發,所以我不清楚它的狀態…… 不過我覺得這很酷,因爲---看到 Linux 中的一些創新傳播到其他地方很棒。我真希望我們能在 macOS 上進行更多原生的容器化工作,而不是 “哦,我們趕緊安裝個 Linux 虛擬機,假裝我們在 macOS 上做容器化工作,但實際上並沒有。” 如果微軟真的在內核中採用這種技術,我覺得這很酷,而不是---我不知道這是否真的是在內核中,還是他們在幕後通過 “WSL 趕快把你的 eBPF 程序傳那邊” 來做。

Grant Seltzer Richman:我認爲它目前是基於用戶空間的,但我不想誤解…… 不過我覺得你說的沒錯。我覺得如果防作弊軟件能夠通過 BPF 程序檢測到發現的新作弊手段,並應用於 Windows 上的遊戲,那會非常酷。

Derek Parker:就像是---叫什麼來着,Punk Buster[8] 吧?這是一些大型遊戲的防作弊系統…… 像 Punk Buster eBPF 版。

(譯者注: 是一款遊戲防作弊軟件, 通常被玩家簡稱爲 PB)

Grant Seltzer Richman:是的。沒錯。

Johnny Boursiquot:我試圖將用戶空間、內核空間等概念可視化…… 對於那些仍在努力理解的人來說---通常當你和我用我們喜歡的編程語言寫程序時,比如 Go,我們寫的是用戶空間程序。當它們需要在操作系統級別做某些事情時,它們會發出系統調用,比如 “嘿,我想打開一個文件。”

對我們來說,開發者是使用標準庫。Go 會說 “嘿,我想打開這個文件,因爲我想讀取內容” 或其他操作。所以這是一個系統調用,然後操作系統會處理所有的事情,接着返回結果給我們。但這一切都發生在用戶空間,對吧?而我們現在討論的是編寫程序的能力,讓它們在內核空間中運行。這就深入了一層,實際上能監聽、響應,甚至可能更改內核正在做的事情。

Grant Seltzer Richman:完全正確。這樣做有很多優勢。讓我們以系統調用爲例,事件的流程是你的 Go 程序嘗試寫入一個文件;在底層,Go 的標準庫使用的是 write 系統調用。在系統調用之前,你的 Go 程序會把所需的信息放到正確的寄存器中,然後執行系統調用指令,內核接管,執行系統調用,返回用戶空間,告訴你 “嘿,我們成功寫入了文件” 或其他信息。

在 eBPF 的世界中,你可以寫一個 eBPF 程序,它在每次調用系統調用時觸發。所以如果你有一個 eBPF 程序,它在每次 write 系統調用被調用時觸發…… 所以在原始事件流程上增加了一步,Go 程序設置系統調用,執行它…… 就在執行之前,eBPF 程序運行;它可以檢查傳遞給系統調用的所有參數,並做任何它想做的事情…… 一旦它完成了,系統調用執行,然後返回到用戶空間,等等。而 BPF 這一邊對實際觸發它的應用程序是完全不可見的。

Johnny Boursiquot:有意思。

Mat Ryer那麼它是作爲一種後臺進程運行在主要任務的旁邊,還是一種阻塞代碼?當它看到某個調用時,它到底是如何運行的?

Derek Parker:如果探針附加在某個函數或類似的地方,它會阻塞。在那個時刻,觸發探針的程序執行將暫停,以便 eBPF 程序可以執行任何需要的檢查。這也是爲什麼在追蹤的上下文中---這是讓我對 eBPF 感興趣的地方---涉及到系統調用的開銷以及追蹤的總體開銷。

類似的事情也會發生---我對它感興趣的原因是我想讓 Delve 的追蹤後端更高效,減少開銷,這樣你也許可以在生產環境中使用它。我曾經發過一條推文,當我剛開始研究這個時,我展示了它增加的開銷。我有一個程序,運行時間是一些奇怪的微秒數... 然後使用基於 eBPF 的追蹤,它從大約 20 微秒增加到了 300-400 微秒左右... 這聽起來像是一個顯著的開銷,但我們談論的是微秒。

然後我使用 Delve 之前基於 ptrace 的追蹤進行了計時,時間增加到了 2.3 秒。所以你從微秒增加到實際的秒,這種開銷在生產環境中是不可接受的。這讓我對它產生了興趣,寫出一些非常小型、目標明確的程序,它們可以作爲某些事件的結果被調用,不會在內核和用戶空間之間進行上下文切換,儘可能短的時間內暫停程序,獲得高效、詳細但又靈活的追蹤數據。

另一個難點是如何實現靈活性。很多時候,當人們編寫 eBPF 程序時,它們非常有針對性。你在編寫函數時已經知道它會附加到哪個對應的函數上;可能是內核函數,或者其他函數…… 你通常已經知道,所以你大概知道要期待哪些參數。但在 Delve 的情況下,我有點濫用了它,因爲我想將探針附加到完全隨機的函數上,我不知道它有多少參數或返回值。我對它一無所知,但我想從中獲取所有信息。我該怎麼做呢?

這引發了很多問題,比如如何編寫一個通用的 eBPF 程序,以及如何在不引入我試圖消除的慢速問題的前提下,在內核空間和用戶空間之間進行通信。

Johnny Boursiquot:你之前提到你使用了一種受限的 C 語言;顯然出於性能原因,有些事情是不允許的。那麼開發流程是什麼樣的?如果我要使用 eBPF,我必須用 C 嗎?還是有包裝器或 SDK?開發流程是什麼樣的?

Grant Seltzer Richman:eBPF 程序本身,從高層次來看---實際上就是這樣,你有兩部分。你有 eBPF 程序本身,然後你有用戶空間程序,它將 eBPF 程序加載到內核中,並監聽反饋…… 本質上是與 eBPF 程序交互的代理。

在 BPF 這一側,我的經驗僅限於用 C 編寫。我聽說過有一個庫可以讓你用 Rust 編寫實際的 BPF 程序,因爲它的後端是 LLVM。LLVM 控制着 eBPF 字節碼的規範,類似於 Windows 的 BPF,不過我對此一無所知,不想詳細討論…… 而且 Rust 是競爭語言,所以我們不能討論它……

Mat Ryer:[笑]

Grant Seltzer Richman:是的,你用 C 編寫 BPF 程序,所以大多數情況下你只是在將這些定義在 BPF 世界中的 helper 函數串聯起來。它們是定義在頭文件中的 BPF helper 函數,使用起來並不複雜。如果你是 Go 開發者,這不會花太長時間讓你上手,尤其是看看例子…… 而且有很多入門指南。

在用戶空間這一側,你可以用多種語言寫程序。你可以使用 C 標準庫,它叫做 libbpf[9]。還有一個項目叫做 BCC[10],雖然不推薦使用,但它可以讓你使用 Python 或 Go 版本,甚至還有一箇舊的、不再維護的 Lua 版本,當然也有 Rust 的版本。用 Go 也有很多不同的庫可以使用。我偏好一個叫做 libbpfgo[11] 的庫,它是 libbpf 的包裝器。還有一個 Go 原生實現,它是 Cilium 項目的一部分…… 但我維護 libbpfgo 並在我幫助維護的項目中使用它,所以我偏好這個。

Johnny Boursiquot:你有點偏心。

Grant Seltzer Richman:是的,我對此很坦誠。

Johnny Boursiquot:顯然你提到這是個 Linux 專屬的東西,除了正在進行的 Windows 版本…… 所以如果我在 Mac 上需要編寫這些程序,我得用某種虛擬機來測試和運行它們。

Grant Seltzer Richman:是的,暫時如此。

Derek Parker:我記得以前有 DTrace[12],用於 Darwin 內核,類似的東西。

Grant Seltzer Richman:是的,macOS 有一個---我不記得確切名字,但最近版本的 macOS 有一個類似的安全框架,不過它們之間沒有互操作性。

Johnny Boursiquot:我們來談談使用場景吧。我對 eBPF 程序的最佳應用場景很感興趣。我們談到過可觀察性,知道某些事件何時發生,Derek 提到這是一個非常有針對性的工具,對吧?所以你已經知道想要獲取哪些系統調用的回調,除了你想獲取所有信息的情況…… 我猜這種情況下寫這些程序的方式會非常不同,而不是隻是尋找某個文件是否被打開,或者類似的事情。

所以我很好奇使用場景…… 我聽說過可觀察性,似乎是個不錯的用例…… 我還聽說過網絡故障排查、寫負載均衡器的情況,我對這方面尤其感興趣……eBPF 能幫你解決哪些問題?你在解決哪些問題?

Grant Seltzer Richman:當然。我最常用的場景是安全性。我幫助維護一個叫做 Tracee[13] 的項目,它掛鉤到數百個不同的事件,試圖關聯所有這些在內核中發生的事情,以確定是否有入侵或惡意軟件…… 它允許你在上面應用策略,做一些很酷的事情,比如當程序或進程被執行時,嘗試捕獲實際運行的二進制文件以供以後檢查…… 你可以做很多安全方面的事情。

可觀察性---你可以在生產中使用 BPF 來確定你的 web 服務的健康狀況。你可以附加到網絡套接字或某種網絡機制上……BPF 支持多種機制,以確定丟包數量或數據包的路由位置;你可以通過這種方式獲取大量信息。

Johnny Boursiquot:我們談論的這種可觀察性,和我們現在常用的術語 “可觀察性” 有些不同。我們通常想到的可觀察性是“我需要一個儀表盤,我需要 Honeycomb[14] 或 DataDog,看我的服務是否運行,延遲是多少” 等等。而我們現在談論的是一種不同層級的可觀察性,沒錯吧?

Grant Seltzer Richman: 是的,確實不同。在大多數情況下,甚至可以說所有情況下,你可以訪問到原始內存。你可以看到數據包的全部內容,或者用戶空間程序的全部內存內容。不過即便你不檢查內存,你仍然可以讓這些 BPF 程序觸發並報告 “嘿,發生了這個事”,就像你在 Go 程序中添加一行代碼,然後重新編譯並運行它一樣…… 比如 println,而不是編輯源代碼並重新編譯,你可以添加一個 BPF 程序,附加到內存中的某個地方,看看某行代碼何時執行。這就是 Delve 所做的。

Mat Ryer:那麼我們需要自己編寫所有這些東西嗎?是否已經有一些現成的工具?有沒有工具可以監控內存分配,比如收集到 Prometheus 中並顯示在儀表盤上?有沒有現成的工具在圍繞這個技術發展?是否有一個生態系統?

Derek Parker:是的,我知道有一些工具---從系統管理員的角度來看,Grant 之前提到的 Brendan Gregg,來自 Netflix,他是 DevOps 的專家…… 他有一整套基於 eBPF 的工具、腳本和單行代碼,可以用來檢查系統。我記得他有一篇很棒的博客,介紹如何在五分鐘內調試生產問題,裏面也提到了很多基於 eBPF 的腳本和工具。

但我覺得你提到的問題是這些工具的產品化,以及如何將它們集成到指標收集系統中…… 我知道在這個領域有很多努力正在進行。

Mat Ryer:是的,這很有趣。那麼當你說在生產環境中運行這些東西時,是不是需要提前計劃、啓用、構建功能?還是可以直接附加到正在運行的進程上?因爲它是在內核中;幾乎在所有進程的底層。

Derek Parker:是的。我認爲大多數準備工作在於確保你有一個可以加載這些程序的內核,我認爲只要你運行的是現代內核,應該沒有問題。但在用戶空間程序方面,你不需要做任何協調。你只需要與內核協調,並讓你正在運行的程序加載 eBPF 程序。但你不需要與用戶空間程序進行協調。對於 Delve 或類似的工具來說,這就像是一個普通的調試會話,我們只是請求內核允許我們做一些事情,程序不需要參與決策。

Mat Ryer:是的,這很有趣。這對於調試或任何類型的檢查都非常有用;你幾乎不需要額外運行任何東西…… 我可以理解爲什麼會有一系列單行代碼工具,因爲這在工具箱中非常有用。非常有趣。我們會嘗試找到它並把鏈接放在節目筆記中。這聽起來非常有趣。至少我們可以看到一些真實的 eBPF 程序示例。

Grant Seltzer Richman:是的。我還想補充一下,關於之前的問題,關於生態系統…… 我會說,如果這項技術讓你興奮,或者說擁有這種可見性讓你興奮,但你可能會被嚇到,甚至不想自己編寫 eBPF 代碼,實際上有一個正在發展和成熟的生態系統圍繞着這個技術。許多產品正在開發中,以獲得這種可見性。

另外,你完全不需要重新編譯代碼,這對 SRE 或安全人員來說非常好用。你可以運行服務並在不重啓服務的情況下,編寫 BPF 程序,檢查不同的內存區域,這非常有價值。

Mat Ryer:那麼稍微高層一點的呢?比如你是一個 web 開發者,想實現文件監控,自動重載的功能。你能不能寫一個 eBPF 程序監控某個路徑下的文件變化,然後採取行動,提醒你刷新?

Grant Seltzer Richman:是的,絕對可以。我覺得 Derek 說得很好,Brendan Gregg 有很多工具可以非常出色地完成非常特定的任務。我記得有一個工具叫 OpenSnoop[15],它可以告訴你每次文件被打開時的信息。或者一個更強大的工具…… 我再一次推薦我參與的項目 Tracee,你可以運行它並獲得所有你想要的信息,還可以過濾不同的事件,而無需編寫 eBPF 代碼。

Mat Ryer:那就是 Tracee,對吧?

Grant Seltzer Richman:沒錯,它在 Aqua Security 的 GitHub 上。

Johnny Boursiquot:順便提一下,你和 Liz Rice[16] 一起工作,對吧?

Grant Seltzer Richman:我曾經和她一起工作。她在我加入後不久就離開了,很遺憾……

Johnny Boursiquot:[笑] 我本來想問你和一位搖滾明星(我用了一個敏感詞)…… 一位我們社區中知名且受人尊敬的成員一起工作是什麼感覺?[笑]

Mat Ryer:…… 她還玩搖滾。

Johnny Boursiquot:她還玩搖滾。[笑聲]

Grant Seltzer Richman:當她在的時候感覺很棒。我在社區裏仍然經常與她互動。她爲 eBPF 社區做了很多工作,所以我仍然常與她合作。

Mat Ryer:你是說她在你加入後就離開了,還是之前?

Grant Seltzer Richman:在我加入後不久。我可能把她嚇跑了……[笑聲]

Mat Ryer:很可疑。這是個可能性,是吧?我就是這麼想的。

Derek Parker:Johnny,你之前提到對我提到的基於 eBPF 的負載均衡感興趣……

Johnny Boursiquot:是的。

Derek Parker:我知道 Liz Rice[17] 做過一次關於如何實現負載均衡的非常好的演講。如果你感興趣,我強烈推薦你去找找她的演講,真的非常棒。

Johnny Boursiquot:我會去找的,謝謝你。

Mat Ryer:很酷,我們也會找到並把它放在節目筆記中。

Grant Seltzer Richman:eBPF 周圍有一個很棒的社區。雖然有很多東西需要學習,也有很多讓人困惑的地方,但整個生態系統真的在不斷髮展,變得越來越容易接觸…… 有很多人對它感到興奮,並且樂於提供幫助。如果你訪問 eBPF.io,那上面有一個 Slack 頻道,你可以加入,非常有幫助。而且有很多相關的演講……Derek 和我即將參加一個會議發表演講。學習資料可以說是豐富多樣。

Mat Ryer:是的。那麼這個社區主要在哪裏?是不是都集中在那個 Slack 頻道里?如果有人想參與,eBPF 的社區還存在於哪些地方?

Derek Parker:我認爲 eBPF 的技術,尤其是在雲原生和 CNCF(雲原生計算基金會)領域非常流行;雲原生、Kubernetes 生態系統是社區主要的聚集地。此外,它也涉足了一些編程語言社區,針對那些想要實現相關功能的人們。但總體而言,大部分興趣和社區活動都集中在雲原生領域。

Johnny Boursiquot: 我在短暫瞭解 eBPF 的過程中,發現大多數示例似乎都圍繞着 BCC(BPF Compiler Collection),主要用 Python 編寫。我看過一些這樣的例子,當時我心裏想,“好吧,我們在這裏寫了一些 Python 代碼,然後在某個地方插入了一大段 C 代碼……” 我們可以看到代碼中的鉤子,但這讓我回想起之前提到的開發體驗。我很好奇,在 Go 中編寫這些程序的體驗是什麼樣的?在 Go 生態系統中,你通常用哪些庫來與這些程序交互並編寫它們?

Derek Parker:我先來回答這個問題…… 我也要爲 libbpfgo 框架做個宣傳,因爲我在 Delve 中使用它來實現基於 eBPF 的追蹤後端。對於編寫和加載 eBPF 程序並將其應用到 Go 程序中,已經有非常不錯的工具。不過在某些 eBPF 特性上與 Go 結合時會遇到一些挑戰…… 回到正題,畢竟這是 Go Time 播客…… 例如,當你在 Go 程序中使用探針時可能會遇到一些棘手的情況,有兩種探針。對於用戶空間探針,有 uprobes 和 uretprobes。uprobes 可以附加到函數的入口點,而 uretprobes 則附加到函數的返回點。因此,你可以在函數入口和返回時分別進行鉤取。

但這在 Go 中非常棘手,因爲 uretprobes 的工作機制是修改一些數據,比如 goroutine 棧上的某些地址。如果你不熟悉,goroutine 的棧通常非常小,並會隨着時間增長。而在棧增長過程中,Go 運行時需要遍歷棧中的指針,移動它們並進行更新等操作。

所以,如果你不小心使用 uretprobes,可能會導致 Go 程序崩潰,因爲當 Go 運行時試圖複製棧時,會發現一個它無法識別的地址,然後程序就會崩潰。

在 Delve 中,我們不得不做一些非常巧妙的事情,用 ptrace 監控 Go 運行時何時準備複製棧,然後臨時取消 uretprobe,讓它完成操作後再重新附加探針。因此,涉及到 Go 運行時時需要多加小心。對於帶有運行時的語言,或者像 Go 這種自檢查的語言,使用 uretprobes 可能會帶來一些奇怪的問題,這也是你在做這些低級別探查時需要注意的地方。

Mat Ryer:是的,我相信遇到這種崩潰會非常奇怪。如果你有一個小型的 eBPF 程序,它可能會發出一些有趣的信息,例如統計內存分配之類的,那你該如何提取這些信息呢?首先,這些信息會存儲在哪裏?eBPF 程序有自己的內存嗎?Go 程序如何獲取這些信息?

Grant Seltzer Richman:當然。也許我們在討論 BPF 程序時遺漏了一個關鍵點,那就是 BPF 程序到底能做些什麼。BPF 程序主要與各種形式的映射(map)交互;就像 Go 中的 map 一樣,BPF 中也有不同類型的映射,你可以用它們來存儲信息。這些 map 可以在用戶空間和內核空間之間共享,或者在多個 BPF 程序之間共享。

例如,你可以有一個環形緩衝區(ring buffer),假設你有一個簡單的 BPF 程序,它在每次某個函數被調用時觸發,或者每次觸發一個系統調用。在這個 BPF 程序中,你可以創建一個小消息,比如 “系統調用被觸發了”,然後將這個消息放入一個字符串中,使用環形緩衝區發送到用戶空間。在用戶空間中,你可以有一個 goroutine 來監聽這些事件並將它們打印到屏幕上。這些緩衝區和映射使得在用戶空間和 BPF 程序之間可以共享內存。

Mat Ryer:那麼在 Go 端能否獲得一個通道(channel)接口,可以通過 for range 來讀取這些內容?

Grant Seltzer Richman:我會說是的,但這取決於你使用的庫。實際上,底層的原語是不同的接口,但在使用 libbpfgo 的情況下,你確實可以得到一個通道。所以你可以像與其他 Go 程序一樣與它交互。

Mat Ryer:這也包括髮送數據嗎?

Grant Seltzer Richman:發送數據有點不同,因爲你是更新共享映射中的值。雖然有一個接口或 API 可以做到這一點,但環形緩衝區更適合從 BPF 向用戶空間發送數據。

Mat Ryer:我明白了。所以這些 map 就像對象一樣,它們是鍵值對。內核是否已經有了這種概念?或者這是 eBPF 模型化出來的?

Grant Seltzer Richman:不深入討論不同特性的話---因爲有很多我也不太瞭解的東西---eBPF 提供了對內核的可見性,這個概念並不新鮮,但它確實讓事情變得更簡單。

以前,可能你需要編寫一個內核模塊來實現的功能,那些模塊沒有太多安全保證,並且要求你重啓,甚至有時候需要重新編譯 Linux 內核,然後重啓,整個過程耗時很長。而 BPF 讓這一切變得更快、更安全,並且更容易接觸。

Mat Ryer:是的,作爲一個 Go 程序員,能夠通過一個通道接收關於系統內部詳細信息的實時數據,光是這種機制就足夠讓人興奮了。因爲誰知道你能構建出什麼樣的東西呢…… 我剛想到的一個用例是文件監控,但我相信,如果你能深入瞭解內核中的實際情況,可能會有很多其他的應用場景。

Grant Seltzer Richman:可能性是無窮的! [笑聲]

Mat Ryer:是的,我對此感到非常興奮。我們已經聽說了很多關於 libbpfgo 的內容,我們一定會提供相關鏈接。我在查看倉庫時,發現它確實提供了一個不錯的 API。很有趣的是,即使使用這個庫,我是否也可能會遇到我們之前提到的那些崩潰問題?如果我要編寫這樣的代碼,是不是應該避免使用 goroutine?

Derek Parker如果你要在 Go 中使用 eBPF,我唯一建議避免的是 uretprobes,除非你非常非常清楚自己在做什麼…… 因爲幾乎 100% 的情況下,它會導致你的程序崩潰。讓它正常工作的唯一方法是做一些我們在 Delve 中做的那種奇怪的事情,這是一種非常複雜的小技巧。

Mat Ryer:這個技巧能不能打包成一個庫呢?這個小技巧能不能一次解決所有問題?

Derek Parker這是有可能的…… 實際上,這個問題在 Delve 中的一個待處理的拉取請求裏已經得到了解決。實現這個解決方案需要涉及很多東西,比如 Dwarf 知識(即二進制文件中的調試信息),使用 ptrace 並獲得使用 ptrace 的權限,還要結合 eBPF…… 有很多組合在一起的東西,這些都不是典型的 Go 編程體驗。所以這裏確實有一些複雜的地方…… 但總的來說,除了 uretprobes,其他功能都可以安全地與 Go 一起使用。不過如果你使用 uretprobes,幾乎肯定會導致問題。

Mat Ryer:它們通常會用來做什麼?

Johnny Boursiquot:捕捉函數的返回值。

Derek Parker:對。

Mat Ryer:我明白了。所以如果你只需要讀取數據,可以使用環形緩衝區之類的東西來實現。

Derek Parker:是的,從程序內部,uprobe 會觸發並開始執行你的 eBPF 程序,而你的 eBPF 程序可以使用環形緩衝區或映射與用戶空間通信。

Delve 同時使用了兩者。它使用映射從用戶空間向 eBPF 程序傳遞信息,然後使用環形緩衝區將 eBPF 程序生成的數據發送回 Delve。

Mat Ryer:我明白了。這真的很有趣。當我想到 Delve 時,我覺得它是一個非常底層的工具,因爲我通常處理的是更大型的系統。但每次我深入研究時,我都會發現相似的層次結構,通常這些架構會更加複雜…… 我總是覺得這非常有趣。

Johnny Boursiquot:簡單並不容易,夥計……

Mat Ryer:是的,絕對如此。

Mat Ryer:我想問一下,大家覺得 eBPF 的未來會怎樣?我們是不是感覺這只是一個開始,未來會越來越令人興奮?

Johnny Boursiquot:商業產品…… 這是下一個方向。商業產品。 [笑聲]

Mat Ryer:這是下一步的發展嗎?我們現在就可以開始創業了。我們四個。 [笑聲] 就在 Go Time 上直播創業。不是 The Go Time,對吧?應該叫 Go Time。我剛纔做了個像 The Facebook 那樣的事。不過我覺得現在叫 The Go Time 反而更酷。

Johnny Boursiquot:The Go Time。

Mat Ryer:你懂我的意思嗎?感覺我們已經繞了一圈。不過如果我們要創業,公司會是什麼樣子的?

Grant Seltzer Richman:我肯定會說這個生態系統正在成熟,或者說剛剛開始成熟,但還有很多用例尚未被髮掘。libbpf 還沒有達到 1.0 版本。我覺得現在有很多人正在進入這個社區,他們正在學習 BPF…… 內核這邊也有很多討論,關於 BPF 正在吞噬 Linux 的說法,甚至有人在討論用 BPF 代碼重寫 Linux 內核的大部分內容,使其更加模塊化。

比如調度器(scheduler),我們可以動態地將邏輯放入調度器中,改變我們調度進程的方式。當然,驅動程序也是另一個人們正在考慮的領域…… 但從一個更高的視角來看---我不想太過 “思想領袖化”,但……

Johnny Boursiquot:請繼續。請繼續。

Grant Seltzer Richman:我忘了是誰在演講中提到過這個觀點,BPF 代表了一種新的軟件範式,它讓你能夠動態改變軟件與操作系統的交互方式。很難說 BPF 的未來會走向何方,因爲 1)還有無數新的想法可以將 BPF 程序附加到不同的地方;2)有很多新的人才正在進入社區,帶來了很多好點子,新的貢獻者層出不窮…… 總的來說,這些想法幾乎沒有受到什麼限制。就像問 “Go 的下一個大事件是什麼”,或者“你可以用 Go 寫什麼” 一樣,答案几乎是“任何東西”。不過也許 BPF 更酷一些。

Mat Ryer:好吧,這就引出了我們的常規環節…… 是時候進入 “不受歡迎的意見” 環節了。

Mat Ryer:Grant Seltzer,你有什麼不受歡迎的意見要分享嗎?

Grant Seltzer Richman:有的。我還想提一下,我可是這個環節的冠軍……

Mat Ryer:真的嗎?

Grant Seltzer Richman:我在這個節目上發表過有史以來最不受歡迎的意見。

Mat Ryer:是嗎?你是說 eBPF 比 Go 更酷的時候嗎? [笑聲] 你打破了自己的記錄。

Grant Seltzer Richman:我當時好像說的是關於棒球的。

Johnny Boursiquot:你想換個不受歡迎的意見嗎?

Grant Seltzer Richman:不,我仍然認爲棒球是最好的運動。但這次我不會試圖超越自己。我不會說任何政治性的東西…… 不過我想說,我一直以來的觀點是,工程團隊裏每個小組都應該有一個安全工程師。

Mat Ryer:真的嗎?

Grant Seltzer Richman:我認爲很多軟件開發者,甚至是負責架構整個系統的人,在做決定時,如果能夠有一個安全工程師的意見,或者是一個對安全有更多培訓的開發人員的意見,對於整個組織的安全性會有非常大的幫助,而不是僅僅依賴一個在旁邊的安全團隊,他們只能對現有的基礎設施拋出一些產品。

Johnny Boursiquot:所以在你看來,這與在產品發佈前進行的一次安全審查不同?你說的是在我們構建軟件時,團隊中就應該有一個安全人員。

Grant Seltzer Richman:是的。

Mat Ryer:這有點像測試。以前測試幾乎是獨立於軟件開發的一個環節,後來我們成爲了測試驅動開發者,意識到編寫良好測試代碼是我們的責任…… 我們是不是正在走向一種 “安全驅動開發”(Security-Driven Development,SDD)的模式?

Johnny Boursiquot:SDD。哦,我還以爲你在說別的…… [笑聲]

Mat Ryer:不,- 驅動開發。DD。

Johnny Boursiquot:明白了。

Mat Ryer: 這真的很難做到,不知道爲什麼。寫下來。我想我做到了。我覺得我們可以在剪輯中修復它。[笑聲] 如果大家都同意的話,我很樂意繼續下一個話題。

Derek Parker: 那樣最理想了。

Mat Ryer: 設計系統時,安全性確實是你需要考慮的關鍵問題之一。你說得對,有時候你只需做一些設計決策就可以讓系統更加穩健。例如,如果系統是冪等的,你可以多次重試某個操作,採取 “寧可安全也不冒險” 的原則,因爲系統的設計方式確保不會因爲重試而出問題。同樣的做法也適用於安全問題。通過做出某些設計選擇,你自然會讓系統更加安全。所以,這確實是個有趣的話題…… 我們一定要在 Twitter 上測試這個觀點。雖然我們也可以在 Facebook 上測試,但我想我們都知道爲什麼不這樣做……

Grant Seltzer Richman: 哈哈。

Mat Ryer: 因爲我們永遠也看不到測試結果……

Derek Parker: BGP,和 BPF 不同。

Johnny Boursiquot: [笑聲]

Mat Ryer: 哦,是嗎?這就是我們做這個節目的原因……[笑聲] 哦,不……!

Johnny Boursiquot: 這個澄清很及時,的確如此。

Mat Ryer: 我犯了個錯誤。好吧,非常有趣……Johnny,你怎麼看?你覺得團隊裏有安全專家是什麼感覺?

Johnny Boursiquot: 我並不反對。很難反對這個觀點。

Mat Ryer: 說實話,很難反對任何關於安全的觀點。你不能成爲那個在房間裏說 “我覺得我們應該少關注安全” 的人。[笑聲] Derek,今天你有什麼不受歡迎的觀點嗎?

Derek Parker: 有的。上次我沒有發表什麼觀點,我緊張了。但這次我有一個。雖然沒有 Grant 的觀點那麼發人深省。

Mat Ryer: 也沒有那麼戲劇性,畢竟你這次沒緊張得說不出話來。[笑聲] 這個環節的戲劇性正是來源於大家的緊張感。

Derek Parker: 最近我在寫 Go 代碼和 eBPF 代碼之間來回切換,這讓我回到了很多 C 語言的編程中。所以我的不受歡迎的觀點是---蛇形命名法比駝峯命名法更好。[笑]

Johnny Boursiquot: 哇哦。

Mat Ryer: 那麼,對於那些不熟悉這兩種命名法的人來說,能解釋一下它們的區別嗎?

Derek Parker: 蛇形命名法是這樣的:word_another_word_another_word,而駝峯命名法是這樣的:wordAnotherWord……Go 語言通常使用駝峯命名法,而 C 或 Rust 通常使用蛇形命名法。我個人覺得蛇形命名法看起來更好,也更易讀。我不知道,駝峯命名法總給人一種詞語混在一起的感覺,而蛇形命名法看起來更像一個句子…… 我覺得它看起來更好。

Mat Ryer: 這個觀點挺有意思的。那在 Twitter 上的標籤(hashtag)呢?你會用蛇形命名法來寫標籤嗎?

Derek Parker: 我通常都全用小寫。我會把字符串轉成小寫後寫標籤。

Mat Ryer: 把字符串轉成小寫。

Derek Parker: 對。

Mat Ryer: 那你會用 eBPF 來處理這個問題嗎?比如在發推之前改掉它?[笑聲] 不過據說這其實是一個無障礙訪問的問題,全用小寫對屏幕閱讀器來說不太友好。我之前在電腦上故意輸入很多無意義的東西,讓電腦讀出來,我玩了好幾個小時。

Derek Parker: 是的,是的。

Mat Ryer: 是的,基本上就是在模糊測試 say 命令。

Derek Parker: 確實,SSH 到別人的電腦上,然後用 say 命令輸入一些隨機的內容,樂趣無窮。[笑]

Mat Ryer: 哦,是的,確實有趣。

Derek Parker: 但通常我會保持單詞簡短,用一個詞的標籤,這樣希望不會影響可讀性。

Mat Ryer: 但蛇形標籤會解決這個問題,不是嗎?

Derek Parker: 沒錯。

Johnny Boursiquot: 雖然看起來有點可疑,但的確如此……

Derek Parker: [笑聲]

Mat Ryer: 看起來是有點奇怪。我以前用 Ruby 編程時,標準輸入(STDIN)也使用蛇形命名法,比如下劃線分隔詞彙…… 是啊,我也不知道。

Johnny Boursiquot: 俗話說,“入鄉隨俗。”

Grant Seltzer Richman: 有一年在 GopherCon 上有一個非常有趣的演講,講的是如何編寫更易於訪問的 Go 代碼,或者說通用代碼,其中的一部分就是讓代碼更容易被屏幕閱讀器讀取。當 Derek 提出他的觀點時,我就在想這個問題。我覺得蛇形命名法可能比駝峯命名法更容易讓屏幕閱讀器讀取。

Mat Ryer: 是的,可能是這樣吧。不過 Derek,你的觀點能走到多遠?你會給你的孩子起蛇形命名的名字嗎?

Johnny Boursiquot: 你還在考慮這個問題嗎?[笑聲]

Derek Parker: 是的,確實是這樣。我最小的孩子叫 Davie_,你知道的……[笑]

Mat Ryer: 我真迫不及待想見到第一個給孩子起這種名字的工程師,比如名字中帶下劃線之類的。我會非常喜歡。

Derek Parker:  [笑]

Mat Ryer: 這個觀點不錯。那麼,Johnny,你接受這個觀點了嗎?

Johnny Boursiquot: 我做過一些 Ruby 編程,所以我對下劃線的可讀性非常熟悉,但既然這是 Go Time 播客,我們談論的是 Go,我得說 “不,我不喜歡這個觀點。”

Derek Parker: [笑聲]

Mat Ryer: 但 Derek,你在寫 Go 代碼時會用下劃線嗎?

Derek Parker: 不會……

Mat Ryer: 你真的會在 Go 代碼裏用下劃線命名嗎?

Derek Parker: 不會,不會。我還沒那麼怪。

Johnny Boursiquot: 他知道該怎麼做。

Mat Ryer: 那如果你真的這麼做了,代碼會變成什麼樣子?會有多糟?

Derek Parker: 我覺得問題出在大寫的蛇形命名法讓我覺得非常彆扭。它在 Go 裏行不通,因爲大寫字母表示導出功能---比如蛇形命名中的首字母大寫…… 這就完全不對勁了。這不對勁。

Mat Ryer: 所有的東西在成爲 “標準做法” 前看起來都不對。

Derek Parker: 對,沒錯。[笑]

Mat Ryer: 這就是一種趨勢。其實沒有什麼問題。我現在就去試試看,看看是什麼樣子。嗯,我感覺不太好。我感覺有點噁心。太糟糕了。對…… 不過這個觀點確實不錯。我喜歡這種觀點。Johnny,你最近有不受歡迎的觀點嗎?

Johnny Boursiquot: 我的不受歡迎的觀點就是,我永遠也想不出一個不受歡迎的觀點。

Mat Ryer: 是啊,我知道。因爲你太受歡迎了。

Johnny Boursiquot: 每個觀點最終都會變得很受歡迎,所以我厭倦了不受歡迎的觀點,因爲它們總是變得很受歡迎。

Mat Ryer: 這太有深意了。你不受歡迎的觀點似乎是 “我們不應該繼續這個環節。”[笑聲] 有趣的是,當我們把這些觀點發到 Twitter 上時---我真的認爲 Grant 是記錄保持者,因爲…… 大多數時候,人們會同意這些觀點。通常,大家的論點都很有力。

Johnny Boursiquot: 現在我更關注的是有多少人持不同意見的比例。我試圖弄清楚究竟有多少人傾向於某個觀點。這比去說 “哦,這個觀點非常不受歡迎” 更有趣,因爲這種情況不常發生。

Mat Ryer: 是的,確實不常發生。這非常有趣。好的,我們快結束了。我想快速做一個 “喊出”(shout-at)環節…… 這就像 “喊出”(shout-out),但我第一次做的時候說錯了……

Johnny Boursiquot: 是的,你是在喊着叫。

Mat Ryer: 所以現在變成了 “喊出”(shout-at)…… 我們要“喊出” 一個特定的線下活動。今天我要 “喊出” 的是 Meetup.com 上的 GDN 頁面。網址是 meetup.com/pro/go。在那裏你可以找到很多資源、本地線下活動、附近的 Gopher 社區,你可以去認識並交流…… 誰知道呢?你可能會在那裏找到對 eBPF 感興趣的人,然後你們可以一起討論並加入進來。看看你能否構建一些很酷的東西。

Johnny Boursiquot: 對於那些不知道的人來說,GDN 代表 Go Developer Network(Go 開發者網絡)。這是所有 Go 線下活動和事件背後的元組織,甚至包括 GoBridge 等等。它是幕後團隊的幕後團隊。

Mat Ryer: 是的,它有很多成員。截至目前有 117,000 名成員…… 你可以成爲其中一員;如果你已經是其中一員,你可以讓這個數字再增加一個。

Johnny Boursiquot: 是的,加入 GDN,我們會把你的會員證信息郵寄給你。開玩笑的,沒有會員證。

Mat Ryer: 我剛纔還很興奮呢。我在想 “我要加入這個組織。”

Johnny Boursiquot: [笑] 我們不是發放執照的組織。

Mat Ryer: 哎…… 這樣還有什麼意思?我本來想加入的。如果沒有徽章……

Johnny Boursiquot: 你想要證書嗎?

Mat Ryer: 一個小銀色的 Gopher 徽章,你可以到處炫耀,還能享受半價優惠。

Johnny Boursiquot: 我可以私信你我的地址,你可以給我寄張支票……

Mat Ryer: 好吧。那你會給我做個 Gopher 警徽嗎?

Johnny Boursiquot: 是的,我會給你畫個塗鴉,然後寄給你。

Mat Ryer: 它上面會寫 “治安官 Mat” 嗎?[笑聲]

Johnny Boursiquot: 你可以做治安官,Mat。你可以做治安官。

Mat Ryer: 我們應該這樣做。我們應該在社區裏設立等級制度。

Johnny Boursiquot: 等級制度?我們在哪裏?[笑聲]

Mat Ryer: 是的,是的,現在就像警察部隊一樣。

Derek Parker: Go 開發者部隊,對吧。

Mat Ryer: 對。現在就像政府機構一樣。“Mat Ryer,GDN。” 這聽起來像一個新聞組織。Johnny,這是它的主頁嗎?你似乎對此很瞭解……

Johnny Boursiquot: 是的,這是入門頁面。

Mat Ryer: 你怎麼知道這麼多?

Johnny Boursiquot: 哦…… 我認識一些認識一些人的人。

Mat Ryer: 聽起來像是個可疑的組織。[笑聲] “我認識一些認識一些人的人……” 就像,“哦,你這 GitHub 賬號不錯。真是可惜,如果它出了什麼事……” [笑聲]

好吧,今天的時間差不多了。希望你喜歡這次關於 eBPF 的深度探討。非常技術性,也非常有趣…… 而且相當令人興奮。我確實想看看 Gopher 們將用它構建什麼。我認爲這裏有一些令人興奮的機會。如果你用它構建了什麼有趣的東西,請發推告訴我們 @GoTimeFM,我們很想聽到你的故事。

非常感謝今天的嘉賓。Derek Parker,Grant Seltzer---總是很高興和你們一起交流。你們一定要再來。當然,還有 Johnny Boursiquot…… 還有我。就這樣吧。再見!

這可能不是我最專業的結尾,但……[笑聲] 就這樣吧。現在我們要播放結束曲了。

Johnny Boursiquot: 是的。

Mat Ryer: 再見……

Johnny Boursiquot: 再見。

更多參考:

libbpf[18]

libbpf/libbpf[19]

深入淺出 eBPF 安全項目 Tracee[20]

bcc 之 opensnoop 工具的使用 [21]

參考資料

[1]

#201 eBPF and Go: https://changelog.com/gotime/201

[2]

Derek Parker: https://github.com/derekparker

[3]

Delve: https://github.com/go-delve/delve

[4]

Grant Seltzer: https://github.com/grantseltzer

[5]

Aqua Security: https://github.com/aquasecurity

[6]

Brendan Gregg: https://www.brendangregg.com/

[7]

BTF 驗證器: https://forsworns.github.io/zh/blogs/20210706/

[8]

Punk Buster: https://help.ea.com/tw/help/battlefield/punkbuster-bans-kicks-troubleshooting/

[9]

libbpf: https://docs.kernel.org/bpf/libbpf/libbpf_overview.html

[10]

BCC: https://github.com/iovisor/bcc

[11]

libbpfgo: https://github.com/aquasecurity/libbpfgo

[12]

DTrace: https://www.brendangregg.com/dtrace.html

[13]

Tracee: https://github.com/aquasecurity/tracee

[14]

Honeycomb: https://www.honeycomb.io/

[15]

OpenSnoop: https://github.com/brendangregg/perf-tools/blob/master/opensnoop

[16]

Liz Rice: https://www.lizrice.com/

[17]

Liz Rice: https://github.com/lizrice

[18]

libbpf: https://github.com/libbpf

[19]

libbpf/libbpf: https://github.com/libbpf/libbpf

[20]

深入淺出 eBPF 安全項目 Tracee: https://www.ebpf.top/post/tracee_intro/

[21]

bcc 之 opensnoop 工具的使用: https://www.rutron.net/posts/2203/bcc-opensnoop-usage/

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