Netflix 零配置服務網(wǎng)格與按需集群發(fā)現(xiàn)
本文翻譯自由 David Vroom, James Mulcahy, Ling Yuan, Rob Gulewich 編寫的 Netflix 博客 Zero Configuration Service Mesh with On-Demand Cluster Discovery[1]。
Netflix 相信大家并不陌生,在 Spring Cloud 生態(tài)中就有 Netflix 全家桶。多年前,我也曾將基于 Netflix OSS 構建的微服務架構搬上了 Kubernetes 平臺,并持續(xù)折騰好幾年。
Spring Cloud Netflix 有著龐大的用戶群以及用戶場景,其提供了微服務治理的一整套解決方案:服務發(fā)現(xiàn) Eureka、客戶端負載均衡 Ribbon、斷路器 Hystrix、微服務網(wǎng)格 Zuul。
有過相同經(jīng)歷的小伙伴應該都會同感,這樣一套微服務解決方案的架構,經(jīng)過多年的演進也會讓人痛苦不堪:復雜度越來越高、版本碎片化嚴重、多語言多框架的支持和功能無法統(tǒng)一等等。這也是 Netflix 自己也不得不面對的問題,隨后他們將目光轉向了服務網(wǎng)格,并尋求一個無縫遷移的方案。
這篇文章中,他們給出了答案:Netflix 與社區(qū)合作,并自建控制平面與已有的服務發(fā)現(xiàn)體系兼容。這套實現(xiàn)下來可能并不容易,也未看到他們要將其開源的想法,但是至少可以給大家一個參考。
順便預告一下自己正在計劃的一篇,如何將微服務平滑遷移到 Flomesh 服務網(wǎng)格平臺。不止無縫兼容 Eureka,還有 HashiCorp Consul,未來還會兼容更多的服務發(fā)現(xiàn)方案。
以下是原文的翻譯:
在這篇文章中,我們將討論 Netflix 服務網(wǎng)格(Service Mesh)實踐的相關信息:歷史背景、動機,以及我們?nèi)绾闻c Kinvolk 和 Envoy 社區(qū)合作,在復雜的微服務環(huán)境中推動服務網(wǎng)格落地的一個特征:按需集群發(fā)現(xiàn)(On-demand Cluster Discovery)。
Netflix 的 IPC 簡史
Netflix 是早期云計算的采納者,特別是對于大規(guī)模的公司:我們在 2008 年開始了遷移,并且到 2010 年,Netflix的流媒體完全運行在AWS上[2]。今天我們擁有豐富的工具,包括開源和商業(yè)的,所有這些都是為云原生環(huán)境設計的。然而,在 2010 年,幾乎沒有這樣的工具存在:CNCF[3] 直到 2015 年才成立!由于沒有現(xiàn)成的解決方案,我們需要自己構建它們。
對于服務間的進程間通信(IPC),我們需要一個中間層負載均衡器通常提供的豐富功能集。我們還需要一種解決方案,來應對云環(huán)境的現(xiàn)實:一個高度動態(tài)的環(huán)境,其中節(jié)點不斷上線和下線,服務需要快速響應變化并隔離失敗。為了提高可用性,我們設計了可以單獨發(fā)生故障的系統(tǒng)組件,以避免單點故障。這些設計原則引導我們走向了客戶端負載均衡,而 2012年圣誕節(jié)的宕機[4] 進一步堅定了這個決策。在云早期,我們構建了Eureka[5] 用于服務發(fā)現(xiàn),以及 Ribbon(內(nèi)部名為NIWS)用于IPC[6]。Eureka 解決了服務如何發(fā)現(xiàn)與其通信的實例的問題,而 Ribbon 提供了負載均衡的客戶端,以及許多其他的彈性特性。這兩項技術,加上一系列其他的彈性和混沌工具,產(chǎn)生了巨大的收益:我們的可靠性因此得到了顯著的改善。
Eureka 和 Ribbon 提供了一個簡單而強大的接口,讓它們的使用變得容易。一個服務與另一個服務通信,需要知道兩件事:目的服務的名稱,以及流量是否應該是安全的。Eureka 為此提供的抽象是虛擬 IP(VIPs)用于非安全通信,和安全 VIPs(SVIPs)用于安全通信。服務向 Eureka 宣布一個 VIP 名和端口(例如:_myservice_,端口 _8080_),或一個 SVIP 名和端口(例如:_myservice-secure_,端口 8443),或同時使用兩者。IPC 客戶端針對該 VIP 或 SVIP 進行實例化,而 Eureka 客戶端代碼通過從 Eureka 服務器獲取它們來處理該 VIP 到一組 IP 和端口對的轉換??蛻舳诉€可以選擇啟用像重試或熔斷這樣的 IPC 功能,或者使用一組合理的默認值。
圖片
在這種架構中,服務間通信不再通過負載均衡器的單點故障通道。但問題是,Eureka 現(xiàn)在成為了 VIP 注冊主機真實性的新的單一故障點。然而,如果 Eureka 宕機,服務仍然可以互相通信,盡管它們的主機信息隨著 VIP 的上下線而逐漸過時。在故障期間以降級但可用的狀態(tài)運行仍然是相比完全停止流量的明顯改進。
在這種架構中,服務與服務間的通信不再經(jīng)過負載均衡器的單一故障點。但問題是,Eureka 現(xiàn)在成為了 VIP 注冊主機真實性的新的單一故障點。然而,如果 Eureka 宕機,服務仍然可以互相通信,盡管它們的主機信息隨著 VIP 的上下線而逐漸過時。在故障期間以降級但可用的狀態(tài)運行仍然是相比完全停止流量的明顯改進。
為什么選擇網(wǎng)格?
上述架構在過去的十年里為我們服務得很好,但隨著業(yè)務需求的變化和行業(yè)標準的演變,我們的 IPC 生態(tài)系統(tǒng)在很多方面都增加了更多的復雜性。首先,我們增加了不同的 IPC 客戶端的數(shù)量。我們的內(nèi)部 IPC 流量現(xiàn)在是純 REST、GraphQL[7] 和 gRPC[8] 的混合。其次,我們從只使用 Java 環(huán)境遷移到了多語言環(huán)境:我們現(xiàn)在也支持 node.js[9]、Python[10] 以及各種開源和現(xiàn)成的軟件。第三,我們繼續(xù)為 IPC 客戶端增加更多功能,如 自適應并發(fā)限制[11]、斷路器[12]、對沖和故障注入已成為我們工程師為使系統(tǒng)更可靠而采用的標準工具。與十年前相比,我們現(xiàn)在支持更多功能、更多語言、更多客戶端。確保所有這些實現(xiàn)之間的功能一致性并確保它們都以相同的方式運行是具有挑戰(zhàn)性的:我們希望這些功能有一個單一、經(jīng)過充分測試的實現(xiàn),這樣我們可以在一個地方進行更改和修復錯誤。
這就是服務網(wǎng)格的價值所在:我們可以在一個實現(xiàn)中集中 IPC 功能,并使每種語言的客戶端盡可能簡單:它們只需要知道如何與本地代理通話。Envoy[13] 對我們來說是代理的絕佳選擇:它是一個經(jīng)過戰(zhàn)斗考驗的開源產(chǎn)品,已經(jīng)在行業(yè)中被廣泛使用,擁有 許多關鍵的彈性功能[14],以及當我們需要擴展其功能時的 良好的擴展點[15]。能夠 通過一個集中的控制平面配置代理[16] 是一個殺手級的功能:這使我們可以動態(tài)配置客戶端負載均衡,就像它是一個集中的負載均衡器,但仍然避免了服務到服務請求路徑中的負載均衡器作為單一的故障點。
為什么選擇網(wǎng)格?
過去十年,上述架構已經(jīng)為我們提供了良好的服務,盡管不斷變化的業(yè)務需求和行業(yè)標準的演進使我們的 IPC 生態(tài)系統(tǒng)變得更為復雜。首先,我們增加了不同 IPC 客戶端的數(shù)量。目前,我們的內(nèi)部 IPC 流量包含了簡單的 REST,GraphQL[17],和 gRPC[18]。其次,我們從僅 Java 環(huán)境轉變?yōu)槎嗾Z言環(huán)境:現(xiàn)在我們還支持 node.js[19],Python[20],以及各種開源和現(xiàn)成的軟件。第三,我們繼續(xù)為 IPC 客戶端增加更多功能:諸如 自適應并發(fā)限制[21]、熔斷[22]、hedging 和故障注入等功能已成為我們的工程師用來提高系統(tǒng)可靠性的標準工具。與十年前相比,我們現(xiàn)在在更多的語言、更多的客戶端中支持更多的功能。保持所有這些實現(xiàn)之間的功能一致性,確保它們的行為保持一致是具有挑戰(zhàn)性的:我們想要的是所有這些功能的單一、經(jīng)過良好測試的實現(xiàn),以便我們能在一個地方進行變更和修復錯誤。
這就是服務網(wǎng)格的作用所在:我們可以將 IPC 功能集中在單一的實現(xiàn)中,并盡可能簡化每種語言的客戶端:它們只需要知道如何與本地代理通信。Envoy[23] 作為代理對我們來說非常合適:它是一個經(jīng)過實戰(zhàn)測試的開源產(chǎn)品,在行業(yè)中應用于高規(guī)模場景,擁有 許多關鍵的彈性功能[24],以及 良好的擴展點[25],以便我們需要時能擴展其功能。通過 中央控制平面配置代理的能力[26] 是一個殺手級的功能:這允許我們動態(tài)配置客戶端負載均衡,就像它是中央負載均衡器一樣,但仍然避免了負載均衡器成為服務到服務請求路徑中的單點故障。
轉向服務網(wǎng)格
一旦認定我們決定轉向服務網(wǎng)格是正確的選擇,下一個問題便是:我們應如何進行遷移?我們確定了一些遷移的限制條件。首先:我們希望保留現(xiàn)有的接口。通過指定 VIP 名稱加上安全服務的抽象為我們提供了良好服務,我們不想破壞向后兼容性。其次:我們希望自動化遷移,并使其盡可能無縫。這兩個限制意味著我們需要支持 Envoy 中的 Discovery 抽象,以便 IPC 客戶端可以繼續(xù)在底層使用它。幸運的是,Envoy 已經(jīng)有了 現(xiàn)成的抽象[27] 可以用。VIP 可以表示為 Envoy 集群,代理可以從我們的控制平面使用集群發(fā)現(xiàn)服務 (CDS) 獲取它們。這些集群中的主機表示為 Envoy 端點,可以使用端點發(fā)現(xiàn)服務 (EDS) 獲取。
我們很快遇到了一個無縫遷移的障礙:Envoy 要求在代理的配置中指定集群。如果服務 A 需要與集群 B 和 C 通信,那么需要在 A 的代理配置中定義集群 B 和 C。這在規(guī)模上可能具有挑戰(zhàn)性:任何給定的服務可能會與數(shù)十個集群通信,而每個應用程序的集群集合都是不同的。此外,Netflix 始終在變化:我們不斷推出新的項目,如直播、廣告[28] 和游戲,并且不斷發(fā)展我們的架構。這意味著服務通信的集群會隨著時間的推移而改變。鑒于我們可用的 Envoy 原語,我們評估了一些填充集群配置的不同方法:
- 讓服務所有者定義他們的服務需要與之通信的集群。這個選項看似簡單,但實際上,服務所有者并不總是知道,或想要知道,他們與哪些服務通信。服務通常會導入由其他團隊提供的庫,這些庫在底層與多個其他服務通信,或與像遙測和日志記錄等其他操作服務通信。這意味著服務所有者需要知道這些輔助服務和庫是如何在底層實現(xiàn)的,并在它們發(fā)生變化時調(diào)整配置。
- 根據(jù)服務的調(diào)用圖自動生成 Envoy 配置。這種方法對于預先存在的服務來說很簡單,但是在啟動新服務或添加新的上游集群以進行通信時具有挑戰(zhàn)性。
- 將所有集群推送到每個應用程序:這個選項以其簡單性吸引了我們,但是紙巾上的簡單計算很快向我們顯示,將數(shù)百萬個端點推送到每個代理是不可行的。
考慮到我們無縫采納的目標,每個選項都有足夠重大的缺點,使我們探索了另一個選項:如果我們能在運行時按需獲取集群信息,而不是預先定義它,會怎樣?當時,服務網(wǎng)格工作仍在啟動階段,只有少數(shù)幾個工程師在致力于它。我們聯(lián)系了 Kinvolk[29],看看他們是否能與我們和 Envoy 社區(qū)合作實施這個功能。這次合作的結果是 按需集群發(fā)現(xiàn)[30](On-Demand Cluster Discovery,ODCDS)。有了這個功能,代理現(xiàn)在可以在第一次嘗試連接它時查找集群信息,而不是在配置中預先定義所有集群。
有了這個功能,我們需要給代理提供集群信息以供查詢。我們已經(jīng)開發(fā)了一個實現(xiàn) Envoy XDS 服務的服務網(wǎng)格控制平面。然后我們需要從 Eureka 獲取服務信息以返回給代理。我們將 Eureka 的 VIP 和 SVIP 表示為單獨的 Envoy Cluster Discovery Service (CDS) 集群(因此,服務 myservice 可能有集群 myservice.vip
- 客戶端請求進入 Envoy。
- 根據(jù) Host / :authority 頭(此處使用的頭可配置,但這是我們的方法)提取目標集群。如果已知該集群,跳到步驟 7。
- 集群不存在,所以我們暫停了正在傳輸?shù)恼埱蟆?/li>
- 向控制平面的 Cluster Discovery Service (CDS) 端點發(fā)出請求??刂破矫娓鶕?jù)服務的配置和 Eureka 注冊信息生成定制的 CDS 響應。
- Envoy 獲取集群(CDS),觸發(fā)通過 Endpoint Discovery Service (EDS) 拉取端點。根據(jù)該 VIP 或 SVIP 的 Eureka 狀態(tài)信息返回集群的端點。
- 客戶端請求解除暫停。
- Envoy 正常處理請求:它使用負載平衡算法選擇一個端點并發(fā)出請求。
這個流程在幾毫秒內(nèi)完成,但只在對集群的第一次請求時。之后,Envoy 的行為就好像集群是在配置中定義的一樣。關鍵是,該系統(tǒng)允許我們無需任何配置即可無縫遷移服務至服務網(wǎng)格,滿足我們的主要采納限制之一。我們呈現(xiàn)的抽象繼續(xù)是 VIP 名稱加上安全,并且我們可以通過配置單個 IPC 客戶端連接到本地代理而不是直接連接到上游應用程序來遷移到網(wǎng)格。我們繼續(xù)使用 Eureka 作為 VIP 和實例狀態(tài)的真實來源,這使得我們能夠在遷移時支持一些應用程序在網(wǎng)格上,而另一些不在網(wǎng)格上的異構環(huán)境。還有一個額外的好處:我們可以通過僅為我們實際通信的集群獲取數(shù)據(jù)來保持 Envoy 的內(nèi)存使用率較低。
圖片
上圖展示了一個 Java 應用中的 IPC 客戶端通過 Envoy 與注冊為 SVIP A 的主機通信。 Envoy 從網(wǎng)格控制平面獲取 SVIP A 的集群和端點信息。網(wǎng)格控制平面從 Eureka 獲取主機信息。
按需獲取此數(shù)據(jù)的缺點是:這會增加對集群的第一次請求的延遲。我們遇到了服務在第一次請求時需要非常低延遲訪問的用例,并且增加了幾毫秒額外的開銷。對于這些用例,服務需要預定義它們通信的集群,或在第一次請求之前準備好連接。我們還考慮過根據(jù)歷史請求模式在代理啟動時從控制平面預推送集群??偟膩碚f,我們覺得系統(tǒng)中的降低的復雜性證明了對少量服務的缺點是合理的。
我們在服務網(wǎng)格之旅的初期?,F(xiàn)在我們真誠地使用它,我們希望與社區(qū)合作做出更多的 Envoy 改進。將我們的 自適應并發(fā)限制[31] 實現(xiàn)移植到 Envoy 是一個很好的開始 - 我們期待著與社區(qū)在更多方面合作。我們特別對社區(qū)在增量 EDS 方面的工作感興趣。EDS 端點占了更新量的最大部分,這對控制平面和 Envoy 造成了不必要的壓力。
我們要非常感謝 Kinvolk 的人員對 Envoy 的貢獻:Alban Crequy, Andrew Randall, Danielle Tal, 特別是 Krzesimir Nowak 的出色工作。我們也要感謝 Envoy 社區(qū)的支持和鋒利的審查:Adi Peleg, Dmitri Dolguikh, Harvey Tuch, Matt Klein, 和 Mark Roth。與你們所有人合作是一次很好的經(jīng)歷。
這是我們通向服務網(wǎng)格之旅的系列文章的第一篇,敬請期待。
參考資料
[1]
Zero Configuration Service Mesh with On-Demand Cluster Discovery: 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年圣誕節(jié)的宕機: 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(內(nèi)部名為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] 自適應并發(fā)限制: https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581
[12] 斷路器: https://netflixtechblog.com/making-the-netflix-api-more-resilient-a8ec62159c2d
[13] Envoy: 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] 自適應并發(fā)限制: https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581
[22] 熔斷: https://netflixtechblog.com/making-the-netflix-api-more-resilient-a8ec62159c2d
[23] Envoy: 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] 現(xiàn)成的抽象: 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] Kinvolk: https://kinvolk.io/
[30] 按需集群發(fā)現(xiàn): https://github.com/envoyproxy/envoy/pull/18723
[31] 自適應并發(fā)限制: https://github.com/envoyproxy/envoy/issues/7789





























