Skip to main content

如何在 20 分钟内给你的 K8s PaaS 上线一个新功能

wonderflow

wonderflow

OAM/KubeVela maintainer

2020年12月14日 19:33, by @wonderflow

上个月,KubeVela 正式发布了, 作为一款简单易用且高度可扩展的应用管理平台与核心引擎,可以说是广大平台工程师用来构建自己的云原生 PaaS 的神兵利器。 那么本文就以一个实际的例子,讲解一下如何在 20 分钟内,为你基于 KubeVela 的 PaaS “上线“一个新能力。

在正式开始本文档的教程之前,请确保你本地已经正确安装了 KubeVela 及其依赖的 K8s 环境。

KubeVela 扩展的基本结构#

KubeVela 的基本架构如图所示:

image

简单来说,KubeVela 通过添加 Workload TypeTrait 来为用户扩展能力,平台的服务提供方通过 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, versionkind 定位资源,而 kind 在 K8s restful API 中对应的是 resources。以大家熟悉 Deploymentingress 为例,它的对应关系如下:

api-groupkindversionresources
appsDeploymentv1deployments
networking.k8s.ioIngressv1ingresses

这里补充一个小知识,为什么有了 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 文件的固定格式就是分为 outputparameter 两部分。其中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个整型和1个结构体成员。
#Config: {
name: string
value: int
other: {
key: string
value: string
}
}
  1. 在变量中使用这个结构体类型,并作为数组使用。
parameter: {
name: string
image: string
config: [...#Config]
}
  1. 同样的目标中也是以变量引用的方式使用。
output: {
...
spec: {
containers: [{
name: parameter.name
image: parameter.image
env: parameter.config
}]
}
...
}
  1. 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 的社区仓库中来。

KubeVela 官方文档翻译活动

背景#

KubeVela v1.0 启用了新的官网架构和文档维护方式,新增功能包括文档版本化控制、i18n 国际化以及自动化流程。但目前 KubeVela 官方文档只有英文版,这提高了学习和使用 KubeVela 的门槛,不利于项目的传播和发展,同时翻译工作也能显著提升语言能力,帮助我们拓宽阅读技术资料的广度,故组织本次活动。

活动流程#

本次活动主要在 kubevela.io repo 下进行,报名参与和认领任务都在 KubeVela 官方文档翻译登记 中(请务必在表格中登记信息)。

开始翻译#

翻译流程

参与翻译活动的基本流程如下:

  • 任务领取:在 KubeVela 官方文档翻译登记 登记并认领任务;
  • 提交:参与人员提交 PR 等待 review;
  • 审阅:maintainer 审阅 PR;
  • 终审: 对 review 后的内容进行最后确认;
  • 合并:merge 到 master 分支,任务结束。

参与指南#

下面具体介绍参与翻译的具体工作。

准备工作#

  • 账号:您需要先准备一个 GitHub 账号。使用 Github 进行翻译任务的认领和 PR 提交。
  • 仓库和分支管理
    • fork kubevela.io 的仓库,并作为自己仓库的上游: git remote add upstream https://github.com/oam-dev/kubevela.io.git
    • 在自己的仓库,也就是 origin 上进行翻译;
    • 一个任务新建一个 branch
  • Node.js 版本 >= 12.13.0 (可以使用 node -v 命令查看)
  • Yarn 版本 >= 1.5(可以使用 yarn --version 命令查看)

参与步骤#

Step1:任务浏览

KubeVela 官方文档翻译登记 登记并浏览有哪些任务可以认领。

Step2:任务领取

KubeVela 官方文档翻译登记 表格中编辑并认领任务。注意:为保证质量,同一译者只能同时认领三个任务,完成后才可继续认领。

Step3:本地构建和预览

# 命令安装依赖
$ yarn install
# 本地运行中文文档
$ yarn run start -- --locale zh
yarn run v1.22.10
warning From Yarn 1.0 onwards, scripts don't require "--" for options to be forwarded. In a future version, any explicit "--" will be forwarded as-is to the scripts.
$ docusaurus start --locale zh
Starting the development server...
Docusaurus website is running at: http://localhost:3000/zh/
✔ Client
Compiled successfully in 7.54s
ℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /zh/
ℹ 「wds」: Content not from webpack is served from /Users/saybot/own/kubevela.io
ℹ 「wds」: 404s will fallback to /index.html
✔ Client
Compiled successfully in 137.94ms

请勿修改 /docs 目录下内容,中文文档在 /i18n/zh/docusaurus-plugin-content-docs 中,之后就可以在 http://localhost:3000/zh/ 中进行预览了。

Step4:提交 PR

确认翻译完成就可以提交 PR 了,注意:为了方便 review 每篇翻译为一个 PR,如果翻译多篇请 checkout 多个分支并提交多个 PR。

Step5:审阅

由 maintainer 对 PR 进行 review。

Step6:任务完成

翻译合格的文章将会 merge 到 kubevela.io 的 master 分支进行发布。

翻译要求#

  • 数字和英文两边是中文要加空格。
  • KubeVela 统一写法。K 和 V 大写。
  • 翻译完请先阅读一遍,不要出现遗漏段落,保证文章通顺、符合中文阅读习惯。不追求严格一致,可以意译。review 的时候也会检验。
  • 你和您不要混用,统一使用用 “你”
  • 不会翻译的词汇可以不翻译,可以在 PR 中说明,review 的时候会查看。
  • Component、Workload、Trait 这些 OAM/KubeVela 里面定义的专属概念不要翻译,我们也要加强这些词汇的认知。可以在一篇新文章最开始出现的时候用括号加上中文翻译。
  • 注意中英文标点符号。
  • PR 命名规范 Translate <翻译文件相对路径>,如 Translate i18n/zh/docusaurus-plugin-content-docs/current/introduction.md

KubeVela 正式开源:一个高可扩展的云原生应用平台与核心引擎

Lei Zhang and Fei Guo

Lei Zhang and Fei Guo

CNCF TOC Member/Kubernetes

2020年12月7日 12:33, by Lei Zhang and Fei Guo

image

美国西部时间 2020 年 11 月 18 日,在云原生技术“最高盛宴”的 KubeCon 北美峰会 2020 上,CNCF 应用交付领域小组(CNCF SIG App Delivery) 与 Open Application Model (OAM) 社区,以及来自阿里云、微软云的 OAM 项目维护者们在演讲中共同宣布了 KubeVela 开源项目的正式发布。

从 11 月 18 号到 20 号,在为期三天的 KubeCon 北美峰会上有连续 3 场技术演讲,会从不同维度介绍关于 KubeVela 项目的具体细节,其中还包括一个长达 1 个半小时的 KubeVela 互动教学环节。多个重量级组织以如此规模和密度在 KubeCon 北美峰会演讲中介绍一个首次发布的社区开源项目,在 KubeCon 诞生以来并不多见。

KubeVela Github 地址:https://github.com/oam-dev/kubevela/

什么是 KubeVela ?#

一言以蔽之,KubeVela 是一个简单易用且高度可扩展的应用管理平台与核心引擎。KubeVela 是基于 Kubernetes 与 OAM 技术构建的。

详细的说,对于应用开发人员来讲,KubeVela 是一个非常低心智负担的云原生应用管理平台,核心功能是让开发人员方便快捷地在 Kubernetes 上定义与交付现代微服务应用,无需了解任何 Kubernetes 本身相关的细节。在这一点上,KubeVela 可以被认为是云原生社区的 Heroku

另一方面,对于平台团队来讲,KubeVela 是一个强大并且高可扩展的云原生应用平台核心引擎。基于这样一个引擎,平台团队可以快速、高效地以 Kubernetes 原生的方式在 KubeVela 中植入任何来自云原生社区的应用管理能力,从而基于 KubeVela 打造出自己需要的云原生平台,比如:云原生数据库 PaaS、云原生 AI 平台、甚至 Serverless 服务。在这一点上,KubeVela 可以被认为是一个“以应用为中心”的 Kubernetes 发行版,以 OAM 为核心,让平台团队可以基于 KubeVela 快速打造出属于自己的 PaaS、Serverless 乃至任何面向用户的云原生平台项目。

KubeVela 解决了什么问题?#

现如今,云原生技术的迅猛发展可能让很多人都感觉到眼花缭乱,但实际上,这个生态的总体发展趋势和主旋律,是通过 Kubernetes 搭建了一个统一的基础设施抽象层,为平台团队屏蔽掉了“计算”、“网络”、“存储”等过去我们不得不关注的基础设施概念,使得我们能够基于 Kubernetes 方便地构建出任何我们想要的垂直业务系统而无需关心任何基础设施层的细节。这正是 Kubernetes 被称为云计算界的 Linux 以及 “Platform for Platforms” 的根本原因。

但是,当我们把视角从平台团队提升到垂直业务系统的最终用户(如:应用开发人员)的时候,我们会发现 Kubernetes 这样的定位和设计在解决了平台团队的大问题之后,却也为应用开发者们带来了挑战和困扰。比如,太多的用户都在抱怨 Kubernetes “太复杂了”。究其原因,其实在于 Kubernetes 中的核心概念与体系,如:Pod、sidecar、Service、资源管理、调度算法和 CRD 等等,主要是面向平台团队而非最终用户设计的。缺乏面向用户的设计不仅带来了陡峭的学习曲线,影响了用户的使用体验,拖慢了研发效能,甚至在很多情况下还会引发错误操作乃至生产故障(毕竟不可能每个业务开发人员都是 Kubernetes 专家)。

这也是为什么在云原生生态中,几乎每一个平台团队都会基于 Kubernetes 构建一个上层平台给用户使用。最简单的也会给 Kubernetes 做一个图形界面,稍微正式一些的则往往会基于 Kubernetes 开发一个类 PaaS 平台来满足自己的需求。理论上讲,在 Kubernetes 生态中各种能力已经非常丰富的今天,开发一个类 PaaS 平台应该是比较容易的。

然而现实却往往不尽如人意。在大量的社区访谈当中,我们发现在云原生技术极大普及的今天,基于 Kubernetes 构建一个功能完善、用户友好的上层应用平台,依然是中大型公司们的“专利”。这里的原因在于:

Kubernetes 生态本身的能力池固然丰富,但是社区里却并没有一个可扩展的、方便快捷的方式,能够帮助平台团队把这些能力快速“组装”成面向最终用户的功能(Feature)。

这种困境带来的结果,就是尽管大家今天都在基于 Kubernetes 在构建上层应用平台,但这些平台本质上却并不能够与 Kubernetes 生态完全打通,而都变成一个又一个的垂直“烟囱”。

在开源社区中,这个问题会更加明显。在今天的 Kubernetes 社区中,不乏各种“面向用户”、“面向应用”的 Kubernetes 上层系统。但正如前文所述,这些平台都无一例外的引入了自己的专属上层抽象、用户界面和插件机制。这里最典型的例子包括经典 PaaS 项目比如 Cloud Foundry,也包括各种 Serverless 平台。作为一个公司的平台团队,我们实际上只有两个选择:要么把自己局限在某种垂直的场景中来适配和采纳某个开源上层平台项目;要么就只能自研一个符合自己诉求的上层平台并且造无数个社区中已经存在的“轮子”。

那么,有没有”第三种选择”能够让平台团队在不造轮子、完全打通 Kubernetes 生态的前提下,轻松的构建面向用户的上层平台呢?

KubeVela 如何解决上述问题?#

KubeVela 项目的创立初衷,就是以一个统一的方式同时解决上述最终用户与平台团队所面临的困境。这也是为何在设计中,KubeVela 对最终用户和平台团队这两种群体进行了单独的画像,以满足他们不同的诉求。

由于 KubeVela 默认的功能集与“Heroku”类似(即:主要面向应用开发人员),所以在下文中,我们会以应用开发人员或者开发者来代替最终用户。但我们很快也会讲到,KubeVela 里的每一个功能,都是一个插件,作为平台团队,你可以轻松地“卸载”它的所有内置能力、然后“安装”自己需要的任何社区能力,让 KubeVela 变成一个完全不一样的系统。

1. 应用开发者眼中的 KubeVela#

前面已经提到,对于开发者来说,KubeVela 是一个简单、易用、又高可扩展的云原生应用管理工具,它可以让开发者以极低的心智负担和上手成本在 Kubernetes 上定义与部署应用。而关于整个系统的使用,开发者只需要编写一个 docker-compose 风格应用描述文件 Appfile 即可,不需要接触和学习任何 Kubernetes 层的相关细节。

1)一个 Appfile 示例#

在下述例子中,我们会将一个叫做 vela.yaml 的 Appfile 放在你的应用代码目录中(比如应用的 GitHub Repo)。这个 Appfile 定义了如何将这个应用编译成 Docker 镜像,如何将镜像部署到 Kubernetes,如何配置外界访问应用的路由和域名,又如何让 Kubernetes 自动根据 CPU 使用量来水平扩展这个应用。

name: testapp
services:
express-server:
image: oamdev/testapp:v1
build:
docker:
file: Dockerfile
contrxt: .
cmd: ["node", "server.js"]
port: 8080
cpu: "0.01"
route:
domain: example.com
rules:
- path: /testapp
rewriteTarget: /
autoscale:
min: 1
max: 4
cpuPercent: 5

只要有了这个 20 行的配置文件,你接下来唯一需要的事情就是 $ vela up,这个应用就会被部署到 Kubernetes 中然后被外界以 https://example.com/testapp 的方式访问到。

2)Appfile 是如何工作的?#

在 KubeVela 的 Appfile 背后,有着非常精妙的设计。首先需要指出的就是,这个 Appfile 是没有固定的 Schema 的

什么意思呢?这个 Appfile 里面你能够填写的每一个字段,都是直接取决于当前平台中有哪些工作负载类型(Workload Types)和应用特征(Traits)是可用的。而熟悉 OAM 的同学都知道,这两个概念,正是 OAM 规范的核心内容,其中:

  • 工作负载类型(Workload Type),定义的是底层基础设施如何运行这个应用。在上面的例子中,我们声明:名叫 testapp 的应用会启动一个类型为“在线 Web 服务(Web Service)” 的工作负载,其实例的名字是 express-server。

  • 应用特征(Traits),则为工作负载实例加上了运维时配置。在上面的例子中,我们定义了一个 Route Trait 来描述应用如何被从外界访问,以及一个 Autoscale Trait 来描述应用如何根据 CPU 使用量进行自动的水平扩容。

而正是基于这种模块化的设计,这个 Appfile 本身是高度可扩展的。当任何一个新的 Workload Type 或者 Trait 被安装到平台后,用户就可以立刻在 Appfile 里声明使用这个新增的能力。举个例子,比如后面平台团队新开发了一个用来配置应用监控属性的运维侧能力,叫做 Metrics。那么只需要举手之捞,应用开发者就可以立刻使用 $ vela show metrics 命令查看这个新增能力的详情,并且在 Appfile 中使用它,如下所示:

name: testapp
services:
express-server:
type: webservice
image: oamdev/testapp:v1
build:
docker:
file: Dockerfile
contrxt: .
cmd: ["node", "server.js"]
port: 8080
cpu: "0.01"
route:
domain: example.com
rules:
- path: /testapp
rewriteTarget: /
autoscale:
min: 1
max: 4
cpuPercent: 5
metrices:
port: 8080
path: "/metrics"
scheme: "http"
enabled: true

这种简单友好、又高度敏捷的使用体验,正是 KubeVela 在最终用户侧提供的主要体感。

3)Vela Up 命令#

前面提到,一旦 Appfile 准备好,开发者只需要一句 vela up 命令就可以把整个应用连同它的运维特征部署到 Kubernetes 中。部署成功后,你可以使用 vela status 来查看整个应用的详情,包括如何访问这个应用。

通过 KubeVela 部署的应用会被自动设置好访问 URL(以及不同版本对应的不同 URL),并且会由 cert-manager 生成好证书。与此同时,KubeVela 还提供了一系列辅助命令(比如:vela logs 和 vela exec)来帮助你在无需成为 Kubernetes 专家的情况下更好地管理和调试你的应用。如果你对上述由 KubeVela 带来的开发者体验感兴趣的话,欢迎前往 KubeVela 项目的用户使用文档来了解更多。

而接下来,我们要切换一下视角,感受一下平台团队眼中的 KubeVela 又是什么样子的。

2. 平台工程师眼中的 KubeVela#

实际上,前面介绍到的所有开发者侧体验,都离不开 KubeVela 在平台侧进行的各种创新性设计与实现。也正是这些设计的存在,才使得 KubeVela 不是一个简单的 PaaS 或者 Serverless,而是一个可以由平台工程师扩展成任意垂直系统的云原生平台内核

具体来说,KubeVela 为平台工程师提供了三大核心能力,使得基于 Kubernetes 构建上述面向用户的云原生平台从“阳春白雪”变成了“小菜一碟”:

第一:以应用为中心。在 Appfile 背后,其实就是“应用”这个概念,它是基于 OAM 模型实现的。通过这样的方式,KubeVela 让“应用”这个概念成为了整个平台对用户暴露的核心 API。KubeVela 中的所有能力,都是围绕着“应用”展开的。这正是为何基于 KubeVela 扩展和构建出来的平台,天然是用户友好的:对于一个开发者来说,他只关心“应用”,而不是容器或者 Kubernetes;而 KubeVela 会确保构建整个平台的过程,也只与应用层的需求有关。

第二:Kubernetes 原生的高可扩展性。在前面我们已经提到过,Appfile 是一个由 Workload Type 和 Trait 组成的、完全模块化的对象。而 OAM 模型的一个特点,就是任意一个 Kubernetes API 资源,都可以直接基于 Kubernetes 的 CRD 发现机制注册为一个 Workload Type 或者 Trait。这种可扩展性,使得 KubeVela 并不需要设计任何“插件系统”:KubeVela 里的每一个能力,都是插件,而整个 Kubernetes 社区,就是 KubeVela 原生的插件中心

第三:简单友好但高度可扩展的用户侧抽象体系。在了解了 Appfile 之后,你可能已经对这个对象的实现方式产生了好奇。实际上,KubeVela 中并不是简单的实现了一个 Appfile。在平台层,KubeVela 在 OAM 模型层实现中集成了CUELang 这种简洁强大的模板语言,从而为平台工程师基于 Kubernetes API 对象定义用户侧抽象(即:“最后一公里”抽象)提供了一个标准、通用的配置工具。更重要的是,平台工程师或者系统管理员,可以随时随地的每个能力对应的 CUE 模板进行修改,这些修改一旦提交到 Kubernetes,用户在 Appfile 里就可以立刻使用到新的抽象,不需要重新部署或者安装 KubeVela。

在具体实现层,KubeVela 是基于 OAM Kubernetes Runtime 构建的,同时采用 KEDA ,Flagger,Prometheus 等生态项目作为 Trait 的背后的依赖。当然,这些依赖只是 KubeVela 的选型,你可以随时为 KubeVela 定制和安装你喜欢的任何能力作为 Workload Type 或者 Trait。综合以上讲解,KubeVela 项目的整体架构由用户界面层,模型层,和能力管理层三部分组成,如下所示:

有了 KubeVela,平台工程师终于拥有了一个可以方便快捷地将任何一个 Kubernetes 社区能力封装抽象成一个面向用户的上层平台特性的强大工具。而作为这个平台的最终用户,应用开发者们只需要学习这些上层抽象,在一个配置文件中描述应用,就可以一键交付出去。

3. KubeVela VS 经典 PaaS#

很多人可能会问,KubeVela 跟经典 PaaS 的主要区别和联系是什么呢?

事实上,大多数经典 PaaS 都能提供完整的应用生命周期管理功能,同时也非常关注提供简单友好的用户体验,提升研发效能。在这些点上,KubeVela 跟经典 PaaS 的目标,是非常一致的。

但另一方面,经典 PaaS 往往是不可扩展的(比如 Rancher 的 Rio 项目),或者会引入属于自己的插件生态(哪怕这个 PaaS 是完全基于 Kubernetes 构建的),以此来确保平台本身的用户体验和能力的可控制性(比如 Cloud Foundry 或者 Heroku 的插件中心)。

相比之下,KubeVela 的设计是完全不同的。KubeVela 的目标,从一开始就是利用整个 Kubernetes 社区作为自己的“插件中心”,并且“故意”把它的每一个内置能力都设计成是独立的、可插拔的插件。这种高度可扩展的模型,背后其实有着精密的设计与实现。比如,KubeVela 如何确保某个完全独立的 Trait 一定能够绑定于某种 Workload Type?如何检查这些相互独立的 Trait 是否冲突?这些挑战,正是 Open Application Model(OAM)作为 KubeVela 模型层的起到的关键作用,一言以蔽之:OAM 是一个高度可扩展的应用定义与能力管理模型。

KubeVela 和 OAM 社区欢迎大家设计和制作任何 Workload Type 和 Trait 的定义文件。只要把它们存放在 GitHub 上,全世界任何一个 KubeVela 用户就都可以在自己的 Appfile 里使用你所设计的能力。具体的方式,请参考 $ vela cap (即:插件能力管理命令)的使用文档

了解更多#

KubeVela 项目是 OAM 社区的官方项目,旨在取代原先的 Rudr 项目。不过,与 Rudr 主要作为“参考实现”的定位不同,KubeVela 既是一个端到端、面向全量场景的 OAM Kubernetes 完整实现,同时也是阿里云 EDAS 服务和内部多个核心 PaaS/Serverless 生产系统底层的核心组件。 此外,KubeVela 中 Apppfile 的设计,也是 OAM 社区在 OAM 规范中即将引入的“面向用户侧对象”的核心部分。

如果你想要更好的了解 KubeVela 项目,欢迎前往其官方网站上学习具体的示例和手册。以下也是一些非常好的学习内容和方式:

  • 前往学习 KubeVela Quick Start(新手教程),一步步了解 KubeVela 的使用方法。
  • 前往 OAM 社区深入交流和反馈。中文:钉钉群 23310022,英文:GitterCNCF Slack
  • 尝试为 KubeVela 添加来自开源社区的插件能力。此外,如果你有任何关于扩展 KubeVela 的奇妙想法,比如,基于 KubeVela 开发一个自己的云原生数据库 PaaS 或者 AI PaaS,欢迎前往 OAM 社区通过 Issue 来进行讨论。
  • 为 KubeVela 贡献代码. KubeVela 项目是一个诞生自云原生社区的开源项目(感谢来自 8 家不同公司的初始贡献者,并特别鸣谢 KubeVela 网站的发起者 guoxudong)。

KubeVela 项目的维护者会在项目稳定后,即将整个项目所有权捐赠给中立开源基金会。