作者 | 孫健波(天元)
來源|阿里巴巴雲原生公衆號
上個月,KubeVela 正式發佈了, 作爲一款簡單易用且高度可擴展的應用管理平臺與核心引擎,可以說是廣大平臺工程師用來構建自己的雲原生 PaaS 的神兵利器。 那麼本文就以一個實際的例子,講解一下如何在 20 分鐘內,爲你基於 KubeVela 的 PaaS “上線“一個新能力。
在正式開始本文檔的教程之前,請確保你本地已經正確安裝了 KubeVela 及其依賴的 K8s 環境。
KubeVela 擴展的基本結構
KubeVela 的基本架構如圖所示:
簡單來說,KubeVela 通過添加 Workload Type 和 Trait 來爲用戶擴展能力,平臺的服務提供方通過 Definition 文件註冊和擴展,向上通過 Appfile 透出擴展的功能。官方文檔中也分別給出了基本的編寫流程,其中 2 個是 Workload 的擴展例子,一個是 Trait 的擴展例子:
我們以一個內置的 WorkloadDefinition 爲例來介紹一下 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
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)部分
我們拆開來慢慢介紹,其實學起來很簡單。
不含擴展字段的 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
爲例,它的對應關係如下:
這裏補充一個小知識,爲什麼有了 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