如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?
作者 | 孫健波(天元)
來(lái)源|阿里巴巴云原生公眾號(hào)
上個(gè)月,KubeVela 正式發(fā)布了, 作為一款簡(jiǎn)單易用且高度可擴(kuò)展的應(yīng)用管理平臺(tái)與核心引擎,可以說(shuō)是廣大平臺(tái)工程師用來(lái)構(gòu)建自己的云原生 PaaS 的神兵利器。 那么本文就以一個(gè)實(shí)際的例子,講解一下如何在 20 分鐘內(nèi),為你基于 KubeVela 的 PaaS “上線(xiàn)“一個(gè)新能力。
在正式開(kāi)始本文檔的教程之前,請(qǐng)確保你本地已經(jīng)正確安裝了 KubeVela 及其依賴(lài)的 K8s 環(huán)境。
KubeVela 擴(kuò)展的基本結(jié)構(gòu)
KubeVela 的基本架構(gòu)如圖所示:
簡(jiǎn)單來(lái)說(shuō),KubeVela 通過(guò)添加 Workload Type 和 Trait 來(lái)為用戶(hù)擴(kuò)展能力,平臺(tái)的服務(wù)提供方通過(guò) Definition 文件注冊(cè)和擴(kuò)展,向上通過(guò) Appfile 透出擴(kuò)展的功能。官方文檔中也分別給出了基本的編寫(xiě)流程,其中 2 個(gè)是 Workload 的擴(kuò)展例子,一個(gè)是 Trait 的擴(kuò)展例子:
- OpenFaaS 為例的 Workload Type 擴(kuò)展
- 云資源 RDS 為例的 Workload Type 擴(kuò)展
- KubeWatch 為例的 Trait 擴(kuò)展
我們以一個(gè)內(nèi)置的 WorkloadDefinition 為例來(lái)介紹一下 Definition 文件的基本結(jié)構(gòu):
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata:name: webserviceannotations:definition.oam.dev/description: "`Webservice` is a workload type to describe long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.If workload type is skipped for any service defined in Appfile, it will be defaulted to `Web Service` type." spec:definitionRef:name: deployments.appsextension:template: |output: {apiVersion: "apps/v1"kind: "Deployment"spec: {selector: matchLabels: {"app.oam.dev/component": context.name}template: {metadata: labels: {"app.oam.dev/component": context.name}spec: {containers: [{name: context.nameimage: parameter.imageif parameter["cmd"] != _|_ {command: parameter.cmd}if parameter["env"] != _|_ {env: parameter.env}if context["config"] != _|_ {env: context.config}ports: [{containerPort: parameter.port}]if parameter["cpu"] != _|_ {resources: {limits:cpu: parameter.cpurequests:cpu: parameter.cpu}}}]}}}}parameter: {// +usage=Which image would you like to use for your service// +short=iimage: string// +usage=Commands to run in the containercmd?: [...string]// +usage=Which port do you want customer traffic sent to// +short=pport: *80 | int// +usage=Define arguments by using environment variablesenv?: [...{// +usage=Environment variable namename: string// +usage=The value of the environment variablevalue?: string// +usage=Specifies a source the value of this var should come fromvalueFrom?: {// +usage=Selects a key of a secret in the pod's namespacesecretKeyRef: {// +usage=The name of the secret in the pod's namespace to select fromname: string// +usage=The key of the secret to select from. Must be a valid secret keykey: string}}}]// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)cpu?: string}乍一看挺長(zhǎng)的,好像很復(fù)雜,但是不要著急,其實(shí)細(xì)看之下它分為兩部分:
- 不含擴(kuò)展字段的 Definition 注冊(cè)部分
- 供 Appfile 使用的擴(kuò)展模板(CUE Template)部分
我們拆開(kāi)來(lái)慢慢介紹,其實(shí)學(xué)起來(lái)很簡(jiǎn)單。
不含擴(kuò)展字段的 Definition 注冊(cè)部分
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata:name: webserviceannotations:definition.oam.dev/description: "`Webservice` is a workload type to describe long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.If workload type is skipped for any service defined in Appfile, it will be defaulted to `Web Service` type." spec:definitionRef:name: deployments.apps這一部分滿(mǎn)打滿(mǎn)算 11 行,其中有 3 行是在介紹 webservice 的功能,5行是固定的格式。只有 2 行是有特定信息:
definitionRef:name: deployments.apps這兩行的意思代表了這個(gè) Definition 背后用的 CRD 名稱(chēng)是什么,其格式是 <resources>.<api-group>。了解 K8s 的同學(xué)應(yīng)該知道 K8s 中比較常用的是通過(guò) api-group, version 和 kind 定位資源,而 kind 在 K8s restful API 中對(duì)應(yīng)的是 resources。以大家熟悉 Deployment 和 ingress 為例,它的對(duì)應(yīng)關(guān)系如下:

這里補(bǔ)充一個(gè)小知識(shí),為什么有了 kind 還要加個(gè) resources 的概念呢? 因?yàn)橐粋€(gè) CRD 除了 kind 本身還有一些像 status,replica 這樣的字段希望跟 spec 本身解耦開(kāi)來(lái)在 restful API 中單獨(dú)更新, 所以 resources 除了 kind 對(duì)應(yīng)的那一個(gè),還會(huì)有一些額外的 resources,如 Deployment 的 status 表示為 deployments/status。
所以相信聰明的你已經(jīng)明白了不含 extension 的情況下,Definition 應(yīng)該怎么寫(xiě)了,最簡(jiǎn)單的就是根據(jù) K8s 的資源組合方式拼接一下,只要填下面三個(gè)尖括號(hào)的空格就可以了。
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata:name: <這里寫(xiě)名稱(chēng)> spec:definitionRef:name: <這里寫(xiě)resources>.<這里寫(xiě)api-group>針對(duì)運(yùn)維特征注冊(cè)(TraitDefinition)也是這樣。
apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata:name: <這里寫(xiě)名稱(chēng)> spec:definitionRef:name: <這里寫(xiě)resources>.<這里寫(xiě)api-group>所以把 Ingress 作為 KubeVela 的擴(kuò)展寫(xiě)進(jìn)去就是:
apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata:name: ingress spec:definitionRef:name: ingresses.networking.k8s.io除此之外,TraitDefinition 中還增加了一些其他功能模型層功能,如:
- appliesToWorkloads: 表示這個(gè) trait 可以作用于哪些 Workload 類(lèi)型。
- conflictWith: 表示這個(gè) trait 和哪些其他類(lèi)型的 trait 有沖突。
- workloadRefPath: 表示這個(gè) trait 包含的 workload 字段是哪個(gè),KubeVela 在生成 trait 對(duì)象時(shí)會(huì)自動(dòng)填充。 …
這些功能都是可選的,本文中不涉及使用,在后續(xù)的其他文章中我們?cè)俳o大家詳細(xì)介紹。
所以到這里,相信你已經(jīng)掌握了一個(gè)不含 extensions 的基本擴(kuò)展模式,而剩下部分就是圍繞 CUE 的抽象模板。
供 Appfile 使用的擴(kuò)展模板(CUE Template)部分
對(duì) CUE 本身有興趣的同學(xué)可以參考這篇 CUE 基礎(chǔ)入門(mén) 多做一些了解,限于篇幅本文對(duì) CUE 本身不詳細(xì)展開(kāi)。
大家知道 KubeVela 的 Appfile 寫(xiě)起來(lái)很簡(jiǎn)潔,但是 K8s 的對(duì)象是一個(gè)相對(duì)比較復(fù)雜的 YAML,而為了保持簡(jiǎn)潔的同時(shí)又不失可擴(kuò)展性,KubeVela 提供了一個(gè)從復(fù)雜到簡(jiǎn)潔的橋梁。 這就是 Definition 中 CUE Template 的作用。
CUE 格式模板
讓我們先來(lái)看一個(gè) Deployment 的 YAML 文件,如下所示,其中很多內(nèi)容都是固定的框架(模板部分),真正需要用戶(hù)填的內(nèi)容其實(shí)就少量的幾個(gè)字段(參數(shù)部分)。
apiVersion: apps/v1 kind: Deployment meadata:name: mytest spec:template:spec:containers:- name: mytestenv:- name: avalue: bimage: nginx:v1metadata:labels:app.oam.dev/component: mytestselector:matchLabels:app.oam.dev/component: mytest在 KubeVela 中,Definition 文件的固定格式就是分為 output 和 parameter 兩部分。其中output中的內(nèi)容就是“模板部分”,而 parameter 就是參數(shù)部分。
那我們來(lái)把上面的 Deployment YAML 改寫(xiě)成 Definition 中模板的格式。
output: {apiVersion: "apps/v1"kind: "Deployment"metadata: name: "mytest"spec: {selector: matchLabels: {"app.oam.dev/component": "mytest"}template: {metadata: labels: {"app.oam.dev/component": "mytest"}spec: {containers: [{name: "mytest"image: "nginx:v1"env: [{name:"a",value:"b"}]}]}}} }這個(gè)格式跟 json 很像,事實(shí)上這個(gè)是 CUE 的格式,而 CUE 本身就是一個(gè) json 的超集。也就是說(shuō),CUE的格式在滿(mǎn)足 JSON 規(guī)則的基礎(chǔ)上,增加了一些簡(jiǎn)便規(guī)則, 使其更易讀易用:
- C 語(yǔ)言的注釋風(fēng)格。
- 表示字段名稱(chēng)的雙引號(hào)在沒(méi)有特殊符號(hào)的情況下可以缺省。
- 字段值結(jié)尾的逗號(hào)可以缺省,在字段最后的逗號(hào)寫(xiě)了也不會(huì)出錯(cuò)。
- 最外層的大括號(hào)可以省略。
CUE 格式的模板參數(shù)–變量引用
編寫(xiě)好了模板部分,讓我們來(lái)構(gòu)建參數(shù)部分,而這個(gè)參數(shù)其實(shí)就是變量的引用。
parameter: {name: stringimage: string } output: {apiVersion: "apps/v1"kind: "Deployment"spec: {selector: matchLabels: {"app.oam.dev/component": parameter.name}template: {metadata: labels: {"app.oam.dev/component": parameter.name}spec: {containers: [{name: parameter.nameimage: parameter.image}]}}} }如上面的這個(gè)例子所示,KubeVela 中的模板參數(shù)就是通過(guò) parameter 這個(gè)部分來(lái)完成的,而 parameter 本質(zhì)上就是作為引用,替換掉了 output 中的某些字段。
完整的 Definition 以及在 Appfile 使用
事實(shí)上,經(jīng)過(guò)上面兩部分的組合,我們已經(jīng)可以寫(xiě)出一個(gè)完整的 Definition 文件:
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata:name: mydeploy spec:definitionRef:name: deployments.appsextension:template: |parameter: {name: stringimage: string}output: {apiVersion: "apps/v1"kind: "Deployment"spec: {selector: matchLabels: {"app.oam.dev/component": parameter.name}template: {metadata: labels: {"app.oam.dev/component": parameter.name}spec: {containers: [{name: parameter.nameimage: parameter.image}]}}}}為了方便調(diào)試,一般情況下可以預(yù)先分為兩個(gè)文件,一部分放前面的 yaml 部分,假設(shè)命名為 def.yaml 如:
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata:name: mydeploy spec:definitionRef:name: deployments.appsextension:template: |另一個(gè)則放 cue 文件,假設(shè)命名為 def.cue :
parameter: {name: stringimage: string } output: {apiVersion: "apps/v1"kind: "Deployment"spec: {selector: matchLabels: {"app.oam.dev/component": parameter.name}template: {metadata: labels: {"app.oam.dev/component": parameter.name}spec: {containers: [{name: parameter.nameimage: parameter.image}]}}} }先對(duì) def.cue 做一個(gè)格式化,格式化的同時(shí) cue 工具本身會(huì)做一些校驗(yàn),也可以更深入的通過(guò) cue 命令做調(diào)試:
cue fmt def.cue調(diào)試完成后,可以通過(guò)腳本把這個(gè) yaml 組裝:
./hack/vela-templates/mergedef.sh def.yaml def.cue > mydeploy.yaml再把這個(gè) yaml 文件 apply 到 K8s 集群中。
$ kubectl apply -f mydeploy.yaml workloaddefinition.core.oam.dev/mydeploy created一旦新能力 kubectl apply 到了 Kubernetes 中,不用重啟,也不用更新,KubeVela 的用戶(hù)可以立刻看到一個(gè)新的能力出現(xiàn)并且可以使用了:
$ vela worklaods Automatically discover capabilities successfully ? Add(1) Update(0) Delete(0)TYPE CATEGORY DESCRIPTION +mydeploy workload description not definedNAME DESCRIPTION mydeploy description not defined在 Appfile 中使用方式如下:
name: my-extend-app services:mysvc:type: mydeployimage: crccheck/hello-worldname: mysvc執(zhí)行 vela up 就能把這個(gè)運(yùn)行起來(lái)了:
$ vela up -f docs/examples/blog-extension/my-extend-app.yaml Parsing vela appfile ... Loading templates ...Rendering configs for service (mysvc)... Writing deploy config to (.vela/deploy.yaml)Applying deploy configs ... Checking if app has been deployed... App has not been deployed, creating a new deployment... ? App has been deployed 🚀🚀🚀Port forward: vela port-forward my-extend-appSSH: vela exec my-extend-appLogging: vela logs my-extend-appApp status: vela status my-extend-appService status: vela status my-extend-app --svc mysvc我們來(lái)查看一下應(yīng)用的狀態(tài),已經(jīng)正常運(yùn)行起來(lái)了(HEALTHY Ready: 1/1):
$ vela status my-extend-app About:Name: my-extend-appNamespace: env-applicationCreated at: 2020-12-15 16:32:25.08233 +0800 CSTUpdated at: 2020-12-15 16:32:25.08233 +0800 CSTServices:- Name: mysvcType: mydeployHEALTHY Ready: 1/1Definition 模板中的高級(jí)用法
上面我們已經(jīng)通過(guò)模板替換這個(gè)最基本的功能體驗(yàn)了擴(kuò)展 KubeVela 的全過(guò)程,除此之外,可能你還有一些比較復(fù)雜的需求,如條件判斷,循環(huán),復(fù)雜類(lèi)型等,需要一些高級(jí)的用法。
結(jié)構(gòu)體參數(shù)
如果模板中有一些參數(shù)類(lèi)型比較復(fù)雜,包含結(jié)構(gòu)體和嵌套的多個(gè)結(jié)構(gòu)體,就可以使用結(jié)構(gòu)體定義。
條件判斷
有時(shí)候某些參數(shù)加還是不加取決于某個(gè)條件:
parameter: {name: stringimage: stringuseENV: bool } output: {...spec: {containers: [{name: parameter.nameimage: parameter.imageif parameter.useENV == true {env: [{name: "my-env", value: "my-value"}]}}]}... }在 Appfile 就是寫(xiě)值。
name: my-extend-app services:mysvc:type: mydeployimage: crccheck/hello-worldname: mysvcuseENV: true可缺省參數(shù)
有些情況下參數(shù)可能存在也可能不存在,即非必填,這個(gè)時(shí)候一般要配合條件判斷使用,對(duì)于某個(gè)字段不存在的情況,判斷條件是是 _variable != _|_。
parameter: {name: stringimage: stringconfig?: [...#Config] } output: {...spec: {containers: [{name: parameter.nameimage: parameter.imageif parameter.config != _|_ {config: parameter.config}}]}... }這種情況下 Appfile 的 config 就非必填了,填了就渲染,沒(méi)填就不渲染。
默認(rèn)值
對(duì)于某些參數(shù)如果希望設(shè)置一個(gè)默認(rèn)值,可以采用這個(gè)寫(xiě)法。
parameter: {name: stringimage: *"nginx:v1" | string } output: {...spec: {containers: [{name: parameter.nameimage: parameter.image}]}... }這個(gè)時(shí)候 Appfile 就可以不寫(xiě) image 這個(gè)參數(shù),默認(rèn)使用 “nginx:v1”:
name: my-extend-app services:mysvc:type: mydeployname: mysvc循環(huán)
Map 類(lèi)型的循環(huán)
parameter: {name: stringimage: stringenv: [string]: string } output: {spec: {containers: [{name: parameter.nameimage: parameter.imageenv: [for k, v in parameter.env {name: kvalue: v},]}]} }Appfile 中的寫(xiě)法:
name: my-extend-app services:mysvc:type: mydeployname: "mysvc"image: "nginx"env:env1: value1env2: value2數(shù)組類(lèi)型的循環(huán)
parameter: {name: stringimage: stringenv: [...{name:string,value:string}] } output: {...spec: {containers: [{name: parameter.nameimage: parameter.imageenv: [for _, v in parameter.env {name: v.namevalue: v.value},]}]} }Appfile 中的寫(xiě)法:
name: my-extend-app services:mysvc:type: mydeployname: "mysvc"image: "nginx"env:- name: env1value: value1- name: env2value: value2KubeVela 內(nèi)置的 context 變量
大家可能也注意到了,我們?cè)?parameter 中定義的 name 每次在 Appfile中 實(shí)際上寫(xiě)了兩次,一次是在 services 下面(每個(gè)service都以名稱(chēng)區(qū)分), 另一次則是在具體的name參數(shù)里面。事實(shí)上這里重復(fù)的不應(yīng)該由用戶(hù)再寫(xiě)一遍,所以 KubeVela 中還定義了一個(gè)內(nèi)置的 context,里面存放了一些通用的環(huán)境上下文信息,如應(yīng)用名稱(chēng)、秘鑰等。 直接在模板中使用 context 就不需要額外增加一個(gè) name 參數(shù)了, KubeVela 在運(yùn)行渲染模板的過(guò)程中會(huì)自動(dòng)傳入。
parameter: {image: string } output: {...spec: {containers: [{name: context.nameimage: parameter.image}]}... }KubeVela 中的注釋增強(qiáng)
KubeVela 還對(duì) cuelang 的注釋做了一些擴(kuò)展,方便自動(dòng)生成文檔以及被 CLI 使用。
parameter: {// +usage=Which image would you like to use for your service// +short=iimage: string// +usage=Commands to run in the containercmd?: [...string]...}其中,+usgae 開(kāi)頭的注釋會(huì)變成參數(shù)的說(shuō)明,+short 開(kāi)頭的注釋后面則是在 CLI 中使用的縮寫(xiě)。
總結(jié)
本文通過(guò)實(shí)際的案例和詳細(xì)的講述,為你介紹了在 KubeVela 中新增一個(gè)能力的詳細(xì)過(guò)程與原理,以及能力模板的編寫(xiě)方法。
這里你可能還有個(gè)疑問(wèn),平臺(tái)管理員這樣添加了一個(gè)新能力后,平臺(tái)的用戶(hù)又該怎么能知道這個(gè)能力怎么使用呢?其實(shí),在 KubeVela 中,它不僅能方便的添加新能力,它還能自動(dòng)為“能力”生成 Markdown 格式的使用文檔! 不信,你可以看下 KubeVela 本身的官方網(wǎng)站,所有在 References/Capabilities 目錄下能力使用說(shuō)明文檔(比如這個(gè)),全都是根據(jù)每個(gè)能力的模板自動(dòng)生成的哦。 最后,歡迎大家寫(xiě)一些有趣的擴(kuò)展功能,提交到 KubeVela 的社區(qū)倉(cāng)庫(kù)中來(lái)。
如果你有任何疑問(wèn),歡迎釘釘搜索群號(hào):23310022 加入交流群。
總結(jié)
以上是生活随笔為你收集整理的如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MSHA x Chaos 容灾高可用实践
- 下一篇: Seata-AT 如何保证分布式事务一致