深度好文!新浪微博架構師詳析微博雲原生技術的思考與實踐

csdn雲計算 發佈 2020-02-10T00:18:25+00:00

未來資料庫訪問會以Database Mesh形式提供訪問,封裝數據分片、讀寫分離、從庫負載均衡、熔斷、鏈路路採集能力,例如Google Cloud SQL提供本地Proxy,業務方無需將IP位址列入白名單或配置SSL,即可安全地訪問Cloud SQL。

來源 | 劉超的通俗雲計算

作者 | 陳飛

責編 | Carol

出品 | CSDN雲計算(ID:CSDNcloud)


現在越來越多的企業開始全面擁抱雲計算,開始關注云原生技術。


從管理物理數據中心到使用雲主機,我們不用再關心基礎運維。從雲主機到 Kubernetes容器,我們不用再關心機器的管理。雲上抽象層級越高,就越少人需要關心底層問題,企業就能夠節省大量的人力成本與資源投入。雲原生技術就是更高一層的抽象, CNCF對雲原生技術的定義是:


有利利於各組織在公有雲、私有雲和混合雲等新型動態環境中,構建和運行可彈性擴展應用。通過容器器、 服務網格、微服務、不不可變基礎設施和聲明式API等技術,構建容錯性好、易易於管理和便於觀察的松耦合系統。

例如FaaS架構,開發者可以完全不用考慮服務器,構建並運行應用程式和服務。還有面向開源架構的的雲原生技術,與提供 MySQL, Redis 雲服務類似,提供基於Spring Cloud、Dubbo、HSF 等開源微服務架構的應用管理服務,開發者無需考慮部署、監控、運維的問題。

例如新浪微博,新浪微博也一直在致力於推動基礎設施雲原生化,他們圍繞Kubernetes構建面向容器器的雲原生基礎設施,形成了了物理數據中心加多個公有雲的混合雲 Kubernetes平台,提供秒級伸縮能力。構建開箱即用的CI/CD體系,依託雲原生伸縮能力,保證大量的Job穩定運行,讓開發人員擺脫代碼發布泥沼。接下介紹這幾方面的實踐經驗。


物理數據中心Kubernetes化


面向單機器的基礎設施架構已經無法發揮雲的最大優勢。把容器按照服務顆粒度進行管理,每個服務對應一組虛擬機,雖然基礎運維通過 IaaS 層抽象得到了極大簡化,但是業務的運維成本依然很高,業務SRE需要維護複雜的設備配置腳本,管理不同服務設備配置的差異性,需要7*24小時對故障設備進行干預。


不僅如此,資源利用率無法最大化,服務池是按設備劃分,一個新設備添加到服務池後只能被這個服務使用,它的冗餘的計算能力並不能為其他服務使用。另外不同業務容器運行在不同的機器上,容器網絡架構更關注性能而非隔離性,通常會採用Host模式,這也提高了服務混合部署的運維成本。


基礎設施只有形成集群,才能最大程度發揮容器的良好隔離、資源分配與編排管理的優勢。目前Kubernetes已經容器編排系統的事實標準,提供面向應用的容器集群部署和管理系統,消除物理(虛擬)機,網絡和存儲基礎設施的負擔。同時CNCF推出一致性認證,推動各公有雲廠商提供標準的 Kubernetes服務,這就確保通過Kubernetes部署的應用在不不同雲廠商之間具有可遷移性,避免被廠商鎖定


之前提到微博的容器會獨占物理機的網絡協議棧,雖然能夠做到網絡效率的最大化,但是會導致多容器部署時出現埠衝突,無法滿足Kubernetes動態編排的需求。為了了解決埠衝突問題,新浪微博首先測試了了vxlan網絡架構,因為其數據平面需要進行封裝、解封操作,網絡性能損耗超過5%,並不不滿足微博後端服務對網絡性能的要求。最後經過評估可行的網絡方案有兩種:MacVlan和Calico BGP。


其中 MacVlan 成熟穩定,通過機房上聯交換機改為Vlan Trunk模式,在物理機上創建MacVlan網卡子接口,通過CNI插件將虛擬網卡插入Pause容器中,實現容器網絡與物理網絡打通。容器的網絡通信直接通過MacVlan物理子接口,發出的報文在網卡上打VlanTag,數據平面基本沒有性能損耗。控制平面因需要對所有上聯交換機進行Vlan Trunk改造,工作量量較大,所以這個方案僅針對高配物理機所在網絡進行了改造。

Calico BGP是可以同時實現數據平面0損耗與控制平面自動化的容器網絡解決方案。與MacVlan實現的扁平二層網絡不同,Calico在每個節點上部署 BGP Client與Reflector實現了一個扁平的三層網絡,每個節點發布的路由狀態由 Felix 維護。不過由於Felix採用iptables實現路路由ACLs功能,對性能存在一定影響。因為物理數據中心不面向外部用戶開放,所以ACLs功能對微博是可以去除的,我們對 Calico 進行了優化,去除iptables依賴。



微博也主動回饋Kubernetes社區,也包括為Kubernetes代碼庫做貢獻,例例如修復多租戶下網絡隔離TC資源泄露問題。

之前的運維是面向物理機的,所以物理機上存在很多運維工具,如日誌推送、域名解析、時鐘同步、定時任務等。業務通過Kubernetes編排後,以上的功能都需要進行容器化改造。例如在容器中使用systemd會涉及到提權問題,在實踐過程中發現用systemd如果權限控制不當會造成容器器被Kill的情況。所以微博單獨開發了兼容linux crontab語法的定時任務工具gorun,把這個工具集成在了了運維容器里面

因為業務容器會產生大量日誌,出於I/O性能考慮,同時為了方便快速定位,日誌會存儲於本地PVC中,支持配額管理,避免一個容器把磁碟寫滿。運維基礎設施容器通過監聽文件,對老舊日誌進行壓縮清理,性能Profile日誌會在本地進行統計計算後通過UDP協議推送到Graphite或Prometheus。對於關鍵日誌,會通過Flume推送到Kafka集群,而且支持失敗重傳,保證日誌的一致性。



通過對運維容器化後,所有業務Pod都具備相同的運維能力,形成標準化的監控報警、運維決策、流量切換、服務降級,異常封殺、日誌查詢的服務保障體系,服務可運維性大幅度提升。


容器編排


Kubernetes的Deployment支持Pod自我修復,滾動升級和回滾,擴容和縮容,這些特性都是雲原生基礎設施必備的。但是Kubernetes設計原則中對集群的管理尤其是服務升級過程中保持「無損」升級,對Deployment進行行滾動升級,會創建新Pod替換老Pod,以保證Deployment中Pod的副本數量。原有里面的IP位址和滾動升級之前的IP位址是不會相同的。


而如果集群夠大,一次滾動發布就會導致負載均衡變更(集群副本數/滾動發布步長)次。對於微博服務來說,頻繁變更會導致這個負載均衡管轄下的後端實例的接口不不穩定。


微博實現了常備Pod的In-place Rolling Updates功能,根據業務冗餘度及業務實際需要來調整上線的步長,上線過程中保持容器的IP不變,減少在上線過程中業務的抖動。


因為業務的啟動需要一定時間,不能按照容器啟停來做步長控制,我們利用Kubernetes容器生命周期管理的liveness/readiness probe 實現容器提供服務的狀態,避免了上線過程中容器大面積重啟的問題。同時優化了了Kubernetes的postStar的原生實現,因為原生里面只調用一次,不管成功與否都會殺掉容器,改成不成功會按照指定的次數或時間進行重試。IP的靜態分配使用Calico CNI實現:



Kubernetes的編排策略相對靈活,分為三個階段,初篩階段用於篩選出符合基本要求的物理機節點,優選階段用於得到在初篩的節點里面根據策略略來完成選擇最優節點。在優選完畢之後,還有一個綁定過程,用於把Pod和物理機進行綁定,鎖定機器上的資源。


這三步完成之後,位於節點上的 kubelet才開始創建Pod。在實際情況中,把物理機上的容器遷移到 Kubernetes,需要保持容器的部署結構儘量一致,例如一個服務池中每台物理機上分配部署了wb_service_a和wb_service_b兩個容器,可以通過 podAffinity來完成服務混部的編排:



一些比較複雜的,運維複雜的集群,通過Kubernetes Operator進行容器編排。Operator是由CoreOS 開發的,用來擴展Kubernetes API,特定的應用程式控制器,它用來創建、配置和管理複雜的有狀態應用,如資料庫、緩存和監控系統。Operator基於Kubernetes的資源和控制器概念之上構建,但同時又包含了了應用程式特定的領域知識。


Operator 可以將運維人員對軟體操作的知識給代碼化,同時利用Kubernetes強大的抽象來管理大規模的軟體應用。例如CacheService的運維是比較複雜的,需要資源編排,數據同步,HA結構編排,備份與恢復,故障恢復等等。通過實現 CacheService Operator可以讓開發通過聲明式的Yaml文件即可創建、配置、管理複雜的Cache集群。CacheService Operator支持:


  1. 創建/銷毀:通過Yaml聲明CacheService規格,即可通過Kubernetes一鍵部署,刪除
  2. 伸縮:可以修改Yaml中聲明的副本數量,Operator實現擴容,配置主從結構,掛載域名等操作
  3. 備份:Operator根據Yaml中聲明的備份機制,實現自動的備份功能,例例如定期備份,錯峰備份等
  4. 升級:實現不停機版本升級,並支持回滾
  5. 故障恢復:單機故障時,自動HA切換,同時恢復副本數量,並自動恢復主從結構


複雜的應用在Kubernetes上部署,服務數量眾多,服務間的依賴關係也比較複雜,每個服務都有自己的資源文件,並且可以獨立的部署與伸縮,這給採用Kubernetes做應用編排帶來了諸多挑戰:


  1. 管理、編輯與更新大量的Yaml配置文件
  2. 部署一個含有大量配置文件的複雜Kubernetes應用,例如上面提到的 CacheService Operator
  3. 參數化配置模板支持多個環境


Helm可以解決這些問題。Helm把Kubernetes資源(如Pods, Deployments, Services等) 打包到一個 Chart中,實現可配置的發布是通過模板加配置文件,動態生成資源清單文件。


彈性伸縮

在雲時代,彈性已經成為新常態。而且微博的社交媒體屬性,不可提前預期的突發峰值是家常便飯,所以基礎設施不但需要具備彈性,而且需要具備在短時間內提供足夠資源的能力。Kubernetes基於容器技術在啟動時間方面比虛擬機更具優勢,省去了虛擬機創建、環境初始化、配置管理等諸多環節,直接拉起業務 Pod, 擴容時間可以從分鐘級縮短到秒級。

而且峰值流量突發時,運維、開發同學可能是在吃飯、睡覺、休假,這個時候靠人為干預肯定是來不及的,所以系統需要自動做出擴容決策。對於複雜的分布式系統,實現自動決策需要解決兩個問題,一個是容量量決策,一個是依賴關係。Kubernetes的HPA(Horizontal Pod Autoscaling)可以根據 Metric自動伸縮一個Deployment 中的 Pod 數量。HPA由一個控制循環實現,循環周期由horizontal-pod-autoscaler-sync-period標誌指定(默認是 30 秒)。在每個周期內,查詢HPA中定義的資源利利用率。並且在擴容前會有一個冷靜期,一般是5分鐘(可通過horizontal-pod-autoscaler-downscale-stabilization參數設置),然後通過下面的公式進行擴縮容:



但是這種容量決策存在兩個問題。因為突發峰值流量上漲迅速,上述擴容機制第一次擴容往擴不不到位,觸發連續多次擴容,導致服務在流量上漲期間一直處於過載狀態,影響服務SLA。另一個問題是冷靜期問題, 如果冷靜期過長,會導致峰值流量無法得到及時擴容,冷靜期過短會錯把抖動當做峰值,造成不必要的成本浪費。


第三個問題,是複雜的業務系統依賴關係複雜,每個服務根據各自指標進行伸縮,由於上面還未伸縮流量被擋在了了上游,下游這時感知不到準確流量趨勢,從整體應用角度看很容易出現上游泄洪下游被淹的問題。

微博整體的彈性伸縮架構是基於混合雲的架構,內網私有雲,公有雲虛機,雲Kubernetes,一體化Kubernetes彈性集群,實現快速自動化資源調度,解決了跨IDC調度、定製的調度算法與策略、容量評估、服務間擴容依賴關係等,構建了全鏈路路,壓測,指標,報警,干預多維度的能力:


  1. 全鏈路路是構建一個應用整體的容量決策體系,各服務不再獨自判定容量,而是根據全鏈路路容量指標作出一致性擴容決策
  2. 壓測可以幫助了解目前部署的冗餘情況,合理的設定擴容公式,避免多次重複性擴容
  3. 指標體系是要從成千上萬個Metric中抽象出可以作為決策的依據,打通負載均衡,Web服務,資料庫資源等多維度指標
  4. 報警及時多路徑觸達,避免單點
  5. 干預不但要支持快速伸縮,還應支持快速優雅降級,為服務擴容爭取時間


CI/CD


雲計算技術的普及,研發流程也隨之變化,越來越多的組織和團隊開始接受 DevOps 理念。持續集成(CI) 和持續交付(CD)是 DevOps 的基石。但是 CI/CD 在實際落地過程中存在諸多困難,導致實際效果不不理想。


以 CI為例,開發同學應該對「順利的話,會有大約100個失敗的測試」這種情形並不不陌生。由於開發環境與測試環境並不一致等諸多因素,CI經常出現不相干的偶發失敗,長此以往開發同學會默認選擇忽略CI環節的報錯警告,最終導致CI/CD淪為一句口號。

利用雲原生的聲明性基礎架構,可以將應用系統的和應用程式存放在 Git 的版本控制庫中,每個開發人員都可以提交拉取請求代碼,輕鬆在Kubernetes 上部署應用程式和運維任務,開發人員可以更高效地將注意力集中在創建新功能而不是運維相關任務上。基於Git的持續交付流水線,有諸多優勢和特點:

  1. 版本控制的聲明性容器器編排,Kubermetes作為一個雲原生的工具,可以把它的「聲明性」看作是「代碼」,聲明意味著配置由一組事實而不不是一組指令組成,例如,「有十個redis伺服器」,而不是「啟動十個redis服務器,告訴我它是否有效」
  2. Git作為事實的唯一真實來源,任何能夠被描述的內容都必須存儲在Git庫中,包括系統相關的:策略, 代碼,配置,甚至監控事件
  3. 與其他工具相結合,例如監控系統可以方便地監控集群,以及檢查比較實際環境的狀態與代碼庫上的狀態是否一致

目前大多數CI/CD工具都使用基於推送的模型。基於推送的流水線意味著代碼從CI系統開始,通過一系列構建測試等最終生成鏡像,最後手動使用 「kubectl」 將部署到 Kubernetes 集群。程式設計師是最不喜歡開發流程被打斷,多個系統間的切換會極大影響程式設計師的開發效率。所以我們通過CI和 IDE結合,把CI流程融入到開發自測環節中,讓程式設計師可以進行面向CI的測試驅動開發,提高對交付代碼質量的信心。

CI/CD流水線是圍繞程式設計師經常使用的GitLab構建,程式設計師可以對Merge Request的CI結果一目了然,避免了在多個系統間來回切換。每次代碼提交都要執行基於分支的完整CI流程,藉助雲原生的彈性能力和共享存儲,解決了大量並發的Job的計算資源瓶頸,同時緩解了Job間共享數據的帶寬壓力以及網絡傳輸延時。



持續部署要比持續集成更加複雜。部署流程中依賴人工的環節非常多,例如灰度是由運維部署到生產環境部分機器,驗證需要依靠開發和運維同學經驗檢查新版本各項指標是否正常,滾動發布與回滾也需要運維同學全程干預。金絲雀部署可以有效規避風險,在生產環境的基礎設施中小範圍的部署新的應用代碼,如果沒有錯誤,新版本才逐漸推廣到整個服務,而不用一次性從老版本切換到新版本。


不過如何驗證沒有錯誤是比較有挑戰的,微服務依賴複雜、部署範圍廣、指標維度多,是最易出錯,最耗時的環節。我們針對這個問題,開發了智能時序數據異常識別服務,覆蓋作業系統,JVM,資源 SLA,業務 SLA 的上千維度指標。它不不但可以自動準確識別異常識別,性能衰減等人工經驗能夠發現的問題,也能夠識別如資源不不合理訪問等人工很難察覺的問題。現在的CD流程包含部署、集成測試、金絲雀驗證、滾動發布、回滾自動化環節。


Weibo Mesh


Service Mesh並不是什麼新的技術,它所關注的高性能、高可用、服務發現和治理等有服務化的一天就已經存在,社區也不不乏這方面的最佳實踐。不不過之前主要是兩種方式,一種是微服務RPC框架形式,例如Motan, gRPC, Thrift, Dubbo等。傳統微服務框架有諸多弊端:


  1. 升級困難,框架、SDK 的與業務代碼強綁定
  2. 多語言問題,各種語言的服務治理能力天差地別,服務質量體系難以統一


還有一種是集中式Proxy形式,例如Nginx, Twemproxy, SQL Proxy等。雖然Proxy的形式一定程度上解決了胖客戶端的問題,沒有了升級問題,多語言可以統一接入。但是在性能方面的損耗,對於耗時較長的請求來說還可以接受,但這在服務間調用這種毫秒級請求時,性能是不能容忍的,而且服務的拆分勢必導致整個體系內耗時隨著微服務規模的擴大而劇增,而且Proxy本身很容易成為整個系統中的瓶頸點。所以經常可以看到後端服務是同時使用 Proxy和RPC的情況。

而Cloud Native會催生出如此火爆的Service Mesh,最主要的因素是 Kubernetes使基礎設施的標準化,大家發現之前這些很重的RPC框架可以抽離出來,原本需要增加維護的複雜性被Kubernetes解決掉了,跨語言、服務治理等收益凸顯出來。而且Mesh的SideCard形式,相比Proxy在請求耗時方面優勢也相當明顯。



微博將Motan RPC胖客戶端實現的治理功能下沉到Agent上,服務註冊和發現依賴微博自研Vintage命名和配置服務,對服務的訂閱和發現來建立服務間依賴的邏輯網絡。業務與的通信協議保持一致,Agent支持HTTP和RPC的調用,業務只需把原有的調用指向Agent即可,不需要改造業務代碼。


在跨語言通信協議設計方面,Google的Protocol Buffers(pb)序列化能夠提供優秀的跨語言序列化能力,但是在一是老舊HTTP遷移到pb 協議的改造成本過高,二是部分語言(例如 PHP)在做複雜pb對象序列化時性能比較差,甚至比json序列化還要慢3倍左右。微博實現了全新語言無關的通信協議 Motan2和跨語言友好的數據序列化協議Simple來應對跨語。

除了代理Service的能力外,Mesh體系提供了緩存、隊列等服務化代理,業務方可以與依賴緩存、隊列資源治理解耦的能力。可以大幅提高那些治理能力比較薄弱的業務和語言的架構水平。


隨著雲原生技術的日趨完善,會有越來越多的基礎設施從原有的 SDK 中抽象出來。未來資料庫訪問會以 Database Mesh形式提供訪問,封裝數據分片、讀寫分離、從庫負載均衡、熔斷、鏈路路採集能力,例如Google Cloud SQL提供本地Proxy,業務方無需將IP位址列入白名單或配置SSL,即可安全地訪問Cloud SQL。

關鍵字: