Netflix 零配置服務網格與按需叢集發現

Netflix 零配置服務網格與按需叢集發現

我們在服務網​​格之旅的初期。現在我們真誠地使用它,我們希望與社區合作做出更多的Envoy 改進。將我們的自適應並發限制[31] 實現移植到Envoy 是一個很好的開始- 我們期待與社區在更多方面合作。我們特別對社區在增量EDS 方面的工作感興趣。EDS 端點佔了更新量的最大部分,這對控制平面和Envoy 造成了不必要的壓力。

本文翻譯自由David Vroom, James Mulcahy, Ling Yuan, Rob Gulewich 所寫的Netflix 部落格 Zero Configuration Service Mesh with On-Demand Cluster Discovery[1]。

Netflix 相信大家並不陌生,在 Spring Cloud 生態中就有Netflix 全家桶。多年前,我也曾將基於Netflix OSS 建置的微服務架構搬上了Kubernetes 平台,並持續折騰好幾年。

Spring Cloud Netflix 有著龐大的用戶群以及用戶場景,其提供了微服務治理的一整套解決方案:服務發現Eureka、客戶端負載平衡 Ribbon、斷路器Hystrix、微服務網格Zuul。

有過相同經驗的小夥伴應該都會同感,這樣一套微服務解決方案的架構,經過多年的演進也會讓人痛苦不堪:複雜度越來越高、版本碎片化嚴重、多語言多框架的支持和功能無法統一等等。這也是Netflix 自己也不得不面對的問題,隨後他們將目光轉向了服務網格,並尋求一個無縫遷移的方案。

在這篇文章中,他們給了答案:Netflix 與社區合作,並自建控制平面與現有的服務發現體系相容。這套實現下來可能並不容易,也未看到他們要將其開源的想法,但是至少可以給大家一個參考。

順便預告一下自己正在規劃的一篇,如何將微服務平滑地遷移到Flomesh 服務網格平台。不只無縫相容Eureka,還有HashiCorp Consul,未來還會相容於更多的服務發現方案。

以下是原文的翻譯:

在這篇文章中,我們將討論Netflix 服務網格(Service Mesh)實踐的相關資訊:歷史背景、動機,以及我們如何與Kinvolk 和Envoy 社群合作,在複雜的微服務環境中推動服務網格落地的一個特徵:按需叢集發現(On-demand Cluster Discovery)。

Netflix 的IPC 簡史

Netflix 是早期雲端運算的採納者,特別是對於大規模的公司:我們在2008 年開始了遷移,並且到2010 年,Netflix的串流媒體完全運行在AWS上[2]。今天我們擁有豐富的工具,包括開源和商業的,所有這些都是為雲端原生環境設計的。然而,在2010 年,幾乎沒有這樣的工具存在:CNCF[3] 直到2015 年才成立!由於沒有現成的解決方案,我們需要自己建造它們。

对于服务间的进程间通信(IPC),我们需要一个中间层负载均衡器通常提供的丰富功能集。我们还需要一种解决方案,来应对云环境的现实:一个高度动态的环境,其中节点不断上线和下线,服务需要快速响应变化并隔离失败。为了提高可用性,我们设计了可以单独发生故障的系统组件,以避免单点故障。这些设计原则引导我们走向了客户端负载均衡,而 2012年圣诞节的宕机[4] 进一步坚定了这个决策。在云早期,我们构建了Eureka[5] 用于服务发现,以及 Ribbon(内部名为NIWS)用于IPC[6]。Eureka 解决了服务如何发现与其通信的实例的问题,而 Ribbon 提供了负载均衡的客户端,以及许多其他的弹性特性。这两项技术,加上一系列其他的弹性和混沌工具,产生了巨大的收益:我们的可靠性因此得到了显著的改善。

Eureka 和Ribbon 提供了一個簡單而強大的接口,讓它們的使用變得容易。一個服務與另一個服務通信,需要知道兩件事:目的服務的名稱,以及流量是否應該是安全的。Eureka 為此提供的抽像是虛擬IP(VIPs)用於非安全通信,和安全性VIPs(SVIPs)用於安全通訊。服務向Eureka 宣告一個VIP 位元和連接埠(例如:_myservice_,連接埠_8080_),或一個SVIP 位元和連接埠(例如:_myservice-secure_,連接埠8443),或同時使用兩者。IPC 用戶端針對該VIP 或SVIP 進行實例化,而Eureka 用戶端程式碼透過從Eureka 伺服器取得它們來處理該VIP 到一組IP 和連接埠對的轉換。客戶端還可以選擇啟用像重試或熔斷這樣的IPC 功能,或使用一組合理的預設值。

圖片圖片

在這種架構中,服務間通訊不再通過負載平衡器的單點故障通道。但問題是,Eureka 現在成為了VIP 註冊主機真實性的新的單一故障點。然而,如果Eureka 宕機,服務仍然可以互相通信,儘管它們的主機資訊隨著VIP 的上下線而逐漸過時。在故障期間以降級但可用的狀態運行仍然是相比完全停止流量的明顯改進。

在這種架構中,服務與服務間的通訊不再經過負載平衡器的單一故障點。但問題是,Eureka 現在成為了VIP 註冊主機真實性的新的單一故障點。然而,如果Eureka 宕機,服務仍然可以互相通信,儘管它們的主機資訊隨著VIP 的上下線而逐漸過時。在故障期間以降級但可用的狀態運行仍然是相比完全停止流量的明顯改進。

為什麼選擇網格?

上述架構在過去的十年中為我們服務得很好,但隨著業務需求的變化和行業標準的演變,我們的IPC 生態系統在許多方面都增加了更多的複雜性。首先,我們增加了不同的IPC 客戶端的數量。我們的內部IPC 流量現在是純REST、GraphQL[7] 和 gRPC[8] 的混合。其次,我們從只使用Java 環境遷移到了多語言環境:我們現在也支援 node.js[9]、Python[10] 以及各種開源和現成的軟體。第三,我們繼續為IPC 用戶端增加更多功能,如 自適應並發限制[11]、斷路器[12]、對沖和故障注入已成為我們工程師為使系統更可靠而採用的標準工具。與十年前相比,我們現在支援更多功能、更多語言、更多客戶端。確保所有這些實現之間的功能一致性並確保它們都以相同的方式運行是具有挑戰性的:我們希望這些功能有一個單一、經過充分測試的實現,以便我們可以在一個地方進行更改和修復錯誤。

這就是服務網格的價值所在:我們可以在一個實作中集中IPC 功能,並使每種語言的客戶端盡可能簡單:它們只需要知道如何與本地代理通話。Envoy[13] 對我們來說是代理的絕佳選擇:它是一個經過戰鬥考驗的開源產品,已經在行業中被廣泛使用,擁有許多關鍵的彈性功能[14],以及當我們需要擴展其功能時的良好的擴展點[15]。能夠透過一個集中的控制平面配置代理[16] 是一個殺手級的功能:這使我們可以動態配置客戶端負載平衡,就像它是一個集中的負載平衡器,但仍然避免了服務到服務請求路徑中的負載平衡器作為單一的故障點。

為什麼選擇網格?

過去十年,上述架構已經為我們提供了良好的服務,儘管不斷變化的業務需求和行業標準的演進使我們的IPC 生態系統變得更為複雜。首先,我們增加了不同IPC 客戶端的數量。目前,我們的內部IPC 流量包含了簡單的REST,GraphQL[17],和 gRPC[18]。其次,我們從僅Java 環境轉變為多語言環境:現在我們也支援 node.js[19],Python[20],以及各種開源和現成的軟體。第三,我們繼續為IPC 用戶端增加更多功能:諸如 自適應並發限制[21]、熔斷[22]、hedging 和故障注入等功能已成為我們的工程師用來提高系統可靠性的標準工具。與十年前相比,我們現在在更多的語言、更多的客戶端支援更多的功能。保持所有這些實現之間的功能一致性,確保它們的行為保持一致是具有挑戰性的:我們想要的是所有這些功能的單一、經過良好測試的實現,以便我們能在一個地方進行變更和修復錯誤。

這就是服務網格的作用所在:我們可以將IPC 功能集中在單一的實作中,並盡可能簡化每種語言的客戶端:它們只需要知道如何與本地代理通訊。Envoy[23] 作為代理對我們來說非常合適:它是一個經過實戰測試的開源產品,在行業中應用於高規模場景,擁有許多關鍵的彈性功能[24],以及良好的擴展點[25] ,以便我們需要時能擴展其功能。透過中央控制平面配置代理的能力[26] 是一個殺手級的功能:這允許我們動態配置客戶端負載平衡,就像它是中央負載平衡器一樣,但仍然避免了負載平衡器成為服務到服務請求路徑中的單點故障。

轉向服務網格

一旦認定我們決定轉向服務網格是正確的選擇,下一個問題就是:我們該如何進行遷移?我們確定了一些遷移的限制條件。首先:我們希望保留現有的介面。透過指定VIP 名稱加上安全服務的抽象化為我們提供了良好服務,我們不想破壞向後相容性。其次:我們希望自動化遷移,並盡可能無縫。這兩個限制意味著我們需要支援Envoy 中的Discovery 抽象,以便IPC 用戶端可以繼續在底層使用它。幸運的是,Envoy 已經有了 現成的抽象[27] 可以用。VIP 可以表示為Envoy 集群,代理可以從我們的控制平面使用集群發現服務(CDS) 取得它們。這些叢集中的主機表示為Envoy 端點,可以使用端點發現服務(EDS) 取得。

我們很快就遇到了一個無縫遷移的障礙:Envoy 要求在代理程式的配置中指定叢集。如果服務A 需要與集群B 和C 通信,那麼需要在A 的代理配置中定義集群B 和C。這在規模上可能具有挑戰性:任何給定的服務可能會與數十個叢集通信,而每個應用程式的叢集集合都是不同的。此外,Netflix 始終在變化:我們不斷推出新的項目,如直播、廣告[28] 和遊戲,並且不斷發展我們的架構。這意味著服務通訊的集群會隨著時間的推移而改變。鑑於我們可用的Envoy 原語,我們評估了一些填充群集配置的不同方法:

  1. 讓服務擁有者定義他們的服務需要與之通訊的叢集。這個選項看似簡單,但實際上,服務擁有者並不總是知道,或想要知道,他們與哪些服務通訊。服務通常會匯入由其他團隊提供的庫,這些庫在底層與多個其他服務通信,或與像遙測和日誌記錄等其他操作服務通信。這意味著服務擁有者需要知道這些輔助服務和函式庫是如何在底層實現的,並在它們發生變化時調整配置。
  2. 根據服務的呼叫圖自動產生Envoy 配置。這種方法對於預先存在的服務來說很簡單,但是在啟動新服務或添加新的上游叢集以進行通訊時具有挑戰性。
  3. 將所有群集推送到每個應用程式:這個選項以其簡單性吸引了我們,但是紙巾上的簡單計算很快向我們顯示,將數百萬個端點推送到每個代理是不可行的。

考慮到我們無縫採納的目標,每個選項都有足夠重大的缺點,使我們探索了另一個選項:如果我們能在運行時按需獲取集群信息,而不是預先定義它,會怎樣?當時,服務網格工作仍在啟動階段,只有少數幾個工程師在致力於它。我們聯繫了 Kinvolk[29],看看他們是否能與我們和Envoy 社群合作實施這個功能。這次合作的結果是 按需叢集發現[30](On-Demand Cluster Discovery,ODCDS)。有了這個功能,代理現在可以在第一次嘗試連接它時查找集群信息,而不是在配置中預先定義所有集群。

有了這個功能,我們需要給代理程式提供叢集資訊以供查詢。我們已經開發了一個實作Envoy XDS 服務的服務網格控制平面。然後我們需要從Eureka 獲取服務資訊以返回給代理。我們將Eureka 的VIP 和SVIP 表示為單獨的Envoy Cluster Discovery Service (CDS) 叢集(因此,服務 myservice 可能有叢集 myservice.vip

  1. 客戶端請求進入Envoy。
  2. 根據Host / :authority 頭(此處使用的頭可配置,但這是我們的方法)提取目標群集。如果已知該集群,請跳到步驟7。
  3. 叢集不存在,所以我們暫停了正在傳輸的請求。
  4. 向控制平面的Cluster Discovery Service (CDS) 端點發出請求。控制平面根據服務的配置和Eureka 註冊資訊產生客製化的CDS 回應。
  5. Envoy 取得叢集(CDS),觸發透過Endpoint Discovery Service (EDS) 拉取端點。根據該VIP 或SVIP 的Eureka 狀態資訊傳回群集的端點。
  6. 客戶端請求解除暫停。
  7. Envoy 正常處理請求:它使用負載平衡演算法選擇一個端點並發出請求。

這個流程在幾毫秒內完成,但只在對叢集的第一次請求時。之後,Envoy 的行為就好像群集是在配置中定義的一樣。關鍵是,該系統允許我們無需任何配置即可無縫遷移服務至服務網格,滿足我們的主要採納限制之一。我們呈現的抽象繼續是VIP 名稱加上安全,我們可以透過配置單一IPC 用戶端連接到本地代理而不是直接連接到上游應用程式來遷移到網格。我們繼續使用Eureka 作為VIP 和實例狀態的真實來源,這使得我們能夠在遷移時支援某些應用程式在網格上,而另一些不在網格上的異質環境。還有一個額外的好處:我們可以透過僅為我們實際通訊的叢集取得資料來保持Envoy 的記憶體使用率較低。

圖片圖片

上圖展示了一個Java 應用程式中的IPC 用戶端透過Envoy 與註冊為SVIP A 的主機通訊。Envoy 從網格控制平面取得SVIP A 的叢集和端點資訊。網格控制平面從Eureka 取得主機資訊。

按需獲取此資料的缺點是:這會增加對叢集的第一次請求的延遲。我們遇到了服務在第一次請求時需要非常低延遲存取的用例,並且增加了幾毫秒額外的開銷。對於這些用例,服務需要預先定義它們通訊的集群,或在第一次請求之前準備好連接。我們也考慮過根據歷史請求模式在代理程式啟動時從控制平面預推送叢集。總的來說,我們覺得系統中的降低的複雜性證明了對少量服務的缺點是合理的。

我們在服務網​​格之旅的初期。現在我們真誠地使用它,我們希望與社區合作做出更多的Envoy 改進。將我們的 自適應並發限制[31] 實現移植到Envoy 是一個很好的開始- 我們期待與社區在更多方面合作。我們特別對社區在增量EDS 方面的工作感興趣。EDS 端點佔了更新量的最大部分,這對控制平面和Envoy 造成了不必要的壓力。

我們要非常感謝Kinvolk 的人員對Envoy 的貢獻:Alban Crequy, Andrew Randall, Danielle Tal, 特別是Krzesimir Nowak 的出色工作。我們也要感謝Envoy 社群的支持和尖銳的審查:Adi Peleg, Dmitri Dolguikh, Harvey Tuch, Matt Klein, 和Mark Roth。與你們所有人合作是一次很好的經驗。

這是我們通往服務網格之旅的系列文章的第一篇,敬請期待。

參考資料

[1] 

具有按需叢集發現功能的零配置服務網格:https://netflixtechblog.com/zero-configuration-service-mesh-with-on-demand-cluster-discovery-ac6483b52a51

[2] Netflix的串流媒體完全運行在AWS: https://netflixtechblog.com/four-reasons-we-choose-amazons-cloud-as-our-computing-platform-4aceb692afec

[3] CNCF:https://www.cncf.io/

[4] 2012年聖誕節的宕機: https://netflixtechblog.com/a-closer-look-at-the-christmas-eve-outage-d7b409a529ee

[5] 我們建構了Eureka: https://netflixtechblog.com/netflix-shares-cloud-load-balancing-and-failover-tool-eureka-c10647ef95e5

[6] Ribbon(內部名稱為NIWS)用於IPC: https://netflixtechblog.com/announcing-ribbon-tying-the-netflix-mid-tier-services-together-a89346910a62

[7]GraphQL:https://netflixtechblog.com/how-netflix-scales-its-api-with-graphql-federation-part-1-ae3557c187e2

[8] gRPC:https://netflixtechblog.com/practical-api-design-at-netflix-part-1-using-protobuf-fieldmask-35cfdc606518

[9]node.js:https://netflixtechblog.com/debugging-node-js-in-Production-75901bb10f2d

[10]Python:https://netflixtechblog.com/python-at-netflix-bba45dae649e

[11] 自適應併發限制: https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581

[12] 斷路器: https://netflixtechblog.com/making-the-netflix-api-more-resilient-a8ec62159c2d

[13]特使:https://www.envoyproxy.io/

[14] 許多關鍵的彈性功能: https://github.com/envoyproxy/envoy/issues/7789

[15] 良好的擴充點: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/wasm_filter.html

[16] 透過一個集中的控制平面配置代理: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration

[17]GraphQL:https://netflixtechblog.com/how-netflix-scales-its-api-with-graphql-federation-part-1-ae3557c187e2

[18] gRPC:https://netflixtechblog.com/practical-api-design-at-netflix-part-1-using-protobuf-fieldmask-35cfdc606518

[19]node.js:https://netflixtechblog.com/debugging-node-js-in-Production-75901bb10f2d

[20]Python:https://netflixtechblog.com/python-at-netflix-bba45dae649e

[21] 自適應併發限制: https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581

[22] 熔斷: https://netflixtechblog.com/making-the-netflix-api-more-resilient-a8ec62159c2d

[23]特使:https://www.envoyproxy.io/

[24] 許多關鍵的彈性功能: https://github.com/envoyproxy/envoy/issues/7789

[25] 良好的擴充點: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/wasm_filter.html

[26] 中央控制平面配置代理的能力: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration

[27] 現成的抽象: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/terminology

[28] 廣告: https://netflixtechblog.com/ensuring-the-successful-launch-of-ads-on-netflix-f99490fdf1ba

[29]金沃克:https://kinvolk.io/

[30] 按需群集發現: https://github.com/envoyproxy/envoy/pull/18723

[31] 自適應併發限制: https://github.com/envoyproxy/envoy/issues/7789