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部署應用服務後,便可從小範圍按帳號灰度上線開始,經過陸續地監控觀察,輕鬆掌握版本異常回退、擴大灰度範圍、全量發佈、過載保護、異常請求定位追蹤等信息。架構
關於ServiceMesh的實現,咱們重點考察了Istio。經過前期的調研和測試,咱們發現Istio的幾個特性能很好知足UAEK的需求:併發
完美支持Kubernetes平臺; 控制面和數據轉發面分離; Sidecar部署,掌控全部服務間調用流量,無上限的控制力; 使用Envoy做爲Sidecar實現,Envoy使用C++11開發,基於事件驅動和多線程機制運行,性能好併發能力強,媲美NGINX; 對業務的代碼和配置文件零侵入; 配置簡單,操做方便,API完善。負載均衡
整個服務網格分紅控制面板和數據面兩大部分。數據面指的就是注入到應用Pod中的Envoy容器,它負責代理調度模塊間的全部流量。控制面分爲Pilot,Mixer和Citadel三大模塊,具體功能以下:less
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環境中沒法開箱即用。
咱們首先碰到的問題是,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等開發,有豐富的分佈式系統開發經驗。