[ 翻譯 ] Sidecar和DaemonSet: 容器模式之爭
在我們最近的文章中,我們介紹了 ofollow,noindex" target="_blank">使用Linkerd作為服務網格代理 ,併發起了一系列的文章,記錄了 WePay工程團隊 對於將服務網格和 RPC/">gRPC 這樣的模式和技術引入到基礎設施的看法。
針對本系列的第二部分,我們將重點介紹一些我們一直在試驗並且在 Google Kubernetes Engine (GKE)中使用的容器模式。具體來說,我們將基礎架構的一些服務(服務網格代理和其他代理等)引入到一個 面向服務架構 (SOA)的Kubernetes叢集時用到了 sidecar 和 DaemonSet 兩種模式。
戰場
很久以前,在非常早期的Kubernetes叢集中,許多微服務都是誕生自 Google Cloud Platform 專案,它們需要藉助基礎設施來完成日誌採集,請求路由,監控度量以及其他類似的工具集或者流程。隨著時間的推移,每個微服務所提供的資料要麼被其他的微服務消費,要麼是一些站點可靠性工程師(SRE),開發工具和/或是產品開發工程師使用。
圖1:Kubernetes叢集裡最初的容器佈局
加入到這些叢集的每個微服務可以擁有多個副本,它的數量可以是一個或多個。圖1展示了一個基礎設施的樣例,其中包括一些微服務,並且每個副本一側均執行著多個sidecar。對於開發,維護,監控或是消費這些微服務的人來說,包括可靠性,可維護性和生命週期在內的許多事情都非常重要。
下面,我們將根據這些需求深入研究Sidecar和DaemonSet這兩種容器模式,並進行相互比較。具體來說,我們將會把注意力放在Kubernetes叢集(這也是我們微服務的家)中機器資源的使用,sidecar(代理)的生命週期管理以及不同應用程式的請求延遲等方面。
這是我的資源嗎?並不是!
在比較DaemonSet和sidecar模式時,首先想到的一點是,在生產級別的基礎設施裡,sidecar模式最終會比DaemonSet模式佔用更多的機器資源。總的來說,並非所有的基礎設施服務或者代理都需要和我們的主應用程式並排部署。為此,我們將會研究一些案例,在這些案例中,我們致力於讓我們的基礎設施代理承載的跑在WePay Kubernetes叢集裡的應用主容器超過多個。
在執行不到10個微服務的基礎設施中,這可能還不是一個需要直面的問題,但是隨著基礎設施的成熟和業務的增長,十個變成了二十或者三十個。實際上,它最終可能成長為擁有多個叢集,其中包含超過幾十甚至數百個需要監控和跟蹤的微服務。
我們不妨回顧一下圖1展示的基礎設施裡的三個微服務,我們最終將會有15個容器,其中只有20%是微服務的容器。如今,如果我們下定決心要向pool里加入更多的基礎設施代理的話,那麼使用的容器和資源數量會發生如下變化:
函式1:基礎設施裡所有sidecar模式的容器總數(N是微服務的數量)
按照函式1的等式新增新代理的話,我們將會從15個容器增長到20個,其中只有三個用於微服務。此外,我們注意到,有任何代理服務是通過像Java這樣的資源密集型語言實現時,資源問題往往會變得更富有挑戰,舉個例子,隨著時間的推移,記憶體使用量往往會大幅增長。
舉一個現實生活中的例子, Buoyant 最近主動從 Scala 和 JVM ( Linkerd )遷移到了Rust( Conduit ),以便在Kubernetes或者任何其他容器化的基礎設施中,服務網格代理能夠執行的更輕更快。
基於我們在容器資源管理方面的經驗,在我們打算往基礎設施的容器池新增任何新代理時,我們主要把精力放在研究它們的資源利用率和空間佔用情況。
隨著時間的推移,我們不斷向叢集裡新增新的代理時,我們學到的一個更重要的經驗教訓是,一些代理可以一次性處理多個微服務容器而且可以用來監控整個容器池。舉個例子,起初每個微服務容器都有一個日誌聚合的sidecar,它會將微服務的日誌以流的形式傳輸到一個集中式的日誌棧,但是隨著叢集的增長,我們將這些聚合器重構成了圖2所示的模式。
如上圖所示,日誌記錄和監控指標的聚合代理處理的是所分配的 節點 中所有服務容器的日誌和監控指標資料,而不是圖1中的一對一關係,而sidecar代理和代理Ω則繼續保留與他們的微服務容器一對一的關係。使用這種混合模式,我們能夠顯著的減少代理容器的數量,從前面的例子中可以看到,所有容器的總和從20變成了16:
公式2:基礎設施裡所有DaemonSet模式的容器總數(N是微服務的數量)
如今基礎設施上已經運行了超過數十個的微服務,而隨著向生產環境新增更多新的基礎設施代理,還將持續體現出 資源使用 方面顯著的改善。
生命週期的考慮
在WePay,我們遵循敏捷開發的軟體模式來開發我們的產品和工具,因此,從開發,審查/更新,再到部署,我們一直重複這樣的迴圈,直到有一天開發的軟體不再使用或者下線方才結束。對於基礎設施裡使用的所有代理程式,無論它是一個開源專案還是內部構建的,它們均有各自的生命週期,而且對於任何代理程式上的任何改進都需要釋出到所有活躍環境。
在實現我們的應用程式容器和部署我們的代理程式的過程中,我們已經意識到,維護Sidecar模式並不一定都是好的做法,而且可能被過度使用的主要理由有兩個。上面提到的第一個問題,是浪費了機器資源,第二個問題則是代理和微服務容器的生命週期之間的差異。換句話說,儘管我們將一些容器緊密地捆綁在一起可能是有其實際意義的,但是對於部署到整個叢集的所有容器來說,並非都需要以這個模式執行。
舉個例子,比如本系列前一篇文章中所提到的,一個服務網格代理會被用於將請求從出發地路由到目的地,下面圖3展示了使用Sidecar模式的請求。
圖3:使用網格代理sidecar的sidecar模式
Sender服務將請求傳送到一側的sidecar代理,該代理通過服務發現選擇目標代理,而最終,目標代理(例如在Pod A中)將請求轉發到服務X**1容器。圖4展示了使用DaemonSet模式實現相同的功能:
圖4:使用網格代理節點容器的DaemonSet模式
無論這些代理中的任何一員是否部署在應用程式容器一側,該代理仍然能夠實現它的目的,這意味著它的邏輯和執行獨立於任何應用程式容器(我們將在下一節中討論網路範疇)。
這適用於所有安裝在Kubernetes叢集中的容器及獨立部署的代理。當然,這裡假設在Sidecar和DaemonSet模式之間轉換時沒有任何功能或資料訪問方面的損失。
Sidecar模式在生命週期方面的挑戰非常大,我們開始看到一些服務棧自身提供的一些工具,比如 Istio 的 sidecar-injector
以及Linkerd專案中的 linkerd-inject
,它們允許在一個單獨的地方配置代理sidecar。值得一提的是,集中配置sidecar只能簡化配置,並不能解決同一 Kubernetes Pod 中容器執行的中斷問題。在大多數情況下,使用者需要重新建立整個pod以使得所有更改對pod生效。在圖3中,如果需要更新代理(sidecar),則必須重新啟動/重新建立整個pod才能讓變更生效。這意味著必須停止Pod A中的所有容器,重新建立一個pod來代替Pod A.
然而,如果這些容器的生命週期是分開的,就像在DaemonSet模式下那樣,更新一個容器不會中斷其他容器,而且減少了不可預知的問題以及宕機的風險。舉個例子,在圖2中,DaemonSet Pod C中的日誌聚合器的升級不會中斷跑在Pod Z裡的服務1,而這在很多用例中都可以派上用場。
網路距離和請求分組
不同的模式之間常常討論的另一個主要區別是資料和代理之間的距離。這意味著,分別在Sidecar和DaemonSet模式下發出的相同請求,代理髮送或者接收需要經過的跳數或重定向次數之間的差異。
圖5:本地請求(sidecar)vs. 遠端流(DaemonSet)
如圖5所示,在最壞的情況下,傳送到sidecar容器的請求需要的步驟更少並且傳輸的更快。在這種情況下,當sender服務傳送包並使用localhost將該包傳輸到目的地時,要想將包傳送到代理的話只需要在同一網路空間裡將其轉發到相應的埠即可。另一方面,當使用主機名或者IP傳送相同的包到代理時,除了localhost的傳輸步驟之外,還需要經過主機名解析並且網路傳輸路徑也更遠。
延遲這方面的話,如果使用高效能的域名解析服務和網路傳輸裝置,多餘的步驟是可以省略的,但是網路空間和傳輸範圍是兩個容器模式之間的最顯著的差異。
圖6:一個DaemonSet的一組請求以及sidecar關注的單個請求
圖6展示了我們在調研中考慮的另一個側重點,請求分組。 DaemonSet容器必須為叢集節點中的所有目標容器提供服務,而Sidecar容器只需要關注跑在pod裡的容器。這種分組可能對某些代理有意義。
把它們放一起
在sidecar和DaemonSet模式之間進行選擇時每個人會考慮許多不同的側重點,但是多年來機器資源的使用,代理和微服務的生命週期管理以及不同應用程式之間的請求延遲一直是我們關注的焦點。具體來說,我們最終的使用方式如下:
- 使用DaemonSet模式 降低資源使用量
- 在我們可以使用DaemonSets的情況下, 讓應用程式和基礎架構代理的升級變得更加輕鬆
- 再者,在必要時 優化容器之間的延遲 。
在本系列後續的文章中,我們將詳細介紹如何構建服務網格基礎設施的高可用,為我們的REST及gRPC應用程式提供服務。我們還將看看我們是如何使用我們這裡所描述的更快更可靠也更易於維護的基礎設施,在WePay上構建我們的gRPC應用。