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等開發,有豐富的分佈式系統開發經驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章