精彩分享 | 歡樂遊戲 Istio 雲原生服務網格三年實踐思考

騰訊雲原生發表於2022-05-16

作者

吳連火,騰訊遊戲專家開發工程師,負責歡樂遊戲大規模分散式伺服器架構。有十餘年微服務架構經驗,擅長分散式系統領域,有豐富的高效能高可用實踐經驗,目前正帶領團隊完成雲原生技術棧的全面轉型。

導語

歡樂遊戲這邊對 istio 服務網格的引進,自 2019 開始,從調研到規模化落地,至今也已近三年。本文對實踐過程做了一些思考總結,期望能給對網格感興趣的同學們以參考。

在正文開始之前,先明確一下本文所說的服務網格(service mesh)概念 —— 基於 sidecar 通訊代理,網狀拓撲的後端架構級解決方案。目前業界最流行的開源解決方案為 istio。

服務網格的架構思想,是解耦,以及加一層。通過將基礎的治理能力從程式中解耦出去,以 sidecar 的形式提供,以達到更大規模的複用,一旦標準化,將會是行業級的複用!其實作為微服務領域炙手可熱的解決方案,網格所做的解耦分拆程式這件事情本身就很 “微服務”,因為微服務其實也是做程式拆分,解耦並獨立部署,以方便服務的複用。

現狀與收益

技術棧現狀

  • 程式語言:go / c++ / python

  • meta 體系:protobuf(用於描述配置、儲存、協議)

  • RPC 框架:gRPC

  • 單元測試:gtest

  • 容器化:docker + k8s

  • 網格:istio,envoy 閘道器

  • 配置:基於 pb 的 excel 轉表工具,配置分發管理中心

  • 監控:prometheus

  • 其他:程式碼生成工具,藍盾流水線,codecc 程式碼掃描,helm 部署等。

核心收益

  • 技術價值觀:團隊的技術價值觀變得比較開放,擁抱開源生態,貼近雲原生技術棧。

  • 團隊成長:大技術棧演進,是一項頗具挑戰性的任務,團隊同學在打怪升級之後自然就有了能力的提升。

  • RPC 框架:通過引入 gRPC 統一了跨語言的 RPC 框架,原自研 RPC 框架也繼續在用,但底層會適配為 gRPC。

  • 引入 golang:引入了 golang 做常規特性研發,提高了研發效率。

  • 網格能力:無需開發,基於 istio 的 virtual service 做流量治理,按 label 聚合分版本排程流量,使用一致性 hash。

  • 機器成本:這是唯一相對好量化的收益點,準確的資料還是要等後續完成 100% 上雲後才好給出,初步估算的話,可能是原來的百分之六七十。

總體上,我們做的是一個大技術棧體系的演進,體現了較好的技術價值,提升了研發效能。所以對於我們而言,現在再回望,網格化是否值得實踐?答案依舊是肯定的。

但假如拋開技術棧演進這個大背景,單獨看網格本身的話,那麼坦率地講,我們對網格能力的使用是較為初步的:

  • 轉不轉包:熔斷、限流、重試(以冪等為前提),暫未實踐。

  • 包轉給誰:名字服務,有實踐,使用了 virtual service,使用了 maglev 一致性 hash。

  • 除錯功能:故障注入、流量映象,暫未實踐。

  • 可觀測性:關掉了 tracing,暫未實踐。

考慮到實際開銷情況,我們並沒有攔截 inbound 流量,所以如果有依賴這點的功能特性,目前也無法實踐。

網格的真正賣點

從筆者個人的觀察來講,istio 網格最具吸引力的,實際上就兩點:

  • 開放技術棧的想象空間,隨著 istio、envoy、gRPC 整個生態越來越豐富,未來可能會有更多能力提供,開箱即用,業務團隊不必投入開發。

  • 多語言適配,不用為每種語言開發治理 sdk,例如 C++ 編寫的 envoy 可以給所有用 gRPC 的 service 使用。

至於熔斷、限流、均衡、重試、映象、注入,以及 tracing 監控之類的能力,嚴格來講不能算到網格頭上,用 sdk 也是一樣可以實現的。在團隊語言統一的時候,只用維護一種語言版本的 sdk,此時採用治理 sdk 方案也是可行的,也就是所謂的微服務框架方案。採用 sdk 方式下的版本維護問題,以及後期進一步演進網格的問題,這些都不難解決,這裡不再發散。

對於我們自己來講,因為恰好有引進 golang 以及 gRPC,所以現在再看,選擇 istio 作為網格方案也算合適。

網格的思考實踐

一些前置條件

接入網格,要考慮天時地利人和。即,需要滿足一些基本條件:

  • 需要專案階段允許,如果團隊本身一直在做快版本內容迭代,業務需求都忙不過來,恐怕也很難有人力保障。
  • 要有基礎設施環境支援(我們使用了騰訊雲的 tke mesh 服務),這樣不至於所有東西都從零開始。

此外,對於這類大的技術優化,還有必要先統一思想:

  • 自上而下,獲得各級管理干係人的認可,這樣才好做較大人力的投入。
  • 自下而上,發動同學們深度介入探討,使得整體的方向、方案得到大家的認可,這樣大家才有幹勁。

行動之前的三思

在早期的構思階段,需要明確幾個大的關鍵問題:

  • 1)想要達到什麼目標?節約機器成本 / 提升研發效能 / 培養團隊 / 技術棧演進?如果要達到對應的目標,有沒有其他更優路徑?

  • 2)有沒有失控風險?效能是否不可接受,k8s、istio 穩定性是否足夠,有沒有極端的可用性大風險?

  • 3)如何平穩地過渡?服務搬遷過程,研發模式是否都可以平穩過渡?

對於第一點,不同團隊要看自己的實際情況,這裡不做展開。

對於第二點,k8s 在業界的大規模應用非常多,所以還算是可靠的,而且它 level triggered 的設計,使其具備較好的健壯性。相對未知的是 istio,團隊一開始對 istio 做了一些壓測,也考慮了回退無 mesh 的情形,結論是可以嘗試。istio 本質上就是一個複雜大型軟體,所以其本身主要使人望而生畏的點,是其複雜的配置,版本之間的相容性擔憂,以及偏黑盒可控性不好這幾點。現在想來,其實我們團隊的步子邁得還是比較大的。幸運的是,後面的落地過程表明,istio 本身穩定性也還行,不至於三天兩頭出問題。

對於第三點,我們針對性地做了引入間接層的設計,使用私有協議與 gRPC 互轉的閘道器確保了服務平滑遷移上雲,在服務內部引入 grpc 適配層確保了研發人員的開發模式基本不變。

系統整體架構

系統整體架構如下圖所示,可以清晰地看到上文所說的間接層:

圖示:gRPC 適配與網格內外通訊代理

雲原生研發體驗

對於評需求、定方案、寫程式碼等差異不大的點,這裡不做展開。下面主要羅列一些在雲原生的技術棧體系下,與我們以前相比,有顯著差異的一些研發體驗。

  • helm:通過 helm 管理所有服務的內外網 yaml,在服務自身 yaml 裡完整描述其所有部署依賴。

  • 測試環境 dev 副本:因為系統服務過多,雖然內網都是 debug version,資源消耗要遠低於 release 版本,但考慮到複雜的服務間依賴,為每個人部署一套測試環境也不可取,所以目前還是選擇的少數幾套環境,大家複用。對於多人自測環境的衝突問題,我們藉助網格的能力,做了基於 uin 的 dev 副本部署,這樣當小 A 同學開發特定服務的時候,他自己的請求會落到自己的專屬 deployment 上。


圖示:基於不同號碼部署不同的專屬 deployment

  • 測試環境每日全量自動構建部署:但是這也帶來一個問題,pod 重建漂移後日志、coredump 等資訊都不匹配,例如測試同學反饋說前一天遇到個什麼問題,然後開發也不知道前一天在哪個 pod(已經被銷燬)。我們通過設定 k8s 節點親和策略 preferredDuringSchedulingIgnoredDuringExecution,結合日誌路徑固定化(取 deployment 名而非 pod 名),確保測試環境下 pod 重建後還在原 node,日誌路徑也保持一致,這樣進入同服務的新 pod 便可以繼續看到前一天的日誌。

  • 外網金絲雀版本:灰度期間使用,直接通過 yaml 的 deploymentCanary 配置項開啟,藉助 istio virtual service 來配置灰度的流量比例。排查外網問題有時候也會啟用,對於染色的號碼,流量也會匯入金絲雀版本。具體實現就是閘道器程式會讀一份號碼列表配置,只要是在列表裡的號碼請求,就給 gRPC 的 header 打上相關的 label,再基於 vs 的路由能力匯入到金絲雀版本。

  • hpa 實踐:對於 hpa 筆者早先的態度還是有些猶豫的,因為這本質上是會將服務部署釋出時機變得不可控,不像是常規人工干預的釋出,出了問題好介入。線上也確實出過一些問題,例如 hpa (會依賴 hpa 關聯的 metric 鏈路暢通)夜間失效導致業務過載;還有就是在日誌採集弄好之前,hpa 導致 pod 漂移,前一天夜裡某 pod 的告警資訊,第二天想看就比較費勁,還得跑到之前排程到的 node 上去看;另外也出現過程式 hpa 啟動不起來的問題,配置有誤無法載入初始化成功,正在跑著的程式只會 reload 失敗,但是停掉重啟就會啟動失敗。不過 hpa 對於提升資源利用率,還是很有價值的,所以我們現在的做法是做區分對待,對於普通的業務,min 副本數可以較小,對於重要的服務,min 副本數則配置稍大一些。

  • 優雅啟停:直接基於 k8s 的就緒、存活探針實現。

  • 外網日誌收集:這塊之前一直還沒有用到比較好用的平臺服務,業務自己有打過 rsyslog 遠端日誌,後面可能會用 cfs 掛網盤,也算能湊合用。

  • 配置體系:配置的定義用 protobuf,配置的解析基於程式碼生成,配置的分發基於 rainbow,配置的拉取基於 configAgent,配置的歸檔表達以 excel 形式放在了 svn,用工具完成 excel 到程式讀取格式的轉換。configAgent,是 webhook 給 pod 動態注入的容器。

  • 監控體系:prometheus,雲監控。

  • DEBUG_START 環境變數:在容器化部署的早期,我們遇到過一些程式啟動失敗的情況,反覆拉起失敗,然後 pod 到處漂移,排查很不方便,所以我們增加了 DEBUG_START 環境變數,如果設定為 true 的時候,程式啟動失敗時不退出容器。

  • 雲上 perf因為一些安全的許可權原因,容器內無法 perf,現在是臨時申請 node 的 root 許可權進行 perf,需要在 node 上也放一份二進位制檔案,不然 perf 無法解析 symbol 資訊。對於 go 服務的話,則直接使用它自己的工具鏈來剖析。

  • 問題 pod 現場保留:由於 deployment 是基於 label 識別的,所以對於外網那種想保留故障 pod 的現場時會很簡單,直接改一下 label 就好了。

  • coredump 檢視:段錯誤訊號捕獲後會把二進位制本身也拷貝到 coredump 的資料夾,隨便 attach 到 coredump node 上當前存活的任意 pod 就可以檢視。

  • 程式碼生成:這個其實和是否上雲關係不大,只是我們基於 protobuf 做了不少工作(例如用 .proto 定義配置檔案,提供類似 xresloader 的功能),深感頗有益處,這裡也列一下。後續整理相關程式碼並完善文件後,也會考慮開源。

效能情況

討論效能之前,這裡先說一下我們的實踐方式:關掉 tracing,關掉 inbound 攔截(遠端流量過來的時候,並不會走 sidecar)。

圖示:pod1 上的業務容器呼叫 pod2 上的服務,僅攔截 outbound

在上述背景下,結合我們的線上真實案例情況,分享一下讀者可能會比較感興趣的效能資料:

  • 記憶體開銷:系統中共有幾百個服務,使用一致性 hash,envoy sidecar 的記憶體佔用約兩三百兆。

  • CPU 開銷:典型 cpu 開銷和扇出情況相關,例如一個服務較多訪問其他 gRPC 服務,那麼 envoy 的 cpu 開銷甚至會超過主業務程式,但當業務程式扇出較少時 envoy 的開銷就比較低。

對於記憶體開銷的問題,社群有相對明確的解決方案,採用 sidecar crd 來限定載入業務所需訪問目標服務的 xds 資訊,即可大幅減少記憶體佔用。業務程式需要訪問哪些目標服務,可以通過手動維護、靜態註冊或程式碼生成之類的辦法明確,我們後面也會做相關的優化。

接下來我們用相對大的篇幅討論一下 cpu 開銷的問題,先看一個大扇出業務的效能 top 示例:

圖示:大扇出業務程式與 envoy 效能對比示意

看到上圖中的資料,讀者可能會有這樣的疑問:為什麼僅支援 gRPC 的轉發,envoy 就需要如此高的 cpu 開銷(圖中 71.3%,遠超業務程式的 43.7%)呢?關於這點,我們在分析火焰圖之後,也沒有發現顯著異常的地方,它所做的主要工作,也就是在做協議的編解碼與路由轉發而已,無明顯異常熱點。

圖示:envoy 火焰圖示意

現在網上大量介紹 envoy 的資料,基本都會說它效能比較好,難道...真相其實是 envoy 其實做的不夠高效麼?針對這個問題,筆者這裡也無法給出一個明確的答案,envoy 內部確實大量使用了各種 C++ 的抽象,呼叫層級比較多,但這未必是問題的關鍵所在,因為:

  • 可能是 envoy 使用的 libnghttp2 協議解析庫的效能拖累所致...

  • 可能是 envoy 使用 libnghttp2 的“姿勢”不大對,沒有充分發揮其效能...

  • 抑或是 http2 解析、編解碼,以及收發包本來就需要消耗這麼多的 cpu?

關於最後這一點,我們觀測了業務主程式中的 grpc thread,它也需要做 http2 的解析和編解碼,但它的 cpu 開銷顯然低得多。

圖示:業務程式中的 grpc 執行緒 %cpu * 2 後依然比 envoy 小很多

將業務程式中的 grpc 執行緒(紅框部分)%cpu 乘 2 後再與 envoy(藍框部分)做對比,是因為 envoy 對 outbound 攔截的 workload 相對業務程式而言確實近似翻倍,例如就編解碼而言,對於一次 req、rsp,業務程式就是 req 的編碼和 rsp 的解碼,但是對於 envoy,則是 req 的解碼 + 編碼,rsp 的解碼 + 編碼。

圖示:envoy vs 業務程式的編解碼開銷

從上面的示例來看,grpc 自己做 http2 parse + 編解碼 + 收發包的效能,要遠好於使用 libnghttp2 的 envoy,但 envoy 顯然不可能直接採用 grpc 裡面的相關程式碼。要想更好地回答關於 envoy 做 grpc 通訊代理效能的疑問,恐怕還需要做更加細緻的分析論證以及測試(歡迎感興趣或有經驗的讀者來交流)。

總之對於 gRPC 大扇出業務 sidecar cpu 損耗過大的這個問題,我們暫時也沒想到好的優化方案。當然上面那個案例其實相對極端,因為是大扇出 + 主業務程式為 C++ 的情況,如果是小扇出則 sidecar 不會耗多少 cpu,如果是 golang 業務程式則 sidecar 佔 pod 整體 cpu 開銷的比例不會這麼誇張(當然這也反過來說明 golang 效能和 C++ 差距還是蠻大的...)。

對於我們自身來講,網格的綜合效能並沒有嚴重到無法接受的地步:

  • 首先很多業務並不是大扇出型的,這類業務下的 sidecar 的開銷並不大。

  • 其次對於 golang 類的業務程式,sidecar 相較帶來的漲幅比例也會小一些。

  • 最後相對於傳統 IDC 粗放的部署方式,在我們做了整體上雲之後,總體上還是更省機器的。

私有協議或私有網格

如前文所述,envoy 的效能問題,在大扇出業務場景下確實難以忽視。

如果真的無法接受相應的效能開銷,那麼可能私有協議或私有網格會是可選的替代方案。

  • 採用私有協議,基於 envoy 自己寫 filter,解析私有協議頭,然後結合 envoy xds 相關的能力來提供服務(可以參考騰訊開源的解決方案 https://github.com/aeraki-mesh/aeraki ),不難想象在該方案下,完全無需解析 http2,效能必然會有非常顯著的提升。但如果我們真的走到私有協議的老路上去,其實就等於又放棄了 gRPC 生態。(參考前文網格核心賣點 1)

  • 採用私有網格,自己實現 xDS 相對應的系列能力,可以先從最核心能力做起。但採用該方案的話,就又會回到多語言支援的問題上來,需要為 C++ 和 golang 都實現對應的能力(參考前文網格核心賣點 2)。假如真的要自己實現私有網格,在設計上,應當考慮語言相關的 sdk 程式碼是相對簡易的,路由策略等控制面功能依舊下沉在自研 sidecar/agent 裡,資料面邏輯出於效能考慮則由業務程式自己處理。

未來趨勢展望

歡樂自己的實踐

對於歡樂自己的團隊而言,後面會持續做更深度的實踐。例如 envoy filter 開發、k8s crd,以及 istio 的更多能力的實踐(上文也提到了,我們目前僅使用了一小部分網格能力,期望以後能使用熔斷、限流等能力來提升業務的可用性)。

ebpf 的融合

ebpf 可能未來會與容器網路、網格有更好的融合,可以提升網路相關效能表現,或許還會帶來其他一些可能。

proxyless mesh

proxyless mesh 可以看做基於前文討論效能問題的一個延伸,和前面提及的私有網格有些類似。這類方案也會有對應的生存空間,因為始終有些團隊無法接受資料面 sidecar 所帶來的效能開銷:

  • 時延,這點也有不少團隊提及,但如果是普通網際網路業務,筆者個人認為多幾十毫秒級別的延遲影響都不大。

  • cpu 和記憶體開銷,前文已有較多討論。

proxyless mesh 實際上就是 sdk + 網狀拓撲的方案,gRPC 現在也在持續完善對 xDS 的支援,所以也有可能借助 gRPC 的能力來實現。如果自行研發支援 xDS 的 sdk,那對於團隊的投入還是有要求的,除非團隊本身就是大廠的中介軟體類團隊(網易輕舟、百度服務網格、阿里 dubbo,這一兩年都有做 proxyless mesh 的實踐)。

圖示:proxyless gRPC mesh

私有方案對標 xDS

對於程式語言統一的團隊,例如全 golang,那麼只用維護一套服務治理相關的 sdk(控制面邏輯也可以用 agent 承載),所以可能會傾向於做一套自己私有的解決方案。據筆者瞭解,B 站之前就是採用的這種方案,參考 eureka 實現名字服務自己做流量排程。

現在隨著網格的流行(起碼對應的理念已經廣為人知了),私有方案也可以參考對標 xDS 的各種 feature。因為私有方案通常是自研的,所以理論上還能提供相對高效可控的實現,但是需要團隊持續投入維護。

Dapr 執行時

概念很好,故事很巨集大,不過目前看來還為時過早。

參考資料

讓 istio 支援私有協議:【https://github.com/aeraki-mesh/aeraki】

grpc 對 xDS 的支援:【https://grpc.github.io/grpc/cpp/md_doc_grpc_xds_features.html】

proxyless grpc:【https://istio.io/latest/blog/2021/proxyless-grpc/】

nghttp2 解析庫:【https://nghttp2.org/】

infoQ 基礎軟體創新大會微服務專場:【https://www.infoq.cn/video/7RLecjvETz3Nt7HedviF】

xresloader 配置轉換工具:【https://github.com/xresloader/xresloader】

Dapr:【https://dapr.io/】

關於我們

更多關於雲原生的案例和知識,可關注同名【騰訊雲原生】公眾號~

福利:

①公眾號後臺回覆【手冊】,可獲得《騰訊雲原生路線圖手冊》&《騰訊雲原生最佳實踐》~

②公眾號後臺回覆【系列】,可獲得《15個系列100+篇超實用雲原生原創乾貨合集》,包含Kubernetes 降本增效、K8s 效能優化實踐、最佳實踐等系列。

③公眾號後臺回覆【白皮書】,可獲得《騰訊雲容器安全白皮書》&《降本之源-雲原生成本管理白皮書v1.0》

④公眾號後臺回覆【光速入門】,可獲得騰訊雲專家5萬字精華教程,光速入門Prometheus和Grafana。

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章