說說 eBPF 的超能力

什麼是 eBPF

在開始之前,讓我們先談談什麼是 eBPF。該首字母縮寫詞代表可擴展伯克利包過濾器。我不認爲這很有幫助。您真正需要知道的是,eBPF 允許您在內核中運行自定義代碼。它使內核可編程。讓我們稍作停頓,確保我們都在同一個頁面上了解內核是什麼。內核是操作系統的核心部分,分爲用戶空間和內核。我們通常編寫在用戶空間中運行的應用程序。每當這些應用程序想要以任何方式與硬件交互時,無論是讀取還是寫入文件、發送或接收網絡數據包、訪問內存,所有這些都需要只有內核才能擁有的特權訪問權限。用戶空間應用程序必須在想要做任何這些事情時向內核發出請求。內核還負責諸如調度這些不同的應用程序之類的事情,以確保多個進程可以同時運行。

通常,我們只能編寫在用戶空間中運行的應用程序。eBPF 允許我們編寫在內核中運行的內核。我們將 eBPF 程序加載到內核中,並將其附加到一個事件中。每當該事件發生時,它將觸發 eBPF 程序運行。事件可以是各種不同的事物。這可能是網絡數據包的到來。它可能是在內核或用戶空間中進行的函數調用。它可能是一個跟蹤點。我們可以在很多不同的地方附加 eBPF 程序,這看起來是一個完美的事件。

一個 eBPF 示例

爲了更具體一點,我將在這裏展示一個示例。這將是 eBPF 的 Hello World。這是我的 eBPF 程序。實際的 eBPF 程序就是這裏的這幾行代碼。它們是用 C 編寫的,我的程序的其餘部分是用 Python 編寫的。我的 Python 代碼實際上將我的 C 程序編譯成 BPF 格式。我所有的 eBPF 程序要做的就是在這裏寫一些跟蹤,它會輸出 QCon。我將把它附加到執行系統調用的事件中。Execve 用於運行新的可執行文件。每當一個新的可執行文件運行時,execve 就是它運行的原因。每次在我的虛擬機上啓動一個新的可執行文件時,都會導致我的跟蹤被打印出來。

如果我運行這個程序,首先,我們應該看到我們不允許加載 BPF,除非我們有一個特權調用 CAP BPF,它通常只保留給 root。我們需要超級用戶權限才能運行。讓我們用 sudo 試試。我們開始看到很多這些跟蹤事件被寫出。我正在使用雲虛擬機,使用 VS Code 遠程訪問它。事實證明正在運行相當多的可執行文件。在不同的 shell 中,讓我們運行一些東西,讓我們運行 ps。我們可以看到進程 ID 1063059。這是我運行該 ps 可執行文件觸發的跟蹤行。我們可以在跟蹤輸出中看到,我們不僅獲得了文本,還獲得了一些有關觸發該程序運行的事件的上下文信息。我認爲這是 eBPF 提供給我們的重要部分。

eBPF 代碼必須是安全的

當我們將 eBPF 程序加載到內核中時,它的安全運行至關重要。如果它崩潰,那將導致整臺機器癱瘓。爲了確保它是安全的,有一個稱爲驗證的過程。當我們將程序加載到內核中時,eBPF 驗證器會檢查程序是否將運行完成。它永遠不會取消引用空指針。它將執行的所有內存訪問都是安全且正確的。這確保了我們正在運行的 eBPF 程序不會讓我們的機器宕機,並且它們可以正確地訪問內存。由於這個驗證過程,有時 eBPF 被描述爲一個沙箱。例如,我確實想明確一點,這是一種與容器化不同的沙盒。

動態改變內核行爲

eBPF 允許我們在內核中運行自定義程序。這是我們改變內核的行爲方式。這是一個真正的遊戲規則改變者。過去,如果要更改 Linux 內核,需要很長時間。它需要內核編程方面的專業知識。如果您對內核進行更改,通常需要幾年時間才能從內核進入我們在生產中使用的不同 Linux 發行版。內核中的新功能到達您的生產部署通常需要五年時間。這就是爲什麼 eBPF 突然成爲如此流行的技術的原因。大約在去年左右,幾乎所有生產環境都在運行 Linux 內核,這些內核足夠新,可以在其中包含 eBPF 功能。這意味着幾乎每個人現在都可以利用 eBPF 並且 “ 這就是爲什麼你突然看到這麼多工具在使用它。當然,使用 eBPF,我們不必等待 Linux 內核推出。如果我們可以在 eBPF 程序中創建新的內核功能,我們可以將其加載到機器中。我們不必重新啓動機器。我們可以動態地改變機器的行爲方式。我們甚至不必停止並重新啓動正在運行的應用程序,這些更改會立即影響內核。

動態漏洞修補

我們可以將其用於多種不同的目的,其中之一是動態修補漏洞。我們可以使用 eBPF 讓自己對漏洞利用更具彈性。我喜歡這種動態漏洞修補的一個例子是對死亡數據包的彈性。死亡數據包是利用內核漏洞的數據包。隨着時間的推移,其中一些內核無法正確處理數據包。例如,如果您將一個不正確的長度字段放入該網絡數據包中,則隧道可能無法正確處理它,並且可能會崩潰或發生壞事。這很容易通過 eBPF 緩解,因爲我們可以將 eBPF 程序附加到網絡數據包到達的事件上。我們可以看一下包,看看它是否以利用此漏洞的方式形成,即有問題的數據包。難道是死亡之包?如果是,我們可以丟棄該數據包。

eBPF 丟包

作爲一個簡單的例子,我將展示另一個程序示例,該程序將丟棄特定形式的網絡數據包。在此示例中,我將查找 ping 數據包。這就是 ICMP 協議。我可以放下它們。這是我的程序。這裏的細節不用太擔心,我基本上只是在看網絡數據包的結構。我確定我找到了一個 ping 數據包。現在,我只允許他們繼續。XDP_PASS 意味着繼續做你對這個數據包所做的任何事情。這應該發出你得到的任何追蹤。這實際上是一個名爲 pingbox 的容器。我將開始向該地址發送 ping,並且他們正在得到響應。我們可以看到這裏的序列號很好。眼下,我的 eBPF 程序沒有加載。我將運行一個 makefile 來編譯我的程序,清理之前連接到這個網絡接口的所有程序,然後加載我的程序。有 make 運行編譯,然後在這裏附加到網絡接口 eth0。您立即看到它開始追蹤,得到 ICMP 數據包。這並沒有影響行爲,我的序列號仍然像以前一樣滴答作響。

讓我們把它改成,丟棄。我們應該看到的是這裏的跟蹤仍在生成中。它繼續接收那些 ping 數據包。這些數據包正在被丟棄,因此它們永遠不會得到響應。在這邊,序列號已經停止上升,因爲我們沒有收到回覆。讓我們把它改回 PASS,然後再做一次。我們應該看到,有我的序列號,有 40 個左右的數據包丟失了,但現在它又可以工作了。我首先想說明的是,如何連接到網絡接口並處理網絡數據包。此外,我們可以動態地改變這種行爲。我們不必停下來開始 ping。我們不必停下來開始任何事情。我們所做的只是改變內核的行爲。我正在說明這一點,以說明如何處理死亡場景包。

抵禦漏洞利用

使用 BPF Linux 安全模塊,我們可以對許多其他不同的漏洞利用具有彈性。您可能遇到過 Linux 安全模塊,例如 AppArmor 或 SELinux。內核中有一個 Linux 安全模塊 API,它爲我們提供了許多不同的事件,例如 AppArmor 可以查看並確定該事件是否符合策略,並允許或禁止該特定行爲繼續進行。例如,允許或禁止文件訪問。我們可以編寫附加到同一個 LSM API 的 BPF 程序。這給了我們更多的靈活性,更多的動態安全策略。例如,有一個名爲 Tracee 的應用程序,它是由我在 Aqua 的前同事編寫的,它將附加到 LSM 事件並決定它們是否符合策略。

故障恢復能力 - 負載均衡

我們可也可以通過 eBPF 實現哪些其他類型的彈性?另一個例子是負載均衡。負載均衡可用於跨多個不同後端實例擴展請求。我們經常這樣做不僅是爲了擴展,而且是爲了實現故障恢復和高可用性。我們可能有多個實例,因此如果其中一個實例以某種方式失敗,我們仍然有足夠的其他實例來繼續處理該流量。在前面的示例中,我向您展示了一個連接到網絡接口的 eBPF 程序,或者更確切地說,它連接到稱爲網絡接口的 eXpress 數據路徑的東西。在我看來,eXpress 數據路徑非常酷。您可能有 也可能沒有允許您實際運行 XDP 程序的網卡,所以在網絡接口卡的硬件上運行 eBPF 程序。XDP 儘可能接近網絡數據包的物理到達運行。如果你的網卡支持,可以直接在網卡上運行。在這種情況下,內核的網絡堆棧甚至永遠不會看到該數據包。它的處理速度非常快。

如果網卡不支持它,內核可以再次運行您的 eBPF 程序,在收到該網絡數據包後儘可能早地運行。仍然超快,因爲數據包不需要遍歷網絡堆棧,肯定永遠不會被複制到用戶空間內存中。我們可以使用 XDP 非常快速地處理我們的數據包。我們可以做出決定,比如我們是否應該重定向那個數據包。我們可以非常快地在內核中進行第 3 層、第 4 層負載平衡,甚至可能在內核中,也可能在網卡上決定我們是否應該將此數據包向上傳遞到網絡堆棧並傳遞給用戶這臺機器上的空間。也許我們應該將負載均衡到完全不同的物理機器上。我們可以重定向數據包。我們可以非常快地做到這一點。所以可以將其用於負載平衡。

Kube proxy 代理

讓我們簡單地把我們的想法轉向 Kubernetes。在 Kubernetes 中,我們有一個稱爲 kube-proxy 的負載均衡器。kube-proxy 允許負載均衡或告訴 pod 流量如何到達其他 pod。來自一個 pod 的消息如何到達另一個 pod?它充當代理服務。如果本質上不是負載均衡器,什麼是代理?使用 eBPF,我們不僅可以選擇附加到儘可能靠近物理接口的 XDP 接口。我們也有機會附加到套接字接口上,以便儘可能靠近應用程序。應用程序通過套接字接口與網絡通信。我們可以附加到來自 pod 的消息,並且可能繞過網絡堆棧,因爲我們想將它發送到不同機器上的 pod,或者我們可以繞過網絡堆棧並直接循環回到在同一物理機或同一虛擬機上運行的應用程序。通過儘早攔截數據包,我們可以做出這些決策。我們可以避免遍歷整個內核的網絡堆棧,它給我們帶來了一些令人難以置信的性能改進。與基於 iptables 的 Kube-proxy 相比,Kube-proxy 的替換性能可以顯着提高。

高效支持 K8S 的感知網絡

我現在想更深入地探討一下爲什麼 eBPF 可以啓用這種真正高效的網絡,尤其是在 Kubernetes 中。通常,網絡堆棧非常複雜。通過內核網絡堆棧的數據包會經歷一大堆不同的步驟和階段,因爲內核決定如何處理它。在 Kubernetes 中,我們不僅在主機上擁有網絡堆棧,而且我們通常爲每個 pod 運行一個網絡命名空間。通過擁有自己的網絡命名空間,每個 pod 都必須運行自己的網絡堆棧。想象一個到達物理 eth0 接口的數據包。它遍歷整個內核的網絡堆棧,以到達它註定要去的 pod 的虛擬以太網連接。然後它穿過 POD 網絡堆棧通過套接字訪問應用程序。如果我們使用 eBPF,特別是如果我們知道 Kubernetes 身份和地址,我們可以繞過主機上的那個堆棧。當我們在那個 eth0 接口上接收到一個數據包時,如果我們已經知道該 IP 地址是否與特定的 pod 相關聯,我們基本上可以進行查找並將該數據包直接傳遞給 pod,然後通過 pod 的網絡堆棧,但不必經歷主機網絡堆棧上發生的所有複雜性。

使用像 Cilium 這樣爲 Kubernetes 啓用 eBPF 的網絡接口,我們可以啓用此網絡堆棧快捷方式,因爲我們知道 Kubernetes 身份。我們知道哪些 IP 地址與哪些 pod 相關聯,也知道哪些 pod 與哪些服務相關聯,以及命名空間。有了這些知識,我們就可以構建這些服務地圖,顯示集羣內不同組件之間的流量是如何流動的。eBPF 讓我們可以看到數據包。我們可以看到,不僅僅是目標 IP 地址和端口,我們還可以通過代理路由來找出它是什麼 HTTP 請求類型。我們可以將該數據流與 Kubernetes 身份相關聯。

在 Kubernetes 網絡中,IP 地址一直在變化,Pod 來來去去。IP 地址一分鐘可能意味着一件事,兩分鐘後,它意味着完全不同的事情。IP 地址對於理解 Kubernetes 集羣中的流量並沒有太大幫助。Cilium 可以將這些 IP 地址映射到正確的 pod、任何給定時間點的正確服務,併爲您提供更多可讀信息。它明顯更快。無論您是使用 Cilium 還是其他 eBPF 網絡實現,在主機上獲取網絡堆棧的能力都爲我們帶來了可衡量的性能改進。我們可以在這裏看到,左邊的藍線是每秒請求數的請求 - 響應率,我們可以在沒有任何容器的情況下實現,只是直接在節點之間發送和接收流量。我們可以獲得幾乎與使用 eBPF 一樣快的性能。中間的黃色和綠色下方的條向我們展示瞭如果我們不使用 eBPF 會發生什麼,並且我們使用通過主機網絡堆棧的傳統主機路由方法,它明顯變慢了。

eBPF 網絡決策

我們還可以利用 Kubernetes 身份和丟棄數據包的能力來構建非常有效的網絡策略實施。你看到丟包是多麼容易。與其只是檢查數據包並確定它是 ping 數據包,不如將數據包與策略規則進行比較並決定是否應該轉發它們。這是我們擁有的非常好的工具。你可以在 networkpolicy.io 上找到它來可視化 Kubernetes 網絡策略。我們討論了負載均衡,以及如何在 Kubernetes 集羣中以 kube-proxy 的形式使用負載均衡。畢竟,Kubernetes 爲我們提供了巨大的彈性。如果 pod 中的應用程序崩潰,它可以在沒有任何操作員干預的情況下動態重新創建。我們可以自動擴展而無需操作員干預。

故障恢復能力 ClusterMesh

如果您的集羣在特定數據中心運行並且您失去與該數據中心的連接,那麼集羣作爲一個整體的彈性會怎樣?通常,我們可以使用多個集羣。我想展示 eBPF 如何使多個集羣之間的連接變得非常簡單。在 Cilium 中,我們使用一個稱爲 ClusterMesh 的功能來做到這一點。使用 ClusterMesh,我們有兩個 Kubernetes 集羣。每個集羣中運行的 Cilium 代理會讀取一定量的關於該 ClusterMesh 中其他集羣狀態的信息。每個集羣都有自己的配置和狀態數據庫存儲在 etcd 中。我們運行一些 etcd 代理組件,使我們能夠找出我們需要的多集羣特定信息,以便所有集羣上的 Cilium 代理可以共享該多集羣狀態。

多集羣狀態是什麼意思?通常,這將是關於創建高度可用的服務。我們可能會在多個集羣上運行一個服務的多個實例,以使它們具有高可用性。使用 ClusterMesh,我們只需將服務標記爲全局,並將它們連接在一起,以便訪問該全局服務的 pod 可以在其自己的集羣上訪問它,或者在需要時在不同的集羣上訪問它。我認爲這是 Cilium 的一個非常好的功能,並且非常容易設置。如果一個集羣上的後端 pod 因某種原因被破壞,或者整個集羣出現故障,我們仍然可以將來自該集羣上其他 pod 的請求路由到另一個集羣上的後端 pod。它們可以被視爲一項分佈式集羣服務。

我想我有一個例子。我有兩個集羣。我的第一個集羣啓動了,我們可以看到 cm-1(代表 ClusterMesh 1)和第二個集羣 cm-2。他們都在運行一些 pod。我們經常在 Cilium 做一些星球大戰主題的演示。在這種情況下,我們有一些希望能夠與 Rebel 基地通信的 X-wings 戰鬥機。我們在第二個集羣上也有一些類似的 X-wings 和 Rebel 基地。讓我們看一下服務。實際上,我們來描述一下 Rebel base,service rebel-base。你可以看到它被 Cilium 註釋爲一項全球服務。作爲配置的一部分,我已對其進行了註釋,說我希望這是一項全球服務。如果我查看那裏的第二個集羣,情況也是如此。它們都被描述爲全球性的。這意味着,我可以從任一集羣上的 X-wing 發出請求,它會收到來自這兩個不同集羣、這兩個不同集羣后端的負載平衡的響應。讓我們試試看。讓我們循環運行它。讓我們執行  X-wings。不過哪個 X-wings 並不重要。我們想向 Rebel 基地發送消息。希望我們應該看到的是,我們有時會從集羣 1 中隨機獲得響應,有時是集羣 2。

如果其中一個集羣上的 Rebel 基地 pod 發生了不好的事情怎麼辦?讓我們看看代碼上有哪些節點。讓我們刪除集羣 2 上的 Pod。實際上,我將刪除 Rebel 基於第二個集羣的整個部署。我們應該看到的是,所有請求現在都由集羣 1 處理。確實,您可以看到,集羣 1 已經有一段時間了。我們實際上只需將我們的服務標記爲全球性的彈性,它是實現多集羣高可用性的一種非常強大的方式。

故障的可見性

以免我給你留下 eBPF 只是關於網絡的印象,以及網絡的優勢,讓我也談談我們如何使用 eBPF 來實現可觀察性。畢竟,如果確實出現問題,這非常重要。我們需要可觀察性,以便我們瞭解發生了什麼。在 Kubernetes 集羣中,我們有許多主機,而每臺主機只有一個內核。無論我們運行多少用戶空間應用程序,無論我們運行多少容器,它們都在每臺主機共享一個內核。如果它們在 POD 中,無論 POD 有多少,仍然只有一個內核。每當 pod 中的應用程序想要做任何有趣的事情時,比如讀取或寫入文件,或者發送或接收網絡流量,每當 Kubernetes 想要創建一個容器時。任何複雜的事情都涉及內核。內核對整個主機上發生的所有有趣的事情都有可見性。這意味着如果我們使用 eBPF 程序來檢測內核,我們可以瞭解整個主機上發生的一切。因爲我們幾乎可以檢測內核中發生的任何事情,我們可以將它用於各種不同的指標和可觀察性工具、不同類型的跟蹤,它們都可以使用 eBPF 構建。

例如,這是一個名爲 Pixie 的工具,它是一個 CNCF 沙盒項目。它爲我們提供了這個火焰圖,關於整個集羣中運行的信息。它聚合來自集羣中每個節點上運行的 eBPF 程序的信息,以生成整個集羣如何使用 CPU 時間的概覽,並詳細介紹這些應用程序正在調用的特定函數。真正有趣的是,您無需對應用程序進行任何更改,甚至無需更改配置即可獲得此工具。因爲正如我們所看到的,當您對內核進行更改時,它會立即影響在該內核上運行的任何內容。我們不必重新啓動這些進程或任何東西。

這對於我們所說的邊車模型也有一個有趣的含義。在很多方面,與 sidecar 模型相比,eBPF 爲我們提供了更多的簡單性。在 sidecar 模型中,我們必須將一個容器注入到我們想要檢測的每個 pod 中。它必須在 pod 內,因爲這是一個用戶空間應用程序可以瞭解該 pod 中發生的其他事情的方式。它必須與該 pod 共享命名空間。我們必須將那個邊車注入到每個 pod 中。爲此,需要在該 pod 的定義中引入一些 YAML。您可能不會手動編寫該 YAML 來注入 sidecar。它可能是在准入控制中完成的,或者作爲 CI/CD 過程的一部分,可能會自動執行注入該 sidecar 的過程。然而它必須注入。

另一方面,如果我們使用 eBPF,我們在內核中運行我們的工具,那麼我們不需要更改 pod 定義。我們會自動從內核的角度獲得這種可見性,因爲內核可以看到該主機上發生的一切。只要我們將 eBPF 程序添加到每個主機上,我們就會獲得全面的可見性。這也意味着我們可以抵禦攻擊。如果我們的主機以某種方式受到威脅,如果有人設法逃離容器並進入主機,或者即使他們以某種方式運行單獨的 pod,您的攻擊者可能不會費心使用您的可觀察性工具來檢測他們的進程和他們的 pod。如果您的可觀察性工具在內核中運行,那麼無論如何都會看到它們。你無法躲避那些' s 在內核中運行。這種在沒有 sidecar 的情況下運行檢測的能力正在創建一些非常強大的可觀察性工具。

彈性、安全、可觀察性、無 sidercar 部署

它還讓我們想到了無邊服務網格的想法。服務網格具有彈性、可觀察性和安全性。現在有了 eBPF,我們可以在不使用 sidecar 的情況下實現服務網格。我在圖表之前展示了我們如何使用 eBPF 繞過主機上的網絡堆棧。對於服務網格,我們可以更進一步。在傳統的 sidecar 模型中,我們在希望成爲服務網格一部分的每個 pod 中運行一個代理,也許是 Envoy。該代理的每個實例都有路由信息,每個數據包都必須通過該代理。您可以在此圖的左側看到,網絡數據包的路徑非常曲折。它基本上經歷了五個網絡堆棧實例。我們可以用 eBPF 大大縮短它。我們不能總是避免代理。如果我們在第 7 層做某事,我們需要那個代理,但我們可以避免在每個 pod 中都有一個代理實例。我們可以通過少得多的路由信息和配置信息副本來提高可擴展性。我們可以通過網絡堆棧內 XDP 層或套接字層的 eBPF 連接繞過許多這些網絡步驟。eBPF 將爲我們提供資源消耗更少、效率更高的服務網格。我希望這能體現出我認爲 eBPF 圍繞網絡、可觀察性和安全性實現的一些東西,這將爲我們提供更具彈性和可擴展性的部署。我們可以通過少得多的路由信息和配置信息副本來提高可擴展性。我們可以通過網絡堆棧內 XDP 層或套接字層的 eBPF 連接繞過許多這些網絡步驟。eBPF 將爲我們提供資源消耗更少、效率更高的服務網格。我希望這能體現出我認爲 eBPF 圍繞網絡、可觀察性和安全性實現的一些東西,這將爲我們提供更具彈性和可擴展性的部署。我們可以通過少得多的路由信息和配置信息副本來提高可擴展性。我們可以通過網絡堆棧內 XDP 層或套接字層的 eBPF 連接繞過許多這些網絡步驟。eBPF 將爲我們提供資源消耗更少、效率更高的服務網格。我希望這能體現出我認爲 eBPF 圍繞網絡、可觀察性和安全性實現的一些東西,這將爲我們提供更具彈性和可擴展性的部署。

總結

到目前爲止,我幾乎一直在談論 Linux。它也將出現在 Windows 中。微軟一直致力於 Windows 上的 eBPF。他們與 Isovalent 和許多其他對大規模可擴展網絡感興趣的公司一起參與其中。我們共同組建了 eBPF 基金會,它是 Linux 基金會下的一個基金會,真正負責跨不同操作系統的 eBPF 技術。我希望這能說明爲什麼 eBPF 如此重要,它對於軟件的彈性部署如此具有革命性,尤其是在雲原生空間中,但不一定限於此。無論您運行的是 Linux 還是 Windows,都有 eBPF 工具可幫助您優化這些部署並使其更具彈性。


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