服務網格現狀
服務網格為服務提供了複雜的應用層網路管理,如服務發現、流量路由、彈性(超時/重試/斷路)、認證/授權、可觀察性(日誌/度量/追蹤)等。
在分散式應用的早期,這些要求是通過直接將所需的邏輯嵌入到應用中來解決的,比如spring cloud等微服務框架。服務網格將這些功能從應用程式中提取出來,作為基礎設施的一部分透明的提供給所有應用程式使用,因此不再需要修改每個應用程式。
在早期,服務網格的功能通常是以庫的形式實現的,要求網格中的每個應用程式都要連結到以應用程式的語言框架編寫的庫。類似的事情也發生在網際網路的早期:曾幾何時,應用程式還需要執行自己的 TCP/IP 協議棧!正如我們將在這篇文章中討論的那樣,服務網格正在發展成為一種核心責任,就像網路堆疊一樣。
今天,服務網路通常使用一種叫做sidecar模型的架構實現的,通過在每一個應用pod上新增一個代理sidecar容器,如Envoy或Linkerd,這些代理也被稱為服務網格的資料平面。
這種方式優點是支援異構系統的服務治理,如果許多服務是用不同的語言編寫部署的,或者如果你正在執行不可變的第三方應用程式,這就很有好處,因為它對服務的治理是無侵入式的。同時治理模組升級對使用者應用是透明的。
缺點也是比較明顯的:
-
服務治理程式本身佔用的系統資源也不可被忽略,20個服務,每個服務5個pod就會有100個代理容器,這種純粹的重複都會耗費資源。
-
服務網格將服務治理從應用中分離出來變成獨立的程式,這樣便增加了兩跳網路的通訊成本,勢必會對整體時延有所增加。
用eBPF解鎖核心級服務網格
引入eBPF
由於sidecar模式的缺點,如何減少代理帶來的資源佔用以及應用到代理之間的網路延遲成為Service Mesh能否推廣的關鍵。理想的服務網格是作為Linux的一部分透明的提供服務,就像今天的TCP一樣。
為什麼我們以前沒有在核心中建立一個服務網格?或者Linux社群不直接解決這些需求?我們知道,Linux核心的發展是非常緩慢而嚴謹的,它必須確保使用者使用的版本是安全的。它如果有bug,那可能就是災難級的,所以新版本的核心需要幾年的時間才能進入使用者手中。與之相對應的雲原生技術棧,它的發展,更新迭代是非常迅速的。
而eBPF的出現改變了這個狀況,它是一種核心技術,它允許自定義程式在核心中執行,從而動態地擴充套件Linux核心功能。
eBPF有一個巨大的優勢,eBPF程式碼可以在執行時插入到現有的 Linux 核心中,類似於 Linux 核心模組,但與核心模組不同,它可以以安全和可移植的方式進行。這使得 eBPF 的實現能夠隨著服務網格社群的發展而繼續發展,從而使 Linux 核心能夠跟上快速發展的雲原生技術棧。
告別服務代理,全面擁抱eBPF?
看起來eBPF能做的事情有很多,那我們可以用eBPF將所有功能寫入到作業系統核心嗎?
簡短的回答:這將是相當困難的,而且可能也不是一個正確的做法。eBPF 採用的是一個事件-處理器模型,因此其執行方式有一些限制。你可以把 eBPF 模型看成是核心的 “Function as a service”。例如,eBPF 程式碼的執行路徑必須是完全確定的,會在執行前進行驗證,以確保其可以在核心中安全執行。eBPF 程式不能有任意的迴圈,否則驗證程式將不知道程式何時停止執行。簡而言之,eBPF 是圖靈不完整的。
圖靈完備是針對一套資料操作規則而言的概念,資料操作規則可以是一門程式語言,也可以是計算機裡具體實現了的指令集。
當這套規則可以實現圖靈機的所有功能時,即可以計算出一切可計算問題,就稱它具有圖靈完備性。
圖靈不完備的語言常見原因有迴圈或遞迴受限(無法寫不終止的程式,如 while(true){}; ), 無法實現類似陣列或列表這樣的資料結構(不能模擬紙帶). 這會使能寫的程式有限。圖靈不完備不是沒有意義的,有些場景我們需要限制語言本身. 如限制迴圈和遞迴, 可以保證該語言能寫的程式一定是終止的,就像我們的eBPF一樣,這也是圖靈完備可能帶來的壞處。
在許多方面,eBPF是O(1)複雜性的理想選擇(例如檢查一個資料包,操作一些位元,然後傳送它)。實現像HTTP/2和gRPC這樣複雜的協議可能是O(n)複雜度,而且非常難以除錯。那麼,這些L7功能可以駐留在哪裡?
Envoy代理已經成為服務網狀結構實現的事實上的代理,並且對我們大多數客戶需要的第7層功能有非常好的支援。雖然eBPF和Kernel可以用來改善網路的執行(短路最佳路徑、解除安裝TLS/mTLS、可觀察性收集等),但複雜的協議協商、解析和使用者擴充套件可以保留在使用者空間。對於第7層的複雜情況,Envoy仍然是服務網狀結構的資料平面。
因此 eBPF 是優化服務網格的一種強大方式,同時我們認為 Envoy 代理是資料平面的基石。
優化服務網格
Per-Node or Sidecar?
為了減少每個應用執行一個代理帶來的資源消耗,可以採用共享代理模式,即一個節點中的所有工作負載使用一個共享代理,這種模型可以提供記憶體和配置開銷的優化,對大型叢集而言,記憶體開銷時需要首要考慮的問題,這將是非常有意義的。當我們的代理從sidecar模型轉向per-node時,代理為多個程式提供連線,代理必須具有多租戶感知。這與我們從使用單個虛擬機器轉向使用容器時發生的過渡完全相同。由於我們不再使用在每個虛擬機器中執行的完全獨立的作業系統副本,而開始與多個應用程式共享作業系統,Linux 必須具有多租戶感知。這就是名稱空間和 cgroup 存在的原因。好在Envoy 已經有了名稱空間的初步概念,它們被稱為監聽器。監聽器可以攜帶單獨的配置並獨立執行。
但共享代理模型並不適用於所有人。許多企業使用者認為,因為採用邊車代理可以獲得更好的租戶和工作負載隔離,可以減少出現問題時的故障域,一些額外的記憶體開銷是值得的。
除了這兩種方式來優化記憶體上的開銷外,還可以讓每個節點上的每個服務賬戶共享一個代理以及帶有微型代理的共享遠端代理。
使用eBPF優化網路效能
由於eBPF工作在核心層,這使得它可以繞過核心的部分網路堆疊,使網路效能得到顯著改善。
在服務網格的情況下,代理在傳統網路中作為 sidecar 執行,資料包到達應用程式的路徑相當曲折:入站資料包必須穿越主機 TCP/IP 棧,通過虛擬乙太網連線到達 pod 的網路名稱空間。從那裡,資料包必須穿過 pod 的網路堆疊到達代理,代理將資料包通過迴環介面轉發到應用程式。考慮到流量必須在連線的兩端流經代理,與非服務網格流量相比,這將導致延遲的顯著增加。
基於 eBPF 的 Kubernetes CNI 實現,如 Cilium,可以使用 eBPF 程式,明智地鉤住核心中的特定點,沿著更直接的路線重定向資料包。這是可能的,因為 Cilium 知道所有的 Kubernetes 端點和服務的身份。當資料包到達主機時,Cilium 可以將其直接分配到它所要去的代理或 Pod 端點。
通過per-node模型+eBPF進行網路加速,我們的服務網格模型看起來將會是這樣:
eBPF在雲原生中的應用才剛剛開始,一切皆有可能!
參考連結:
https://www.zhaohuabing.com/post/2021-12-19-ebpf-for-service-mesh/
https://cloudnative.to/blog/how-ebpf-streamlines-the-service-mesh/