eBPF 暫時不適合取代服務網格和 sidecar 模式

最近 eBPF 技術在雲原生社區中持續火熱,在我翻譯了《什麼是 eBPF》之後,當閱讀 “雲原生環境中的 eBPF” 之後就一直在思考 eBPF 在雲原生環境中究竟處於什麼地位,發揮什麼樣的作用。當時我評論說 “eBPF 開啓了上帝視角,可以看到主機上所有的活動,而 sidecar 只能觀測到 Pod 內的活動,只要搞好進程隔離,基於 eBPF 的 proxy per-node 纔是最佳選擇”,再看到 William Morgan 的這篇文章 [1] 之後,讓我恍然大悟。

下面節選翻譯了文章我比較同意的觀點,即 eBPF 無法替代服務網格和 sidecar,感興趣的讀者可以閱讀 William 的原文。

—__1 

什麼是 eBPF

在過去,如果你想讓應用程序處理網絡數據包,那是不可能的。因爲應用程序運行在 Linux 用戶空間,它是不能直接訪問主機的網絡緩衝區。緩衝區是由內核管理的,受到內核保護,內核需要確保進程隔離,進程之間不能直接讀取對方的網絡數據包。

正確的做法是,應用程序通過系統調用(syscall)來請求網絡數據包信息,這本質上是內核 API 調用 —— 應用程序調用 syscall,內核檢查應用程序是否有權限獲得其請求的數據包;如果有,就把返回數據包。

有了 eBPF 之後,應用程序不再需要 syscall,數據包不需要在內核空間和用戶空間之間來回交互傳遞。而是我們將代碼直接交給內核,讓內核自己執行,這樣就可以讓代碼全速運行,效率更高。eBPF 允許應用程序和內核以安全的方式共享內存,eBPF 允許應用程序直接向內核提交代碼,目標都是通過超越系統調用的方式來實現性能提升。

eBPF 不是銀彈,你不能用 eBPF 運行任意程序,實際上 eBPF 可以做的事情是非常有限的。

—__2 

eBPF 的侷限性

eBPF 的侷限性也是因爲內核造成的。內核中運行的應用程序應當有自己的租戶,這些租戶之間會爭搶系統的內存、磁盤和網絡,內核的職責就是隔離和調度這些應用程序的資源,同時內核還要保護確認應用程序的權限,保護其不被其他程序破壞。

因爲我們直接將 eBPF 代碼交給內核執行,這繞過了內核安全保護(如 syscall),內核將面臨直接的安全風險。爲了保護內核,所有 eBPF 程序要想運行都必須先通過一個驗證器。

但是要想自動驗證程序是很困難的,驗證器可能會過度限制程序的功能。比如 eBPF 程序不能是阻塞的,不能有無限循環,不能超過預定的大小;其複雜性也受到限制,驗證器會評估所有可能的執行路徑,如果 eBPF 程序不能在某些範圍內完成,或者不能證明每個循環都有一個退出條件,那麼驗證器就不會允許該程序運行。

有很多應用程序都違反了這些限制,要想將它們作爲 eBPF 程序來運行的話,要麼重寫以滿足驗證器的需求,要麼給內核打補丁,來繞過一些驗證(這可能比較困難)。不過隨着內核版本的升級,這些驗證器也變得更加智能,限制也逐漸變得寬鬆,也有一些創造性的方法來繞過這些限制。

但總的來說,eBPF 程序能做的事情非常有限。對於一些重量級事件的處理,例如處理全局範圍內的 HTTP/2 流量,或者 TLS 握手協商不能在純 eBPF 環境中完成。充其量,eBPF 可以做其中的一小部分工作,然後調用用戶空間應用程序來處理對於 eBPF 來說過於複雜而無法處理的部分。

—__3 

eBPF 與服務網格的關係

因爲上文所述的 eBPF 的各項限制,七層流量仍然需要用戶空間的網絡代理來完成,eBPF 並不能替代服務網格。eBPF 可以與 CNI(容器網絡接口)一起運行,處理三層 / 四層流量,而服務網格處理七層流量。

對於每個主機一個代理(per-host)的模式,服務網格的早期實踐者 Linkerd 1.x 就是這麼用的,筆者也是從那個時候開始關注服務網格,Linkerd 1.x 還使用了 JVM 虛擬機!但是經過 Linkerd 1.x 的用戶實踐證明,這種模式相對於 sidecar 模式,對於運維和安全來說會更糟糕。

爲什麼說 sidecar 模式比 per-host 模式更好呢?因爲 sidecar 模式有以下幾個優勢,這是 per-host 模式所不具備的:

  1. 代理的資源消耗隨着應用程序的負載而變化。隨着實例流量的增加,sidecar 會消耗更多的資源,就像應用程序一樣。如果應用程序的流量非常小,那麼 sidecar 就不需要消耗很多資源。Kubernetes 現有的管理資源消耗的機制,如資源請求和限制以及 OOM kill,都會繼續工作。

  2. 代理失敗的爆炸半徑只限於一個 Pod。代理失敗與應用失敗相同,由 Kubernetes 負責處理失敗的 Pod。

  3. 代理維護。例如代理版本的升級,是通過如滾動更新,灰度發佈等應用程序本身相同的機制完成的。

  4. 安全邊界很清楚(而且很小):在 Pod 級別。Sidecar 在應用程序實例的同一安全上下文中運行。它是 Pod 的一部分,與應用程序具有一樣的 IP 地址。Sidecar 執行策略,並將 mTLS 應用於進出該 Pod 的流量,而且它只需要該 Pod 的密鑰。

而對於 per-host 模式,就沒有上述好處了。代理與應用程序 Pod 完全解耦,處理主機上所有 Pod 的流量,這樣會代理各種問題:

  1. 代理消耗的資源是高度可變的,這取決於在某個時間點 Kubernetes 調度了多少個 Pod 在該主機上。你無法有效的預測特定代理的資源消耗情況,這樣代理就有崩潰的風險(原文是這麼說的,這點筆者還是存疑的,希望有點讀者能解幫忙解釋下)。

  2. 主機上 Pod 之間的流量爭搶問題。因爲主機上的所有流量都經過同一個代理,如果有一個應用程序 Pod 的流量極高,消耗了代理的所有資源,主機上的其他應用程序就有被餓死的危險。

  3. 代理的爆炸半徑很大,而且是不斷變化的。代理的故障和升級現在影響到隨機的應用程序集合中的一個隨機的 Pod 子集,意味着任何故障或維護任務都有難以預測的風險。

  4. 使得安全問題更加複雜。以 TLS 爲例,主機上的代理必須包含該主機上所有應用程序的密鑰,這使得它成爲一個新的攻擊媒介,容易受到混淆代理 [2] 問題的影響 —— 代理中的任何 CVE 或漏洞都是潛在的密鑰泄露風險。

簡而言之,sidecar 模式繼續貫徹了容器級別的隔離保護 —— 內核可以在容器級別執行所有安全保護和公平的多租戶調度。容器的隔離仍然可以完美的運行,而 per-host 模式卻破壞了這一切,重新引入了爭搶式的多租戶隔離問題。

當然 per-host 也不是一無是處,該模式最大的好處是可以成數量級的減少代理的數量,減少網絡跳數,這也就減少了資源消耗和網絡延遲。但是與該模式帶來的運維和安全性問題相比,這些優勢都是次要的。我們也可以通過持續優化 sidecar 來彌補 sidecar 模式在這方面的不足,而 per-host 模式的缺陷確是致命性的。

其實歸根結底還是回到了爭搶式多租戶問題上,那麼能否利用現有的內核解決方案,改進一下 per-host 模式中的代理,讓其支持多租戶呢?比如改造 Envoy 代理,使其支持多租戶模式。雖然從理論來說這是可行的,但是工作量巨大,Matt Klein 也覺得不值得這樣做,還不如使用容器來實現租戶隔離。而且即使讓 per-host 模式中的代理支持了多租戶,仍然還有爆炸半徑和安全問題需要解決。

—__4 

總結

不管有沒有 eBPF,在可預見的未來,服務網格都會基於運行在用戶空間的 sidecar 代理(proxyless 模式除外)。Sidecar 模式雖然也有弊端,但它依然是既能保持容器隔離和操作的優勢,又能處理雲原生網絡複雜性的最優方案。eBPF 的能力將來是否會發展到可以處理七層網絡流量,從而替代服務網格和 sidecar,也許吧,但那一天可能很遙遠。

相關鏈接:

  1. https://buoyant.io/2022/06/07/ebpf-sidecars-and-the-future-of-the-service-mesh/

  2. https://en.wikipedia.org/wiki/Confused_deputy_problem

作者介紹

宋淨超(Jimmy Song),Tetrate 佈道師,CNCF Ambassador,雲原生社區創始人,專注於探索後 Kubernetes 時代的雲原生新範式,下面是他的公衆號。

幾米宋 專注於探索後 Kubernetes 時代的雲原生新範式,博客 jimmysong.io

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