乐趣区

关于阿里巴巴:如何在-20-分钟内给你的-K8s-PaaS-上线一个新功能


作者 | 孙健波(天元)
起源 | 阿里巴巴云原生公众号

上个月,KubeVela 正式公布了,作为一款简略易用且高度可扩大的利用治理平台与外围引擎,能够说是宽广平台工程师用来构建本人的云原生 PaaS 的神兵利器。那么本文就以一个理论的例子,解说一下如何在 20 分钟内,为你基于 KubeVela 的 PaaS“上线“一个新能力。

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

KubeVela 扩大的根本构造

KubeVela 的根本架构如图所示:

简略来说,KubeVela 通过增加 Workload TypeTrait 来为用户扩大能力,平台的服务提供方通过 Definition 文件注册和扩大,向上通过 Appfile 透出扩大的性能。官网文档中也别离给出了根本的编写流程,其中 2 个是 Workload 的扩大例子,一个是 Trait 的扩大例子:

  • OpenFaaS 为例的 Workload Type 扩大
  • 云资源 RDS 为例的 Workload Type 扩大
  • KubeWatch 为例的 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 为例,它的对应关系如下:

这里补充一个小常识,为什么有了 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 的社区仓库中来。

如果你有任何疑难,欢送钉钉搜寻群号:23310022 退出交换群。

退出移动版