1. 程式人生 > >Istio在UAEK中的實踐改造之路

Istio在UAEK中的實踐改造之路

為什麼需要ServiceMesh

UCloud App Engine on Kubernetes(後簡稱“UAEK”)是UCloud內部打造的一個基於Kubernetes的,具備高可用、跨機房容災、自動伸縮、立體監控、日誌蒐集和簡便運維等特性計算資源交付平臺,旨在利用容器技術提高內部研發運維效率,讓開發能將更多的精力投入在業務研發本身,同時,讓運維能更從容應對資源伸縮、灰度釋出、版本更迭、監控告警等日常工作。

考慮到Kubernetes本來就是為自動部署、伸縮和容器化而生,再加上UCloud UAEK團隊完成IPv6組網調研和設計實現後,一個成熟的容器管理平臺很快正式在北京二地域的多個可用區上線了。相比於過去申請管理虛擬機器部署應用服務,Kubernetes確實帶來了實實在在的便利,例如方便靈活的自動伸縮以及觸手可及的微服務架構,只需簡單配置即可實現跨可用區容災等。

然而,微服務化又為系統架構帶來許多新的問題,例如服務發現、監控、灰度控制、過載保護、請求呼叫追蹤等。大家已經習慣自行運維一組Zookeeper叢集用以實現服務發現和客戶端負載均衡,使用UAEK後能否免去運維Zookeeper的工作?為了監控業務執行狀態,大家都需要在程式碼里加上旁路上報邏輯,使用UAEK是否能無侵入零耦合地實現監控上報?

此外,過去很多系統模組間呼叫缺少熔斷保護策略,波峰流量一打就癱,使用UAEK是否能幫助業務方免去大規模改造呢?過去排查問題,尤其是呼叫耗時環節排查總是費時費力,使用UAEK能否為定位瓶頸提供方便的工具?

顯然,僅憑一個穩定的Kubernetes平臺不足以解決這些問題。因此,在UAEK立項之初,團隊就把ServiceMesh作為一個必須實現的目標,任何在UAEK上部署的TCP後臺服務,都能享受到ServiceMesh帶來的這些特性:

SideCar模式部署,零侵入,微服務治理程式碼與業務程式碼完全解耦; 與Kubernetes平臺融合的服務發現機制和負載均衡排程; 提供靈活,實時,無需重啟、能根據7層業務資訊進行流量灰度管理功能; 提供統一抽象資料上報API層,用於實現監控和訪問策略控制; 使用分散式請求鏈路追蹤系統,快速追溯Bug,定位系統性能瓶頸; 過載保護機制,能在請求量超過系統設計容量時自動觸發熔斷; 能在服務上線前提供故障模擬注入演習劇本,提前進行故障處理演練; 這樣,使用UAEK部署應用服務後,即可從小範圍按賬號灰度上線開始,通過陸續地監控觀察,輕鬆掌握版本異常回退、擴大灰度範圍、全量釋出、過載保護、異常請求定位追蹤等資訊。

為什麼是Istio?

關於ServiceMesh的實現,我們重點考察了Istio。通過前期的調研和測試,我們發現Istio的幾個特效能很好滿足UAEK的需求:

完美支援Kubernetes平臺; 控制面和資料轉發面分離; Sidecar部署,掌控所有服務間呼叫流量,無上限的控制力; 使用Envoy作為Sidecar實現,Envoy使用C++11開發,基於事件驅動和多執行緒機制執行,效能好併發能力強,媲美NGINX; 對業務的程式碼和配置檔案零侵入; 配置簡單,操作方便,API完善。

整個服務網格分成控制面板和資料面兩大部分。資料面指的就是注入到應用Pod中的Envoy容器,它負責代理排程模組間的所有流量。控制面分為Pilot,Mixer和Citadel三大模組,具體功能如下:

Pilot負責向Kubernetes API獲取並Watch整個叢集的服務發現資訊,並向Envoy下發叢集服務發現資訊和使用者定製的路由規則策略。 Mixer分為Policy和Telemetry兩個子模組。Policy用於向Envoy提供准入策略控制,黑白名單控制,QPS流速控制服務;Telemetry為Envoy提供了資料上報和日誌蒐集服務,以用於監控告警和日誌查詢。 Citadel為服務和使用者提供認證和鑑權、管理憑據和 RBAC。 此外Istio為運維人員提供了一個叫istioctl的命令列工具,類似kubernetes的kubectl。運維編寫好路由規則yaml檔案後,使用istioctl即可向叢集提交路由規則。

Istio整體工作的原理和流程細節非常複雜,所涉及到的技術棧有一定的深度和廣度。這裡只概括一下大體過程:

運維人員使用istioctl或者呼叫API向控制層建立修改路由規則策略。 Pilot向Kube APIServer獲取並watch叢集服務發現資訊。 部署應用程式時,Istio會在pod的部署配置中注入Envoy容器,Envoy會通過iptables nat redirect劫持代理pod中的全部TCP流量。 Envoy會實時從Pilot更新叢集的服務發現資訊和路由規則策略,並根據這些資訊智慧排程叢集內的流量。 Envoy會在每次請求傳送前向Mixer Policy傳送Check請求檢查該請求是否收策略限制或者配額限制,每次請求接收後會向Mixer Telemetry上報本次請求的基本資訊,如呼叫是否成功、返回狀態碼、耗時資料。 Citadel實現了雙向TLS客戶端證書生成與注入,服務端金鑰和證書的下發注入,以及K8S RBAC訪問控制。 Istio在UAEK環境下的改造之路 經過上述的調研和與一系列測試,UAEK團隊充分認可Istio的設計理念和潛在價值,希望通過利用Istio豐富強大的微服務治理功能吸引更多的內部團隊將服務遷移到UAEK環境中。

然而,事實上,在UAEK上接入Istio的過程並非一帆風順。最早開始調研Istio的時候,Istio還在0.6版本,功能並不完善,在UAEK環境中無法開箱即用。

IPv6問題的解決

我們首先碰到的問題是,UAEK是一個純IPv6網路環境,而Istio對IPv6流量的支援並不完備,部分元件甚至無法在IPv6環境下部署。

在介紹具體改造案例之前,先了解下Istio Sidecar是如何接管業務程式的流量。

如上圖所描述,Istio會嚮應用Pod注入兩個容器:proxy-init容器和envoy容器。proxy-init容器通過初始化iptables設定,將所有的TCP層流量通過nat redirect重定向到Envoy監聽的15001埠。以入流量為例,Envoy的服務埠接收到被重定向到來的TCP連線後,通過getsocketopt(2)系統呼叫,使用SO_ORIGINAL_DST引數找到該TCP連線的真實目的地IP地址,並將該請求轉發到真實目的IP。

然而,我們發現在IPv6環境下,Envoy無法劫持Pod的流量。通過抓包觀察和追溯原始碼發現,Pod啟動的時候,首先會執行一個iptables初始化指令碼,完成pod內的nat redirect配置,將容器內的TCP出入流量都劫持到Envoy的監聽埠中,但這個初始化指令碼沒有ip6tables的對應操作並且discard了所有IPv6流量,因此我們修改了初始化指令碼,實現了IPv6的流量劫持。

一波剛平,一波又起。完成IPv6流量劫持後, 我們發現所有訪問業務服務埠的TCP流量都被Envoy重置,進入Envoy容器中發現15001埠並沒有開啟。追溯Envoy和Pilot原始碼發現,Pilot給Envoy下發的listen地址為0:0:0:0:15001, 這是個IPv4地址,我們需要Envoy監聽地址的為[::0]:15000,於是繼續修改Pilot原始碼。

經過上述努力,應用服務端程式Pod終於能成功Accept我們發起的TCP連線。但很快,我們的請求連線就被服務端關閉,客戶端剛連線上就立刻收到TCP FIN分節,請求依然失敗。通過觀察Envoy的執行日誌,發現Envoy接收了TCP請求後,無法找到對應的4層流量過濾器(Filter)。

深入跟進原始碼發現,Envoy需要通過getsocketopt(2)系統呼叫獲取被劫持的訪問請求的真實目的地址, 但在IPv6環境下Envoy相關的實現存在bug,如下程式碼所示。由於缺少判定socket fd的型別, getsocketopt(2)傳入的引數是IPv4環境下的引數,因此Envoy無法找到請求的真實目的地址,遂報錯並立刻關閉了客戶端連線。

發現問題後,UAEK團隊立刻修改Envoy原始碼,完善了getsocketopt(2) 的SO_ORIGINAL_DST選項的IPv6相容性,然後將這一修改提交到Envoy開源社群,隨後被社群合併到當前的Master分支中,並在Istio1.0的Envoy映象中得到更新使用。

到此為止,Istio SideCar終於能在UAEK IPv6環境下正常排程服務間的訪問流量了。

此外,我們還發現Pilot、Mixer等模組在處理IPv6格式地址時出現數組越界、程式崩潰的情況,並逐一修復之。

效能評估

Istio1.0釋出之前,效能問題一直是業界詬病的焦點。我們首先考察了增加了Envoy後,流量多了一層複製,並且請求發起前需要向Mixer Policy進行一次Check請求,這些因素是否會對業務產生不可接收的延遲。經過大量測試,我們發現在UAEK環境下會比不使用Istio時增加5ms左右的延遲,對內部大部分服務來說,這完全可以接受。

隨後,我們重點考察了整個Istio Mesh的架構,分析下來結論是,Mixer Policy和Mixer Telemetry很容易成為整個叢集的效能短板。由於Envoy發起每個請求前都需要對Policy服務進行Check請求,一方面增加了業務請求本身的延遲,一方面也給作為單點的Policy增大了負載壓力。我們以Http1.1請求作為樣本測試,發現當整個網格QPS達到2000-3000的時候,Policy就會出現嚴重的負載瓶頸,導致所有的Check請求耗時顯著增大,由正常情況下的2-3ms增大到100-150ms,嚴重加劇了所有業務請求的耗時延遲,這個結果顯然是不可接受的。

更嚴重的是,在Istio 0.8以及之前的版本,Policy是一個有狀態的服務。一些功能,如全域性的QPS Ratelimit配額控制,需要Policy單個程序記錄整個Mesh的實時資料,這意味著Policy服務無法通過橫向擴容例項來解決效能瓶頸。經過取捨權衡,我們目前關閉了Policy服務並裁剪了一些功能,比如QPS全域性配額限制。

前面提到過,Mixer Telemetry主要負責向Envoy收集每次請求的呼叫情況。0.8版本的Mixer Telemetry也存在嚴重的效能問題。壓測中發現,當叢集QPS達到2000以上時,Telemetry例項的記憶體使用率會一路狂漲。

經過分析定位,發現Telemetry記憶體上漲的原因是資料通過各種後端Adapter消費的速率無法跟上Envoy上報的速率, 導致未被Adapter處理的資料快速積壓在記憶體中。我們隨即去除了Istio自帶的並不實用的stdio日誌蒐集功能,這一問題隨即得到極大緩解。幸運的是,隨著Istio1.0的釋出,Telemetry的記憶體資料積壓問題得到解決,在相同的測試條件下,單個Telemetry例項至少能勝任3.5W QPS情況下的資料蒐集上報。

問題、希望與未來

歷經重重問題,一路走來,一個生產環境可用的ServiceMesh終於在UAEK環境上線了。在這一過程中,也有部門內其他團隊受UAEK團隊影響,開始學習Istio的理念並嘗試在專案中使用Istio。然而,目前的現狀離我們的初心依然存在差距。

Istio依然在高速迭代中,無論是Istio本身還是Envoy Proxy,每天都在演進更新。每一次版本更新,帶來的都是更為強大的功能,更為簡練的API定義,同時也帶來了更復雜的部署架構。從0.7.1到0.8,全新的路由規則v1alpha3與之前的API完全不相容,新的virtualservice與原先的routerule截然不同,給每位使用者構成了不少麻煩。

如何完全避免升級Istio給現網帶來負影響,官方依然沒有給出完美平滑的升級方案。此外,從0.8到1.0雖然各個元件的效能表現有顯著提升,但從業內反饋來看,並沒令所有人滿意,Mixer的Check快取機制究竟能多大程度緩解Policy的效能壓力依然需要觀察。

值得一提的是,我們發現的不少bug同時也在被社群其他開發者發現並逐一解決。令我們開心的是,UAEK團隊不是資訊孤島,我們能感受到Istio官方社群正在努力高速迭代,始終在致力於解決廣大開發者關心的種種問題,我們提交的issue能在數小時內被響應,這些,都讓我們堅信,Istio是一個有潛力的專案,會向Kubernetes一樣走向成功。

從UAEK接入使用者的經驗來看,使用者需要正確地使用好Istio離不開前期深入的Istio文件學習。UAEK後續需致力於要簡化這一過程,讓使用者能傻瓜化、介面化、隨心所欲地定製自己的路由規則成為我們下一個願景。

UAEK團隊始終致力於改革UCloud內部研發流程,讓研發提升效率,讓運維不再苦惱,讓所有人開心工作。除了繼續完善ServiceMesh功能,下半年UAEK還會開放更多的地域和可用區,提供功能更豐富的控制檯,釋出自動化的程式碼管理打包持續整合(CI/CD)特性等等,敬請期待!

作者介紹

陳綏,UCloud資深研發工程師,先後負責監控系統、Serverless產品、PaaS平臺ServiceMesh等開發,有豐富的分散式系統開發經驗。