做者 | 孫健波(天元) 來源|阿里巴巴雲原生公衆號nginx
上個月,KubeVela 正式發佈了, 做爲一款簡單易用且高度可擴展的應用管理平臺與核心引擎,能夠說是廣大平臺工程師用來構建本身的雲原生 PaaS 的神兵利器。 那麼本文就以一個實際的例子,講解一下如何在 20 分鐘內,爲你基於 KubeVela 的 PaaS 「上線「一個新能力。git
在正式開始本文檔的教程以前,請確保你本地已經正確安裝了 KubeVela 及其依賴的 K8s 環境。github
KubeVela 擴展的基本結構
KubeVela 的基本架構如圖所示:web
簡單來講,KubeVela 經過添加 Workload Type 和 Trait 來爲用戶擴展能力,平臺的服務提供方經過 Definition 文件註冊和擴展,向上經過 Appfile 透出擴展的功能。官方文檔中也分別給出了基本的編寫流程,其中 2 個是 Workload 的擴展例子,一個是 Trait 的擴展例子:json
咱們以一個內置的 WorkloadDefinition 爲例來介紹一下 Definition 文件的基本結構:api
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: webservice annotations: 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 extension: 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.name image: parameter.image if 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.cpu requests: cpu: parameter.cpu }} }] }}} } parameter: { // +usage=Which image would you like to use for your service // +short=i image: string // +usage=Commands to run in the container cmd?: [...string] // +usage=Which port do you want customer traffic sent to // +short=p port: *80 | int // +usage=Define arguments by using environment variables env?: [...{ // +usage=Environment variable name name: string // +usage=The value of the environment variable value?: string // +usage=Specifies a source the value of this var should come from valueFrom?: { // +usage=Selects a key of a secret in the pod's namespace secretKeyRef: { // +usage=The name of the secret in the pod's namespace to select from name: string // +usage=The key of the secret to select from. Must be a valid secret key key: string } } }] // +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) cpu?: string }
乍一看挺長的,好像很複雜,可是不要着急,其實細看之下它分爲兩部分:數組
- 不含擴展字段的 Definition 註冊部分
- 供 Appfile 使用的擴展模板(CUE Template)部分
咱們拆開來慢慢介紹,其實學起來很簡單。restful
不含擴展字段的 Definition 註冊部分
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: webservice annotations: 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
這一部分滿打滿算 11 行,其中有 3 行是在介紹 webservice
的功能,5行是固定的格式。只有 2 行是有特定信息:架構
definitionRef: name: deployments.apps
這兩行的意思表明了這個 Definition 背後用的 CRD 名稱是什麼,其格式是 <resources>.<api-group>
。瞭解 K8s 的同窗應該知道 K8s 中比較經常使用的是經過 api-group
, version
和 kind
定位資源,而 kind
在 K8s restful API 中對應的是 resources
。以你們熟悉 Deployment
和 ingress
爲例,它的對應關係以下:app
這裏補充一個小知識,爲何有了 kind 還要加個 resources 的概念呢? 由於一個 CRD 除了 kind 自己還有一些像 status,replica 這樣的字段但願跟 spec 自己解耦開來在 restful API 中單獨更新, 因此 resources 除了 kind 對應的那一個,還會有一些額外的 resources,如 Deployment 的 status 表示爲
deployments/status
。
因此相信聰明的你已經明白了不含 extension 的狀況下,Definition 應該怎麼寫了,最簡單的就是根據 K8s 的資源組合方式拼接一下,只要填下面三個尖括號的空格就能夠了。
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: <這裏寫名稱> spec: definitionRef: name: <這裏寫resources>.<這裏寫api-group>
針對運維特徵註冊(TraitDefinition)也是這樣。
apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata: name: <這裏寫名稱> spec: definitionRef: name: <這裏寫resources>.<這裏寫api-group>
因此把 Ingress
做爲 KubeVela 的擴展寫進去就是:
apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata: name: ingress spec: definitionRef: name: ingresses.networking.k8s.io
除此以外,TraitDefinition 中還增長了一些其餘功能模型層功能,如:
appliesToWorkloads
: 表示這個 trait 能夠做用於哪些 Workload 類型。conflictWith
: 表示這個 trait 和哪些其餘類型的 trait 有衝突。workloadRefPath
: 表示這個 trait 包含的 workload 字段是哪一個,KubeVela 在生成 trait 對象時會自動填充。 ...
這些功能都是可選的,本文中不涉及使用,在後續的其餘文章中咱們再給你們詳細介紹。
因此到這裏,相信你已經掌握了一個不含 extensions 的基本擴展模式,而剩下部分就是圍繞 CUE 的抽象模板。
供 Appfile 使用的擴展模板(CUE Template)部分
對 CUE 自己有興趣的同窗能夠參考這篇 CUE 基礎入門 多作一些瞭解,限於篇幅本文對 CUE 自己不詳細展開。
你們知道 KubeVela 的 Appfile 寫起來很簡潔,可是 K8s 的對象是一個相對比較複雜的 YAML,而爲了保持簡潔的同時又不失可擴展性,KubeVela 提供了一個從複雜到簡潔的橋樑。 這就是 Definition 中 CUE Template 的做用。
CUE 格式模板
讓咱們先來看一個 Deployment 的 YAML 文件,以下所示,其中不少內容都是固定的框架(模板部分),真正須要用戶填的內容其實就少許的幾個字段(參數部分)。
apiVersion: apps/v1 kind: Deployment meadata: name: mytest spec: template: spec: containers: - name: mytest env: - name: a value: b image: nginx:v1 metadata: labels: app.oam.dev/component: mytest selector: matchLabels: app.oam.dev/component: mytest
在 KubeVela 中,Definition 文件的固定格式就是分爲 output
和 parameter
兩部分。其中output
中的內容就是「模板部分」,而 parameter
就是參數部分。
那咱們來把上面的 Deployment YAML 改寫成 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"}] }] }}} }
這個格式跟 json 很像,事實上這個是 CUE 的格式,而 CUE 自己就是一個 json 的超集。也就是說,CUE的格式在知足 JSON 規則的基礎上,增長了一些簡便規則, 使其更易讀易用:
- C 語言的註釋風格。
- 表示字段名稱的雙引號在沒有特殊符號的狀況下能夠缺省。
- 字段值結尾的逗號能夠缺省,在字段最後的逗號寫了也不會出錯。
- 最外層的大括號能夠省略。
CUE 格式的模板參數--變量引用
編寫好了模板部分,讓咱們來構建參數部分,而這個參數其實就是變量的引用。
parameter: { name: string image: 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.name image: parameter.image }] }}} }
如上面的這個例子所示,KubeVela 中的模板參數就是經過 parameter
這個部分來完成的,而 parameter
本質上就是做爲引用,替換掉了 output
中的某些字段。
完整的 Definition 以及在 Appfile 使用
事實上,通過上面兩部分的組合,咱們已經能夠寫出一個完整的 Definition 文件:
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: mydeploy spec: definitionRef: name: deployments.apps extension: template: | parameter: { name: string image: 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.name image: parameter.image }] }}} }
爲了方便調試,通常狀況下能夠預先分爲兩個文件,一部分放前面的 yaml 部分,假設命名爲 def.yaml
如:
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: mydeploy spec: definitionRef: name: deployments.apps extension: template: |
另外一個則放 cue 文件,假設命名爲 def.cue
:
parameter: { name: string image: 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.name image: parameter.image }] }}} }
先對 def.cue
作一個格式化,格式化的同時 cue 工具自己會作一些校驗,也能夠更深刻的經過 cue 命令作調試:
cue fmt def.cue
調試完成後,能夠經過腳本把這個 yaml 組裝:
./hack/vela-templates/mergedef.sh def.yaml def.cue > mydeploy.yaml
再把這個 yaml 文件 apply 到 K8s 集羣中。
$ kubectl apply -f mydeploy.yaml workloaddefinition.core.oam.dev/mydeploy created
一旦新能力 kubectl apply
到了 Kubernetes 中,不用重啓,也不用更新,KubeVela 的用戶能夠馬上看到一個新的能力出現而且可使用了:
$ vela worklaods Automatically discover capabilities successfully ✅ Add(1) Update(0) Delete(0) TYPE CATEGORY DESCRIPTION +mydeploy workload description not defined NAME DESCRIPTION mydeploy description not defined
在 Appfile 中使用方式以下:
name: my-extend-app services: mysvc: type: mydeploy image: crccheck/hello-world name: mysvc
執行 vela up
就能把這個運行起來了:
$ 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-app SSH: vela exec my-extend-app Logging: vela logs my-extend-app App status: vela status my-extend-app Service status: vela status my-extend-app --svc mysvc
咱們來查看一下應用的狀態,已經正常運行起來了(HEALTHY Ready: 1/1
):
$ vela status my-extend-app About: Name: my-extend-app Namespace: env-application Created at: 2020-12-15 16:32:25.08233 +0800 CST Updated at: 2020-12-15 16:32:25.08233 +0800 CST Services: - Name: mysvc Type: mydeploy HEALTHY Ready: 1/1
Definition 模板中的高級用法
上面咱們已經經過模板替換這個最基本的功能體驗了擴展 KubeVela 的全過程,除此以外,可能你還有一些比較複雜的需求,如條件判斷,循環,複雜類型等,須要一些高級的用法。
結構體參數
若是模板中有一些參數類型比較複雜,包含結構體和嵌套的多個結構體,就可使用結構體定義。
- 定義一個結構體類型,包含 1 個字符串成員、1 個整型和 1 個結構體成員。
#Config: { name: string value: int other: { key: string value: string } }
- 在變量中使用這個結構體類型,並做爲數組使用。
parameter: { name: string image: string config: [...#Config] }
- 一樣的目標中也是以變量引用的方式使用。
output: { ... spec: { containers: [{ name: parameter.name image: parameter.image env: parameter.config }] } ... }
- Appfile 中的寫法就是按照 parameter 定義的結構編寫。
name: my-extend-app services: mysvc: type: mydeploy image: crccheck/hello-world name: mysvc config: - name: a value: 1 other: key: mykey value: myvalue
條件判斷
有時候某些參數加仍是不加取決於某個條件:
parameter: { name: string image: string useENV: bool } output: { ... spec: { containers: [{ name: parameter.name image: parameter.image if parameter.useENV == true { env: [{name: "my-env", value: "my-value"}] } }] } ... }
在 Appfile 就是寫值。
name: my-extend-app services: mysvc: type: mydeploy image: crccheck/hello-world name: mysvc useENV: true
可缺省參數
有些狀況下參數可能存在也可能不存在,即非必填,這個時候通常要配合條件判斷使用,對於某個字段不存在的狀況,判斷條件是是 _variable != _|_
。
parameter: { name: string image: string config?: [...#Config] } output: { ... spec: { containers: [{ name: parameter.name image: parameter.image if parameter.config != _|_ { config: parameter.config } }] } ... }
這種狀況下 Appfile 的 config 就非必填了,填了就渲染,沒填就不渲染。
默認值
對於某些參數若是但願設置一個默認值,能夠採用這個寫法。
parameter: { name: string image: *"nginx:v1" | string } output: { ... spec: { containers: [{ name: parameter.name image: parameter.image }] } ... }
這個時候 Appfile 就能夠不寫 image 這個參數,默認使用 "nginx:v1":
name: my-extend-app services: mysvc: type: mydeploy name: mysvc
循環
Map 類型的循環
parameter: { name: string image: string env: [string]: string } output: { spec: { containers: [{ name: parameter.name image: parameter.image env: [ for k, v in parameter.env { name: k value: v }, ] }] } }
Appfile 中的寫法:
name: my-extend-app services: mysvc: type: mydeploy name: "mysvc" image: "nginx" env: env1: value1 env2: value2
數組類型的循環
parameter: { name: string image: string env: [...{name:string,value:string}] } output: { ... spec: { containers: [{ name: parameter.name image: parameter.image env: [ for _, v in parameter.env { name: v.name value: v.value }, ] }] } }
Appfile 中的寫法:
name: my-extend-app services: mysvc: type: mydeploy name: "mysvc" image: "nginx" env: - name: env1 value: value1 - name: env2 value: value2
KubeVela 內置的 context
變量
你們可能也注意到了,咱們在 parameter 中定義的 name 每次在 Appfile中 實際上寫了兩次,一次是在 services 下面(每一個service都以名稱區分), 另外一次則是在具體的name
參數裏面。事實上這裏重複的不該該由用戶再寫一遍,因此 KubeVela 中還定義了一個內置的 context
,裏面存放了一些通用的環境上下文信息,如應用名稱、祕鑰等。 直接在模板中使用 context 就不須要額外增長一個 name
參數了, KubeVela 在運行渲染模板的過程當中會自動傳入。
parameter: { image: string } output: { ... spec: { containers: [{ name: context.name image: parameter.image }] } ... }
KubeVela 中的註釋加強
KubeVela 還對 cuelang 的註釋作了一些擴展,方便自動生成文檔以及被 CLI 使用。
parameter: { // +usage=Which image would you like to use for your service // +short=i image: string // +usage=Commands to run in the container cmd?: [...string] ... }
其中,+usgae
開頭的註釋會變成參數的說明,+short
開頭的註釋後面則是在 CLI 中使用的縮寫。
總結
本文經過實際的案例和詳細的講述,爲你介紹了在 KubeVela 中新增一個能力的詳細過程與原理,以及能力模板的編寫方法。
這裏你可能還有個疑問,平臺管理員這樣添加了一個新能力後,平臺的用戶又該怎麼能知道這個能力怎麼使用呢?其實,在 KubeVela 中,它不只能方便的添加新能力,它還能自動爲「能力」生成 Markdown 格式的使用文檔! 不信,你能夠看下 KubeVela 自己的官方網站,全部在 References/Capabilities
目錄下能力使用說明文檔(好比這個),全都是根據每一個能力的模板自動生成的哦。 最後,歡迎你們寫一些有趣的擴展功能,提交到 KubeVela 的社區倉庫中來。
若是你有任何疑問,歡迎釘釘搜索羣號:23310022 加入交流羣。