知乎:如何在大規模叢集下使用 istio 升級微服務架構

語言: CN / TW / HK

為進一步加強技術交流,推進雲原生生態共建,6 月 28 日下午,首屆雲原生實踐者大會在線上線下同步舉辦。來自作業幫、知乎、轉轉、58、同程等多家科技企業的數十名研發人員,以及中國信通院雲大所的專家共同參與了此次技術研討沙龍。

研討會上,知乎核心架構平臺開發工程師謝楚瑜講述了“知乎的 istio 之旅”,與參會者分享了知乎如何在大規模叢集下使用 istio 升級微服務架構,具體包括如何使 istio 管理的服務和現存服務之間可以互相通訊、知乎的 istio 遷移都遇到和解決過哪些問題、Service Mesh 如何為業務提供了幫助,以及知乎如何在大規模叢集中優化效能指標等。

以下,是謝楚瑜的分享。

大家好,我是謝楚瑜,這次主要是和大家分享下知乎是如何進行 Service Mesh 改造的、在改造期間有遇到了哪些問題,以及隨著規模地不斷擴大,我們又解決了哪些新的問題。

現狀

先看一下 Service Mesh 在知乎目前的情況。

目前,我們的核心業務以及數百個周邊業務都已經完成遷移。在每天的高峰期,Service Mesh 中的流量可以達到百萬級別的 rps。在指標維度也已經達到了百萬級別。我們服務治理的各種功能,如壓測、鑑權、限流和斷路等也通過 Service Mesh 完成了相容和重構。

然後說下遷移的相容方案。知乎在早期的時候就已經完成了容器化改造。那時候,我們其實還沒有用上 K8s,是自己做了一套基於負載均衡以及服務發現的服務通訊發現。因此,在遷移 Service Mesh 時,我們必須首先提供一套方案即可以相容以前的服務通訊,又相容舊方案的服務治理功能,同時遷移過程對業務無感知,如果有大規模故障還可以快速回滾到舊方案。

為了滿足上述需求,我們首先做的就是梳理現有的服務呼叫流程。

以前,客戶端會直接通過註冊中心目標服務的負載均衡地址進行訪問。但在雲原生環境的做法應該是通過 coreDNS 查詢它的一個 ServiceIP,通過這個 ServiceIP 進行服務間通訊。如果是在 Service Mesh 裡的話,訪問 serviceIP 時會直接通過 Envoy 發現一個合適的服務端例項,直接從客戶端打到服務端。

考慮到對舊方案的相容,我們決定對服務發現元件進行改造,增加一個服務發現的代理。如果是上了 Mesh 的服務訪問沒上 Mesh 的服務,那麼它其實和以前是一樣的,仍然通過舊的負載均衡方案去呼叫。而對於客戶端、服務端都是在 Mesh 環境下服務呼叫,這個代理將會返回 ServiceIP,這樣一來就不需要做 SDK 層面的修改,可以直接相容舊的負載均衡方案和新的 Service Mesh 呼叫了。這樣有一個好處,如果我們需要切換到舊方案,比如緊急回滾的情況,只需要把這個代理上面的配置修改下就可以直接切回過往的一套流量方式。

除此之外,關於鑑權和限流這些功能,我們通過一些 Controller 實現了一套同步方案,然後通過 ServiceMesh 的能力實現了相容。然後關於從零上 Mesh 其實還有很多改造的點,比如限流。我們上一次 IstioCon 的分享有比較詳細的描述,這次暫時不贅述了。

在遷移期間,我們有遇到過很多的問題。就像我前面提到的,我們當時有一種集中式的代理方案,在切換到 Mesh 以後,Mesh 其實會給每個業務容器例項後加上一個 Sidecar,這樣相當於說,它的連線方式從以前一種集中式的代理訪問,變成一種點對點的模式。

這會直接導致服務端收到併發數發生很明顯的變化,很多效能比較敏感的服務也會發生改變。關於這個問題,我們通過一些指標去做連線池的適配,對於延時變化非常敏感的服務,我們會做一些人工處理。

然後是連線管理帶來的一些差異。比如我們以前那一套負載均衡方案,會在客戶端斷開連線以後,仍然保持跟服務端的連線。然而在 Istio 中,如果客戶端連線斷開,那麼它和服務端連線也就斷了,這導致我們的業務會在監控上看到一些不一樣的錯誤,如 Golang 服務會看到更多的 Context Cancel 這類錯誤。對於這種情況,我們是通過 SDK 對異常的採集做適配來解決。

還有一個比較核心的點。在 Istio 中,每次讀取指標檔案都可能在記憶體裡面同時寫兩份完整的指標配置。當一個例項的指標檔案達到上百兆的時候,對 Sidecar 的效能影響是非常大的。我在後面會具體提到指標這塊我們是怎麼解決的。

業務應用

在做好了基礎的相容和適配以後,剩下就是考慮怎麼樣讓業務應用起來。

考慮到 Istio 物件的複雜度非常高,一上來如果直接看 VS、DR,還有 Authpolice、Service Entry 這些東西的話,很難直接用起來。另外,每一個配置都可能會因為需求的複雜而進行重複配置,比如一個 vs 的配置裡面有時可能會加一個故障注入改一下,然後又加一個重試規則又改一下。

基於這個問題,我們實現了一個類似於 Envoyfilter 一樣的 Crd、Istiofilter。通過這個 crd,我們可以對 Istio 應用一種 overlay 形式配置。這樣我們可以通過配置多個 Istiofilter 同時管理同一個 vs 配置。如果中間某些配置不要了,比如不要流量映象了,就只需要刪掉對應的 IstioFilter,隨後 vs 上面就只會減少這一個 mirror 的配置,其他配置仍然可以正常使用。目前這一套 Istiofilter 也是有開源版本的,可以直接在 GitHub 上找到。

除此之外,我們 Mesh 的所有能力目前都是直接面向業務研發開放使用的。目前大部分功能都是基於介面這一種粒度進行配置。他們可以通過指標或者自己手動在介面配置介面上配置他們的一些介面定義,在完善介面配置以後就可以在上面使用一些故障注入和限流功能。

考慮到自身業務的特殊性,我們並沒有全面使用 Istio 原生提供的功能。比如,我們自己實現了流量映象功能,它其實可以支援超過 100%這種比例的配置,這樣就可以拿線上的流量進行壓測,也可以將壓力都集中在需要測試的服務端例項上,避免影響其他的客戶端應用。

前面提到我們大概有百萬級的 Rps,同時也已經有 10 萬+的例項執行在上面,這樣就會有一些新的問題。比如像 Ipvs 上如果 Service 數量太多,一些採集工作會導致它在機器上面軟中斷時間變長,進而從業務角度觀察會有一些網路延遲。最終我們是通過 livepatch 關閉了特性來解決的這個問題。

然後是做了 DNS 優化。前面提到,我們當時是用了自己的一套服務發現體系,沒有用 coreDNS。然後我們在切到 Mesh 的時候發現,當時的 coreDNS 比較難以支撐我們的效能需求。於是我們自己搭建了一套 local Dns,每個 K8s 節點放一個 DNS 例項,同時結合這一個 Istio 的 smartdns,減少了由於服務域名沒有填寫完整而重複查詢 DNS 的次數,這樣儘量優化 DNS 在服務呼叫中的時間開銷。

關於 Istio 的引數配置,首先由於叢集規模較大、服務更新頻繁,對於一些服務來說可能配置推送會非常頻繁,推送內容也非常比較大,這樣的話推送配置非常容易超時。所以,我們根據叢集規模調了配置,包括減少了配置推送的量,只針對某個位去推送所需的配置。我們做了一個自動的服務配置範圍(sidecar crd)的適配,這樣確保每個服務都按需載入配置。

除此之外,我們還使用了 Istio 的 DiscoverySelector 減少 istio 採集叢集資訊的範圍。因為我們的叢集中除了常規的業務容器外,還有很多永遠不會參與服務間通訊的容器。但是 DiscoverySelector 有個問題:它裡面的介面可能和我們叢集的 K8s 介面不相容,Istio 版本如果低於 1.13 的話,可能會因為 panic 導致 istiod 偶發重啟。更多細節我們以前也在 知乎專欄 上做過很詳細的分享。

指標方面,前面提到我們指標的維度已經比較大,總維度在百萬以上,這對我們的指標系統確實存在一個挑戰。我們以前其實只有一個 victoriametrics 叢集,隨著指標規模擴大,我們將指標叢集分成了兩個:一個是短期儲存,只存大概一個星期左右的全量指標;另一個是長期儲存,它會把短期儲存指標裡面的 pod 資訊去掉,然後把剩下聚合過的指標存三個月。這樣,我們可以做到在大部分情況都能較快地查詢到監控資料。

還有一個維度的問題,比如最常見的 istio request total 指標,會把某一個客戶端的版本資訊,以及服務的版本資訊全部都帶上。但這會遇到一個問題,比如某一個服務端一直不更新、客戶端一直更新的情況,會導致服務端有特別多不會增長的指標堆積在裡面。可惜 Envoy 也不支援指標過期的功能,導致每一次拉取指標時我們會多拉很多冗餘的指標。

最終,我們決定在服務端去除客戶端的版本資訊、客戶端的指標中去除掉服務端的版本資訊,通過這種方式優化指標維度。這樣做了之後,我們的指標系統壓力有了很大程度的下降,個別服務的 duration 指標維度甚至下降了 20 萬。通過這些操作,我們叢集的監控指標維度基本趨於穩定。

在知乎的 Mesh 化過程中,Mesh 一方面帶來了很多新的問題,比如效能上、運營上的問題。但是另一方面,我們利用 Mesh 節約了很多過往需要在 sdk 層面反覆實現的功能,也利用 Mesh 彌補了我們在監控與測試鏈路方面的許多缺失。Service Mesh 是一項充滿魅力的技術,而知乎的 Mesh 之路也將繼續下去。