
作者 | 墨封
來源 | 阿裡巴巴雲原生公衆号
一周前,我們介紹了
《面對大規模 K8s 叢集,如何先于使用者發現問題》。
本篇文章,我們将繼續為大家介紹 ASI SRE(ASI,Alibaba Serverless infrastructure,阿裡巴巴針對雲原生應用設計的統一基礎設施) 是如何探索在 Kubernetes 體系下,建設 ASI 自身基礎設施在大規模叢集場景下的變更灰階能力的。
我們面臨着什麼
ASI 誕生于阿裡巴巴集團全面上雲之際,承載着集團大量基礎設施全面雲原生化的同時,自身的架構、形态也在不斷地演進。
ASI 整體上主要采用 Kube-on-Kube 的架構,底層維護了一個核心的 Kubernetes 元叢集,并在該叢集部署各個租戶叢集的 master 管控元件:apiserver、controller-manager、scheduler,以及 etcd。而在每個業務叢集中,則部署着各類 controller、webhook 等 addon 元件,共同支撐 ASI 的各項能力。而在資料面元件層面,部分 ASI 元件以 DaemonSet 的形式部署在節點上,也有另一部分采用 RPM 包的部署形式。
同時,ASI 承載了集團、售賣區場景下數百個叢集,幾十萬的節點。即便在 ASI 建設初期,其管轄的節點也達到了數萬的級别。在 ASI 自身架構快速發展的過程中,元件及線上變更相當頻繁,早期時單日 ASI 的元件變更可以達到數百次。而 ASI 的核心基礎元件諸如 CNI 插件、CSI 插件、etcd、Pouch 等,無論任意之一的錯誤變更都可能會引起整個叢集級别的故障,造成上層業務不可挽回的損失。
簡而言之,叢集規模大、元件數量多,變更頻繁以及業務形态複雜是在 ASI,或其他 Kubernetes 基礎設施層建設灰階能力和變更系統的幾大嚴峻挑戰。當時在阿裡巴巴内部,ASI/Sigma 已有數套現有的變更系統,但都存在一定的局限性。
- 天基:具備通用的節點釋出的能力,但不包括叢集、節點集等 ASI 的中繼資料資訊。
- UCP:早期 sigma 2.0 的釋出平台,年久失修。
- sigma-deploy:sigma 3.x 的釋出平台,以鏡像 patch 的形式更新 deployment/daemonset。
- asi-deploy:早期 ASI 的釋出平台,管理了 ASI 自身的元件,僅支援鏡像 patch,隻針對 Aone 的 CI/CD 流水線做适配,以及支援在多個不同環境間灰階,但灰階粒度較粗。
由此,我們希望借鑒前面幾代 sigma/ASI 的釋出平台曆史,從變更時入手,以系統能力為主,再輔以流程規範,逐漸建構 ASI 體系下的灰階體系,建設 Kubernetes 技術棧下的運維變更平台,保障數以千計的大規模叢集的穩定性。
預設和思路
ASI 自身架構和形态的發展會極大地影響其自身的灰階體系建設方式,是以在 ASI 發展的早期,我們對 ASI 未來的形态做了如下大膽的預設:
- 以 ACK 為底座:ACK(阿裡雲容器服務)提供了雲的各種能力,ASI 将基于複用這些雲的能力,同時将阿裡巴巴集團内積累的先進經驗反哺雲。
- 叢集規模大:為提高叢集資源使用率,ASI 将會以大叢集的方式存在,單個叢集提供公共資源池來承載多個二方租戶。
- 叢集數量多:ASI 不僅按 Region 次元進行叢集劃分,還會按照業務方等次元劃分獨立的叢集。
- Addon 數量多:Kubernetes 體系是一個開放架構,會衍生出非常多 operator,而這些 operator 會和 ASI 核心元件一起共同對外提供各種能力。
- 變更場景複雜:ASI 的元件變更場景将不止鏡像釋出形式,Kubernetes 聲明式的對象生命周期管理注定了變更場景的複雜性。
基于以上幾個假設,我們能夠總結在 ASI 建設初期,亟待解決的幾個問題:
- 如何在單個大規模叢集中建設變更的灰階能力?
- 如何在多個叢集間建立規模化的變更灰階能力?
- 在元件數量、種類衆多的情況下,如何保證進行元件管理并保證元件每次的釋出不會影響線上環境?
我們轉換一下視角,脫離叢集的次元,嘗試從元件的角度來解決變更的複雜性。對于每個元件,它的生命周期可以大體劃分為需求和設計階段,研發階段和釋出階段。對于每個階段我們都希望進行規範化,并解決 Kubernetes 本身的特點,将固定的規範落到系統中,以系統能力去保證灰階過程。
結合 ASI 的形态和變更場景的特殊性,我們從以下幾點思路出發去系統化建設 ASI 的灰階體系:
- 需求和設計階段
- 方案 TechReview
- 元件上線變更會審
- 元件研發階段
- 标準化元件研發流程
- 元件釋出變更階段
- 提供元件工作台能力進行元件的規模化管理
- 建設 ASI 中繼資料,細化灰階單元
- 建設 ASI 單叢集、跨叢集的灰階能力
灰階體系建設
1. 研發流程标準化
ASI 核心元件的研發流程可以總結為以下幾個流程:
針對 ASI 自身的核心元件,我們與品質技術團隊的同學共同建設了 ASI 元件的 e2e 測試流程。除了元件自身的單元測試、內建測試外,我們單獨搭建了單獨的 e2e 叢集,用作常态化進行的 ASI 整體的功能性驗證和 e2e 測試。
從單個元件視角入手,每個元件的新功能經過研發後,進行 Code Review 通過并合入 develop 分支,則立即觸發進行 e2e 流程,通過 chorus(雲原生測試平台) 系統建構鏡像後,由 ASIOps(ASI 運維管控平台) 部署到對應的 e2e 叢集,執行标準的 Kubernetes Conformance 套件測試任務,驗證 Kubernetes 範圍内的功能是否正常。僅當所有測試 case 通過,該元件的版本才可标記為可推平版本,否則後續的釋出将會受到管控限制。
然而正如上文提到,Kubernetes 開放的架構意味着它不僅僅包含管控、排程等核心元件,叢集的功能還很大程度依賴于上層的 operator 來共同實作。是以 Kubernetes 範圍内的白盒測試并不能覆寫所有的 ASI 的适用場景。底層元件功能的改變很有大程度會影響到上層 operator 的使用,是以我們在白盒 Conformance 的基礎上增加了黑盒測試用例,它包含對各類 operator 自身的功能驗證,例如從上層 paas 發起的擴縮容,校驗釋出鍊路的 quota 驗證等能力,常态化運作在叢集中。
2. 元件規模化管理
針對 ASI 元件多、叢集多的特點,我們在原有 asi-deploy 功能之上進行拓展,以元件為切入點,增強元件在多叢集間的管理能力,從鏡像管理演進成了YAML 管理。
基于 Helm Template 的能力,我們将一個元件的 YAML 抽離成模闆、鏡像和配置三部分,分别表示以下幾部分資訊:
- 模闆:YAML 中在所有環境固定不變的資訊,例如 apiVersion,kind 等;
- 鏡像:YAML 中與元件鏡像相關的資訊,期望在單一環境或者所有叢集中保持一緻的資訊;
- 配置:YAML 中與單環境、單叢集綁定的資訊,允許存在多樣化的内容,不同叢集中的配置可能不同;
是以,一個完整的 YAML 則由模闆、鏡像和配置共同渲染而成。而 ASIOps 則再會對鏡像資訊和配置資訊這部分 YAML 分别進行叢集次元和時間次元(多版本)進行管理,計算元件目前版本資訊在衆多叢集衆多分布狀況以及元件在單叢集中版本的一緻性狀況。
針對鏡像版本,我們從系統上促使其版本統一,以保證不會因版本過低而導緻線上問題;而針對配置版本,我們則從管理上簡化它的複雜性,防止配置錯誤發入叢集。
有了元件的基礎原型後,我們希望釋出不僅僅是“替換 workload 裡的 image 字段”這樣簡單的一件事。我們目前維護了整個 YAML 資訊,包含了除了鏡像之外的其他配置内容,需要支援除了鏡像變動外的變更内容。是以我們嘗試以盡可能接近 kubectl apply 的方式去進行 YAML 下發。
我們會記錄三部分的 YAML Specification 資訊:
- Cluster Spec:目前叢集中指定資源的狀況;
- Target Spec:現在要釋出進叢集的 YAML 資訊;
- DB Spec:上一次部署成功的 YAML 資訊,與 kubectl apply 儲存在 annotation 中的 last-applied-configuration 功能相同。
對于一個由鏡像、配置和模闆共同建構的 YAML,我們會采集上述三種 Spec 資訊,并進行一次 diff,進而獲得到資源 diff patch,再進行一次 filter out,篩去不允許變更的危險的字段,最後将整體的 patch 以 strategic merge patch 或者 merge patch 的形式發送給 APIServer,觸發使得 workload 重新進入 reconcile 過程,以改變叢集中該 workload 的實際狀況。
除此之外,由于 ASI 元件之間具有較強的相關性,存在許多場景需要同時一次性釋出多個元件。例如當我們初始化一個叢集,或者對叢集做一次整體的 release 時。是以我們在單個元件部署的基礎上增加了 Addon Release 的概念,以元件的集合來表明整個 ASI 的 release 版本,并且根據每個元件的依賴關系自動生成部署流,保證整體釋出的過程中不會出現循環依賴。
3. 單叢集灰階能力建設
在雲原生的環境下,我們以終态的形式去描述應用的部署形态,而 Kubernetes 提供了維護各類 Workload 終态的能力,Operator 對比 workload 目前狀态與終态的差距并進行狀态協調。這個協調的過程,換言之 workload 釋出或者復原的過程,可以由 Operator 定義的釋出政策來處理這個“面向終态場景内的面向過程的流程”。
相比 Kubernetes 上層的應用負載,底層的基礎設施元件在釋出的過程中更關心元件自身的灰階釋出政策和灰階暫停能力,_即不論任何類型的元件,都需要能在釋出過程中具備及時停止釋出的能力,以提供更多的時間進行功能檢測、決策以及復原_。具體而言,這些能力可以歸納為如下幾類:
- updateStrategy:流式更新/滾動更新
- pause/resume:暫停/恢複能力
- maxUnavailable:不可用副本數到達一定時能夠快速停止更新
- partition:更新暫停能力,單次僅更新固定數量副本數,保留一定數量的老版本副本
ASI 中針對 Kubernetes 原生 workload 能力、節點能力都進行了增強。依托于叢集中 Kruise 和 KubeNode 這類 operator 的能力以及上層管控平台 ASIOps 的共同協作,我們對 Kubernetes 基礎設施元件實作了上述灰階能力的支援。對于 Deployment / StatefulSet / DaemonSet / Dataplane 類型的元件,在單叢集中釋出時支援的能力如下:
後文将簡要介紹我們針對不同 Workload 類型的元件進行灰階的實作,詳細的實作細節可以關注我們開源的項目 OpenKruise 以及後續準備開源的 KubeNode。
1)Operator Platform
大多數 Kubernetes 的 operator 以 Deployment 或者 StatefulSet 的方式部署,在 Operator 釋出的過程中,一旦鏡像字段變動,所有 Operator 副本均會被更新。這個過程一旦新版本存在問題,則會造成不可挽回的問題。
針對此類 operator,我們将 controller-runtime 從 operator 中剝離出來,建構一個中心化的元件 operator-manager(OpenKruise 開源實作中為 controller-mesh)。同時每個 operator pod 中會增加一個 operator-runtime 的 sidecar 容器,通過 gRPC 接口為元件的主容器提供 operator 的核心能力。
operator 向 APIServer 建立 Watch 連接配接後,監聽到事件并被轉化為待 operator 協調處理的任務流(即 operator 的流量),operator-manager 負責中心化管控所有 operator 的流量,并根據規則進行流量分片,分發到不同的 operator-runtime,runtime 中的 workerqueue 再觸發實際 operator 的協調任務。
在灰階過程中,operator-manager 支援按照 namespace 級别,哈希分片方式,将 operator 的流量分攤給新舊版本的兩個副本,進而可以從兩個副本處理的負載 workload 來驗證這次灰階釋出是否存在問題。
2)Advanced DaemonSet
社群原生的 DaemonSet 支援了 RollingUpdate,但是其滾動更新的能力上僅支援 maxUnavailable 一種,這對于單叢集數千上萬節點的 ASI 而言是無法接受的,一旦更新鏡像後所有 DaemonSet Pod 将會被更新,并且無法暫停,僅能通過 maxUnavailable 政策進行保護。一旦 DaemonSet 釋出了一個 Bug 版本,并且程序能夠正常啟動,那麼 maxUnavailable 也無法生效。
此外社群提供 onDelete 方式,可以在手動删除 Pod 建立新 Pod,由釋出平台中心端控制釋出順序和灰階,這種模式無法做到單叢集中的自閉環,所有的壓力都上升到釋出平台上。讓上層釋出平台來進行Pod驅逐,風險比較大。最好的方式就是 Workload 能自閉環提供元件更新的能力。是以我們在 Kruise 中加強了 DaemonSet 的能力使其支援上述幾種重要的灰階能力。
如下是一個基本的 Kruise Advanced DaemonSet 的例子:
apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
spec:
# ...
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 5
partition: 100
paused: false
其中 partition 意為保留老版本鏡像的 Pod 副本數,滾更新過程中一旦指定副本數 Pod 更新完成,将不再對新的 Pod 進行鏡像更新。我們在上層 ASIOps 中控制 partition 的數值來滾動更新 DaemonSet,并配合其他 UpdateStrategy 參數來保證灰階進度,同時在新建立的 Pod 上進行一些定向驗證。
3)MachineComponentSet
MachineComponentSet 是 KubeNode 體系内的 Workload,ASI 中在 Kubernetes 之外的節點元件(無法用 Kubernetes 自身的 Workload 釋出的元件),例如 Pouch,Containerd,Kubelet 等均是通過該 Workload 進行釋出。
節點元件以 Kubernetes 内部的自定義資源 MachineComponent 進行表示,包含一個指定版本的節點元件(例如 pouch-1.0.0.81)的安裝腳本,安裝環境變量等資訊;而 MachineComponentSet 則是節點元件與節點集合的映射,表明該批機器需要安裝該版本的節點元件。而中心端的 Machine-Operator 則會去協調這個映射關系,以終态的形式,比對節點上的元件版本以及目标版本的差異,并嘗試去安裝指定版本的節點元件。
在灰階釋出這一部分,MachineComponentSet 的設計與 Advanced DaemonSet 類似,提供了包括 partition,maxUnavailable 的 RollingUpdate 特性,例如以下是一個 MachineComponentSet 的示例:
apiVersion: kubenode.alibabacloud.com/v1
kind: MachineComponentSet
metadata:
labels:
alibabacloud.com/akubelet-component-version: 1.18.6.238-20201116190105-cluster-202011241059-d380368.conf
component: akubelet
name: akubelet-machine-component-set
spec:
componentName: akubelet
selector: {}
updateStrategy:
maxUnavailable: 20%
partition: 55
pause: false
同樣上層 ASIOps 在控制灰階更新節點元件時,與叢集側的 Machine-Operator 進行互動,修改指定 MachineComponentSet 的 partition 等字段進行滾動更新。
相比于傳統的節點元件釋出模式,KubeNode 體系将節點元件的生命周期也閉環至 Kubernetes 叢集内,并将灰階釋出的控制下沉到叢集側,減少中心側對節點中繼資料管理的壓力。
4. 跨叢集灰階能力建設
阿裡巴巴内部針對雲産品、基礎産品制定了變更紅線 3.0,對管控面元件、資料面元件的變更操作的分批灰階、控制間隔、可觀測、可暫停、可復原進行了要求。但變更對象以 region 的單元進行灰階不滿足 ASI 的複雜場景,是以我們嘗試去細化 ASI 上管控面、資料面的變更所屬的變更單元的類型。
我們圍繞叢集這一基礎單元向上,向下分别進行抽象,得到以下幾個基本單元:
- 叢集組:具有共同業務方(ASI 承接的二方使用者)、網絡域(售賣區/OXS/集團)、環境(e2e/測試/預發/金絲雀/小流量/生産)資訊,是以在監控、告警、巡檢、釋出等方面的配置具有共同性。
- 叢集:ASI 叢集概念,對應一個 Kubernetes 叢集預案。
- 節點集:一組具有共同特征的節點集合,包括資源池、子業務池等資訊。
- Namespace:單個叢集中的單個 Namespace,通常 ASI 中一個上層業務對應一個 Namespace。
- 節點:單台主控端節點,對應一個 Kubernetes Node。
針對每種釋出模式(管控元件、節點元件),我們以最小爆炸半徑為原則,将他們所對應的灰階單元編排串聯在一起,以使得灰階流程能夠固化到系統中,元件開發在釋出中必須遵守流程,逐個單元進行部署。編排過程中,我們主要考慮以下幾個因素:
- 業務屬性
- 環境(測試、預發、小流量、生産)
- 網絡域(集團 V、售賣區、OXS)
- 叢集規模(Pod/Node 數)
- 使用者屬性(承載使用者的 GC 等級)
- 單元/中心
- 元件特性
同時我們對每個單元進行權重打分,并對單元間的依賴關系進行編排。例如以下是一條 ASI 監控元件的釋出流水線,由于該監控元件在所有 ASI 場景都會使用同一套方案,它将推平至所有 ASI 叢集。并且在推平過程中,它首先會經過泛電商交易叢集的驗證,再進行集團 VPC 内二方的釋出,最後進行售賣區叢集的釋出。
而在每個叢集中,該元件則會按照上一節中我們讨論的單叢集内的灰階方式進行 1/5/10 批次的分批,逐批進行釋出。
進行了灰階單元編排之後,我們則可以獲得到一次元件推平流水線的基礎骨架。而對于骨架上的每個灰階單元,我們嘗試去豐富它的前置檢查和後置校驗,進而能夠在每次釋出後确認灰階的成功性,并進行有效的變更阻斷。同時對于單個批次我們設定一定的靜默期去使得後置校驗能夠有足夠的時間運作完,并且提供給元件開發足夠的時間進行驗證。目前單批次前置後置校驗内容包括:
- 全局風險規則(封網、熔斷等)
- 釋出時間視窗(ASI 試行周末禁止釋出的規則)
- KubeProbe 叢集黑盒探測
- 金絲雀任務(由諾曼底發起的 ASI 全鍊路的擴縮容任務)
- 核心監控名額大盤
- 元件日志(元件 panic 告警等)
- 主動診斷任務(主動查詢對應的監控資訊是否在釋出過程中有大幅變化)
将整個多叢集釋出的流程串聯在一起,我們可以得到一個元件從研發,測試至上線釋出,整個流程經曆的事件如下圖:
在流水線編排的實作方面,我們對社群已有的 tekton 和 argo 進行了選型調研,但考慮到我們在釋出流程中較多的邏輯不适合單獨放在容器中執行,同時我們在釋出過程中的需求不僅僅是 CI/CD,以及在設計初期這兩個項目在社群中并不穩定。因而我們參考了 tekton 的基礎設計(task / taskrun / pipeline / pipelinerun)進行了實作,并且保持着和社群共同的設計方向,在未來會調整與社群更接近,更雲原生的方式。
成果
經過近一年半的建設,ASIOps 目前承載了近百個管控叢集,近千個業務叢集(包括 ASI 叢集、Virtual Cluster 多租虛拟叢集,Sigma 2.0 虛拟叢集等),400 多個元件(包括 ASI 核心元件、二方元件等)。同時 ASIOps 上包含了近 30 餘條推平流水線,适用于 ASI 自身以及 ASI 承載的業務方的不同釋出場景。
同時每天有近 400 次的元件變更(包括鏡像變更和配置變更),通過流水線推平的此時達 7900+。同時為了提高釋出效率,我們在前後置檢查完善的條件下開啟了單叢集内自動灰階的能力,目前該能力被大多數 ASI 資料面的元件所使用。
如下是一個元件通過 ASIOps 進行版本推平的示例:
同時我們在 ASIOps 上的分批灰階以及後置檢查變更阻斷,也幫助我們攔住了一定由于元件變更引起的故障。例如 Pouch 元件在進行灰階時,由于版本不相容導緻了叢集不可用,通過釋出後觸發的後置巡檢發現了這一現象,并阻斷了灰階程序。
ASIOps 上的元件大多數都是 ASI/Kubernetes 底層的基礎設施元件,近一年半以來沒有因為由元件變更所引起的故障。我們努力将指定的規範通過系統能力固化下來,以減少和杜絕違反變更紅線的變更,進而将故障的發生逐漸右移,從變更引發的低級故障逐漸轉變至代碼 Bug 自身引起的複雜故障。
展望
随着 ASI 的覆寫的場景逐漸擴大,ASIOps 作為其中的管控平台需要迎接更複雜的場景,規模更大的叢集數、元件數的挑戰。
首先我們亟待解決穩定性和效率這一權衡問題,當 ASIOps 納管的叢集數量到達一定量級後,進行一次元件推平的耗時将相當大。我們希望在建設了足夠的前後置校驗能力後,提供變更全托管的能力,由平台自動進行釋出範圍内的元件推平,并執行有效的變更阻斷,在 Kubernetes 基礎設施這一層真正做到 CI/CD 自動化。
同時目前我們需要手動對灰階單元進行編排,确定灰階順序,在未來我們希望建設完全整個 ASI 的中繼資料,并自動對每次釋出範圍内的所有單元進行過濾、打分和編排。
最後,目前 ASIOps 暫時隻做到針對元件相關的變更進行灰階的能力,而 ASI 範圍内的變更遠不止元件這一點。灰階體系應該是一個通用的範疇,灰階流水線需要被賦能到注入資源運維、預案執行的其他的場景中。
此外,整個管控平台的灰階能力沒有與阿裡巴巴有任何緊耦合,完全基于 Kruise / KubeNode 等 Workload 進行打造,未來我們會探索開源整套能力輸出到社群中。