什么是 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 工具可幫助您優化這些部署并使其更具彈性。
審核編輯 :李倩
-
Linux
+關注
關注
87文章
11229瀏覽量
208931 -
代碼
+關注
關注
30文章
4748瀏覽量
68356 -
網格
+關注
關注
0文章
139瀏覽量
16000
原文標題:說說 eBPF 的超能力
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論