KubeNest - 運維特征(Trait)配置化開發框架設計及實踐
一、背景
在云原生應用發布平臺KubeNest中,trait表示跟應用相關的運維動作,如DNS trait用于解決在不同環境中修改應用主workload的DNS配置的運維問題,像DNS trait這樣的trait在KubeNest中已經有10多款,且隨著業務需求增加而不斷增多。在原有KubeNest技術中,每一個trait所需的運維邏輯都是通過Operator實現的,隨著trait的不斷增加,在trait開發和運維上的存在一些問題:
- 不必要重建:在一次應用發布過程中可能涉及多個trait,這些trait由Operator實現都去修改workload,每次修改都會造成pod重建,實際生產過程中,pod重建應該盡量避免
- 開發成本高:新開發一個trait需要通過新建一個operator應用來實現,雖然可以利用kubebuidler開發框架簡化開發,但是仍然需要幾天才能完成,且需要開發者了解Operaotr的開發機制,對于開發者有一定語言能力要求
- 運維成本高:trait數量過多,一旦涉及到公共邏輯代碼修改(如status增加字段)時,需修改n個trait工程,同時需要升級m個集群,帶來的維護成本將是o(n^2)的
- 資源浪費:每個trait是一個單獨的應用,而應用部署時最低配置都是1核1G且多副本,然而內部執行的是簡單轉換邏輯僅用100M左右,因此這些trait實際上帶來大量資源浪費
- 代碼不規范:在trait共建方面,KubeNest僅規定了輸入輸出標準,用戶可定制化trait開發,這也很容易因為代碼不規范造成bug
- 不一致問題:面對相同的輸入,在Operator代碼邏輯不當可能會帶來輸出數據順序不同,而該不一致問題很容易導致pod重建,如toleration順序變化會導致pod重建,而這是無效的重建
為了解決這些問題,我們不僅要規范trait的輸入輸出,更應進一步優化trait開發。
二、舊的架構
在KubeOne,trait是不會直接操作workload,這些trait中90%的trait都是patch類型trait,而且大部分trait的邏輯僅是簡單的邏輯轉換,下面介紹patch類trait的舊的實現架構。
1.Trait Operator化
patch類trait具體的架構如下:
如上圖所示,trait Operator是接受trait CR進行運維邏輯處理后將運維動作以YAML碎片的形式直接patch到workload上,其中trait CR包括兩部分數據:
應用數據:應用相關的數據,用戶無需關心但在Operator邏輯處理所需要的,存在metadata的annatation數據中,如workload的apiVersion和kind,在apply時需要。
用戶數據:屏蔽k8s白屏化展示用戶的數據,在trait CR的spec數據中。
下面以toleration trait進行說明:
- toleration trait CR (用戶選擇底層資源)
- apiVersion: apps.kubeone.alibaba-inc.com/v1
- kind: TolerationInjector
- metadata:
- annotations:
- kubeone.ali/workload-api-version: apps.kruise.io/v1alpha1 # 應用數據
- kubeone.ali/workload-kind: StatefulSet # 應用數據
- ...
- #用戶數據
- spec:
- parameters:
- sigma.ali/is-ecs: "true"
- sigma.ali/resource-pool: "example"
該CR表示用戶希望將pod布置在打上污點標sigma.ali/is-ecs: "true"和sigma.ali/resource-pool: "example"的node上。
- toleration trait產生的YAML片段
toleration trait根據trait CR中用戶輸入轉化成YAML片段,然后將該YAML片段直接patch到workload上,完成該運維操作。
- # YAML片段
- apiVersion: apps.kruise.io/v1alpha1
- kind: StatefulSet
- metadata:
- name: sts-example
- namespace: ns-example
- spec:
- template:
- spec:
- tolerations:
- - effect: NoSchedule
- key: sigma.ali/resource-pool
- operator: Equal
- value: example
- - effect: NoSchedule
- key: sigma.ali/is-ecs
- operator: Equal
- value: 'true'
2.風險點
- 順序導致重建
在原生的statefulset和Open kruise statefulset中,YAML內容的順序不同也會導致重啟。因此,在舊的架構中,trait除了要關注輸入參數的值,還需關注參數的順序問題。當參數順序不同時,trait產生的YAML片段順序也會不同,當patch到workload上時,就會引發workload重啟,從而可能帶來pod重建的故障風險。
- 多次apply導致重建
一次發布過程中可能有多個trait施加運維操作,此時會有多次apply workload而導致pod多次重建。從安全生產角度考慮,用戶希望pod重建次數越少越好。
三、trait配置化開發框架
從前面的架構中,我們可以看出trait operator其實就是一個converter,將用戶數據和應用數據映射成一個YAML片段,然后patch到workload。原有的convert邏輯用operator來實現,低效且浪費資源。現采用一種基于配置的convert方案,完成trait邏輯開發。
1.Trait開發框架
從上圖可以框架分為兩部分:
Trait convert:trait實現借助于Universal Operator通過數據+配置模版=YAML片段的方式生成YAML Fragment,該YAML Fragment會通過ConfigMap存儲。
Apply:會聚合一次發布過程中產生的所有YAML Fragment,然后做一次patch到應用workload,從而避免了多次重建。
下面重點介紹下配置化的“Trait Convert”的設計:
- trait CR:用戶提交的運維動作,其中包括應用數據(存在metadata)和用戶數據(存在spec),可以參考toleration trait CR的示例
- trait definition:去operator,將運維邏輯配置化,本質是一個YAML
- name:該條數據的標示,在template中,通過name來渲染數據
- keyRef:數據來源,值為json path的形式,會根據keyRef從spec中讀取數據
- default:默認值,如果從spec中找不到數據則用默認值
- required:表明此屬性是否必須
- description:對該屬性的描述
- params:在模版中定義了該運維邏輯所需要的用戶數據和每條用戶數據的基本屬性,每個數據屬性有name、default、keyRef、description、required
- tasks:對于配置化的切面拓展,90%的trait是可以直接轉換的,對于不能轉換需要添加復雜邏輯的,trait開發者可以通過tasks來自定義,task會在生成YAML片段前執行,目前支持的task類型有shell、job、http
- template:以go template為基礎的trait模版,結合數據render成最終的YAML片段
- Universal Trait Controller:核心的轉換控制器,結合trait CR和trait definiton生成YAML片段
2.流程介紹
Universal Trait Controller會結合trait CR和trait definition生成YAML片段,具體流程如下:
- 用戶數據處理(Merge)。trait CR中有用戶數據(user data)和應用數據(app data),params中規定了參數要求,merge過程將用戶數據和params結合輸出,記為merged data
- 定制化邏輯處理(TaskRun)。tasks是配置化方案的拓展,是用戶自定義的邏輯,包括多種shell、http、job、func等多種方式。此過程會將merged data和app data作為task的輸入參數,順序執行多個task,tasks執行完會產生新的輸出數據,記為output data
- 數據渲染(Render)。app data、merged data和output data作為終態數據,將這些數據與template通過go template技術渲染得到YAML片段,YAML暫存在Fragment(configMap)中
重點介紹下YAML片段會用到的應用數據,從安全的角度考慮,平臺提供給trait開發者的應用數據做出了限制,目前僅支持以下參數:
- OrderId:每次發布的orderId
- AppName:應用name
- WorkloadApiVersion:workload的apiVersion
- WorkloadKind:workload的Kind
- Namespace:應用的namespace
- CoreNamespace:kubeNest的namespace,值為ark-system
- Replicas:副本數
3.示例
- apiVersion: core.oam.dev/v1alpha1
- kind: TraitDefinition
- metadata:
- name: etcd-secret-injector
- namespace: ns-example
- spec:
- ...
- params:
- - name: END_POINT
- type: "string"
- description: "this is a description"
- default: "https://127.0.0.1"
- required: false
- tasks:
- - name: etcd-http
- kind: http # shell / job / http / func
- spec:
- script: '{{.Params.END_POINT}}/etcd'
- outputs:
- - name: TOKEN
- default: "default token"
- - name: KEY
- default: "default key"
- template: |
- apiVersion: v1
- kind: secret
- metadata:
- name: {{ .AppName }}
- namespace: {{ .Namespace }}
- data:
- token: {{ .Outputs.TOKEN | b64dec }}
- key: {{ .Outputs.KEY | b64dec}}
從上面可以看出,etcd-secret-injector的作用是接受用戶輸入etcd的endpoint,然后轉換生成secret密鑰。
四、方案對比
對比開發 | Operator開發 | 配置化開發 |
---|---|---|
開發成本 |
需掌握Operator開發知識 |
僅需知道YAML編寫知識 |
開發周期 |
前后需幾天時間 |
去Operator,僅編寫YAML,半個小時左右 |
運維成本 |
每個trait需單獨部署與穩定性保障 |
當成功地將大部分trait收斂成YAML配置,僅需部署維護一個Universal trait Operator,大大節省了運維成本 |
資源配置 |
每個trait都是單獨的Operator應用,最低配置1核1G,且需多副本部署 |
無資源消耗 |
標準化 |
僅輸入輸出標準化 |
不僅輸入輸出標準化,而且使開發過程標準化,能很好的避免因代碼不規范引起的bug |
拓展性 |
無 |
多類型task支持用戶自定義邏輯,有很好的切面拓展能力 |
穩定性 |
一次部署容易引發多次pod重建 |
避免多次重建 |
資源配置化開發去Operator,提供了通用trait的開發輸入輸出標準化管理,開發者僅需配置YAML,極大縮短了開發周期,同時將trait 應用收斂,降低運維成本和資源消耗,同時避免了多次重建保障了生產的穩定性。
五、總結
KubeNest作為有狀態應用的部署運維平臺,目標是“一鍵部署,隨處運行”,能夠極大的幫助用戶提高部署運維效率。trait配置化開發方案,現已上線KubeNest,經過雙十一的驗證,有效地保障了KubeNest上應用的穩定性。最后總結下trait配置化開發優點:
- 降本提效:該方案去operator應用能夠有效的收斂資源,同時YAML配置化開發大大的提高了開發效率和降低了運維(部署、升級)成本;
- 數據一致性:模版化保證了數據是面向終態的,使得開發無需關注數據順序,保障數據一致性,消除亂序帶來重啟的風險;
- 促進開源:該方案目前已經經過生產級的驗證,得到很好的反響,并輸出到KubeVela中,使得用戶自定義開發trait更為簡單,促進KubeVela開源生態的建設。
【本文為51CTO專欄作者“阿里巴巴官方技術”原創稿件,轉載請聯系原作者】