做者 | 孙健波(天元)
来源|阿里巴巴云原生公众号nginx
上个月,KubeVela 正式发布了, 做为一款简单易用且高度可扩展的应用管理平台与核心引擎,能够说是广大平台工程师用来构建本身的云原生 PaaS 的神兵利器。 那么本文就以一个实际的例子,讲解一下如何在 20 分钟内,为你基于 KubeVela 的 PaaS “上线“一个新能力。web
在正式开始本文档的教程以前,请确保你本地已经正确安装了 KubeVela 及其依赖的 K8s 环境。json
KubeVela 的基本架构如图所示:api
简单来讲,KubeVela 经过添加 Workload Type 和 Trait 来为用户扩展能力,平台的服务提供方经过 Definition 文件注册和扩展,向上经过 Appfile 透出扩展的功能。官方文档中也分别给出了基本的编写流程,其中 2 个是 Workload 的扩展例子,一个是 Trait 的扩展例子:restful
咱们以一个内置的 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 }
乍一看挺长的,好像很复杂,可是不要着急,其实细看之下它分为两部分:app
咱们拆开来慢慢介绍,其实学起来很简单。框架
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
为例,它的对应关系以下:ide
这里补充一个小知识,为何有了 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 的抽象模板。
对 CUE 自己有兴趣的同窗能够参考这篇 CUE 基础入门 多作一些了解,限于篇幅本文对 CUE 自己不详细展开。
你们知道 KubeVela 的 Appfile 写起来很简洁,可是 K8s 的对象是一个相对比较复杂的 YAML,而为了保持简洁的同时又不失可扩展性,KubeVela 提供了一个从复杂到简洁的桥梁。 这就是 Definition 中 CUE Template 的做用。
让咱们先来看一个 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 规则的基础上,增长了一些简便规则, 使其更易读易用:
编写好了模板部分,让咱们来构建参数部分,而这个参数其实就是变量的引用。
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 文件:
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