大規(guī)模服務(wù)網(wǎng)格性能優(yōu)化 | Aeraki xDS 按需加載
Istio 在大規(guī)模場景下 xDS 性能瓶頸
xDS 是 istio 控制面和數(shù)據(jù)面 envoy 之間的通信協(xié)議,x 表示包含多種協(xié)議的集合,比如:LDS 表示監(jiān)聽器,CDS 表示服務(wù)和版本,EDS 表示服務(wù)和版本有哪些實例,以及每個服務(wù)實例的特征,RDS 表示路由??梢院唵蔚陌?xDS 理解為,網(wǎng)格內(nèi)的服務(wù)發(fā)現(xiàn)數(shù)據(jù)和治理規(guī)則的集合。xDS 數(shù)據(jù)量的大小和網(wǎng)格規(guī)模是正相關(guān)的。
當(dāng)前 istio 下發(fā) xDS 使用的是全量下發(fā)策略,也就是網(wǎng)格里的所有 sidecar,內(nèi)存里都會有整個網(wǎng)格內(nèi)所有的服務(wù)發(fā)現(xiàn)數(shù)據(jù)。比如下圖,雖然 workload 1 在業(yè)務(wù)邏輯上只依賴 service 2, 但是 istiod 會把全量的服務(wù)發(fā)現(xiàn)數(shù)據(jù)(service 2、3、4)都發(fā)送給 workload 1。
這樣的結(jié)果是,每個 sidecar 內(nèi)存都會隨著網(wǎng)格規(guī)模增長而增長,下圖是我們對網(wǎng)格規(guī)模和內(nèi)存消耗做的一個性能測試,x 軸是網(wǎng)格規(guī)模,也就是包含多少個服務(wù)實例,y 軸是單個 envoy 的內(nèi)存消耗。
可以看出,如果網(wǎng)格規(guī)模超過 1萬個實例,單個 envoy 的內(nèi)存超過了 250 兆,而整個網(wǎng)格的開銷還要再乘以網(wǎng)格規(guī)模大小。

Istio 當(dāng)前優(yōu)化方案
針對這個問題,社區(qū)提供了一個方案,就是 Sidecar[1] 這個 CRD,這個配置可以顯式的定義服務(wù)之間的依賴關(guān)系,或者說可見性關(guān)系。比如下圖這個配置的意思就是 workload 1 只依賴 service 2 ,這樣配置以后,istiod 只會下發(fā) service 2 的信息給 workload 1。

這個方案本身是有效的。但這種方式在大規(guī)模場景下很難落地:首先這個方案需要用戶提前配置服務(wù)間完整的依賴關(guān)系,大規(guī)模場景下的服務(wù)依賴關(guān)系很難梳理清楚,而且通常依賴關(guān)系也會隨著業(yè)務(wù)的變化而變化。
Aeraki Lazy xDS
針對上述問題,TCM[2] 團隊設(shè)計了一套無入侵的 xDS 按需加載方案,并開源到 github Aeraki[3] 項目,這是 Lazy xDS 具體的實現(xiàn)細(xì)節(jié):

我們在網(wǎng)格里增加2個組件,一個是 Lazy xDS Egress,Egress 充當(dāng)類似網(wǎng)格模型中默認(rèn)網(wǎng)關(guān)角色,另一個是 Lazy xDS Controller,用來分析并補全服務(wù)間的依賴關(guān)系。
-
首先配置 Egress 的服務(wù)中轉(zhuǎn)能力:Egress 會獲取網(wǎng)格內(nèi)所有服務(wù)信息,并配置所有 HTTP 服務(wù)的路由,這樣充當(dāng)默認(rèn)網(wǎng)關(guān)的 Egress 就可以轉(zhuǎn)發(fā)網(wǎng)格內(nèi)任意 HTTP 服務(wù)的流量。
-
第2步,對于開啟了按需加載特性的服務(wù)(圖中 Workload 1),利用 envoyfilter,將其訪問網(wǎng)格內(nèi) http 服務(wù)的流量,都路由到 egress。
-
第3步,利用 istio sidecar CRD,限制 Workload 1 的服務(wù)可見性。
-
經(jīng)過步驟3后,Workload 1 初始只會加載最小化的 xDS。
-
當(dāng) Workload 1 發(fā)起對 Service 2 的訪問時,(因為步驟2)流量會轉(zhuǎn)發(fā)到 Egress。
-
(因為步驟 1)Egress 會分析接收到的流量特征,并將流量轉(zhuǎn)發(fā)到 Service 2。
-
Egress 會將訪問日志,異步地上報給 Lazy xDS Controller,上報服務(wù)是利用 Access Log Service[4]。
-
Lazy xDS Controller 會對接收到的日志進行訪問關(guān)系分析,然后把新的依賴關(guān)系(Workload 1 -> Service 2)表達到 sidecar CRD 中。
-
同時 Controller 還會將(步驟2) Workload 1 需要轉(zhuǎn)發(fā) Service 2 流量到 Egress 的規(guī)則去除,這樣未來 workload 1 再次訪問 Service 2 就會是直連。
-
(因為步驟 8)istiod 更新可見性關(guān)系,后續(xù)會將 Service 2 的服務(wù)信息發(fā)給 Workload 1。
-
Workload 1 通過 xDS 接收到 Service 2 的服務(wù)信息。
-
當(dāng) Workload 1 再次發(fā)起對 Service 2 的訪問,流量會直達 Service 2(因為步驟9)。
這個方案的好處:
-
首先不需要用戶提前配置服務(wù)間的依賴,而且服務(wù)間依賴是允許動態(tài)的增加的。
-
最終每個 envoy 只會獲得自身需要的 xDS,性能最優(yōu)。
-
這個實現(xiàn)對用戶流量影響也比較小,用戶的流量不會阻塞。性能損耗也比較小,只有前幾次請求會在 Egress 做中轉(zhuǎn),后面都是直連的。
-
此方案對 istio 和 envoy 沒有任何入侵,我們沒有修改 istio/envoy 源碼,使得這套方案能很好的適應(yīng)未來 istio 的迭代。
目前我們只支持七層協(xié)議服務(wù)的按需加載,原因是流量在 Egress 這里中轉(zhuǎn)的時候,Egress 需要通過七層協(xié)議里的 header 判斷原始目的地。純 TCP 協(xié)議是沒有辦法設(shè)置額外的 header。不過因為 istio 主要目的就是為了做七層流量的治理,當(dāng)網(wǎng)格的大部分請求都是七層的,這個情況目前可以接受的。
Lazy xDS 性能測試
測試方案

在同一網(wǎng)格內(nèi)的不同 namespace 中,我們創(chuàng)建了 2 組 book info,左邊 namespace lazy-on 中 productpage 開啟按需加載,右邊 namespace lazy-off 保持默認(rèn)情況。
然后在這個網(wǎng)格內(nèi),我們逐漸增加服務(wù)數(shù)量,使用的是 istio 官方負(fù)載測試工具集[5](以下簡稱「負(fù)載服務(wù)」),每個 namespace 里有 19 個服務(wù), 其中4個 tcp 服務(wù),15個 http 服務(wù),每個服務(wù)初始 pod 數(shù)目為 5,共95個 pod(75 個http,20 個tcp)。我們逐漸增加負(fù)載服務(wù)的 namespace 數(shù)量, 用于模擬網(wǎng)格規(guī)模增長。
性能對比
首先是 CDS 和 EDS 的對比,下圖每組數(shù)據(jù)代表負(fù)載服務(wù) namespace 的增加,每組數(shù)據(jù)里 4 個值:前 2 個值是開啟按需加載后的 CDS 和 EDS,后面 2個值是沒開啟按需加載的 CDS 和 EDS。
接下來是內(nèi)存對比,綠色數(shù)據(jù)表示開啟按需加載后 envoy 的內(nèi)存消耗,紅色的是未開啟的情況。900 pods 規(guī)模 mesh,envoy 內(nèi)存減少 14M ,降低比例約 40%;一萬 pods 規(guī)模 mesh,envoy 內(nèi)存減少約 150M,降低比例約 60%。
隨著服務(wù)可見性的限制,envoy 不會再接收全量的 xDS 更新,下圖是在測試周期內(nèi) envoy 接收到 CDS 更新次數(shù)的對比,開啟按需加載后,更新次數(shù)從 6 千次降低到了 1 千次。
小結(jié)
Lazy xDS 已經(jīng)在 github 開源,請訪問 lazyxds README[6]了解如何使用。
Lazy xDS 功能還在持續(xù)演進,未來我們將支持多集群模式、ServiceEntry 按需加載等功能。