作者 | 墨封
來源 | 阿里巴巴雲原生公眾號
一周前,我們介紹了《面對大規模 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 進行打造,未來我們會探索開源整套能力輸出到社區中。