做者:新浪微博架構師陳飛linux
如今愈來愈多的企業開始全面擁抱雲計算,開始關注雲原生技術。從管理物理數據中心到使用雲主機,咱們不用再關心基礎運維。從雲主機到 Kubernetes 容器,咱們不用再關心機器的管理。雲上抽象層級越高,就越少人須要關心底層問題,企業就可以節省大量的人力成本與資源投入。雲原生技術就是更高一層的抽象, CNCF 對雲原生技術的定義是:程序員
有利利於各組織在公有云、私有云和混合雲等新型動態環境中,構建和運行可彈性擴展應用。經過容器器、 服務網格、微服務、不可變基礎設施和聲明式API等技術,構建容錯性好、易易於管理和便於觀察的鬆耦合系統。redis
例如 FaaS 架構,開發者能夠徹底不用考慮服務器,構建並運行應用程序和服務。還有面向開源架構的的雲原生技術,與提供 MySQL, Redis 雲服務相似,提供基於 Spring Cloud、Dubbo、HSF 等開源微服務架構的應用管理服務,開發者無需考慮部署、監控、運維的問題。算法
微博也一直在致力於推進基礎設施雲原生化,咱們圍繞 Kubernetes 構建面向容器的雲原生基礎設施,造成了物理數據中心加多個公有云的混合雲 Kubernetes 平臺,提供秒級伸縮能力。構建開箱即用的 CI/CD 體系,依託雲原生伸縮能力,保證大量的 Job 穩定運行,讓開發人員擺脫代碼發佈泥沼。接下介紹這幾方面的實踐經驗。數據庫
物理數據中心 Kubernetes 化json
面向單機器的基礎設施架構已經沒法發揮雲的最大優點。把容器按照服務顆粒度進行管理,每一個服務對應一組虛擬機,雖然基礎運維經過 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 ,業務方無需將 I P地址列入白名單或配置 SSL,便可安全地訪問 Cloud SQL 。