扫码阅读
手机扫码阅读

拓展KubeVela模块,看addon如何助力开放生态

249 2023-09-07

Cloud Native

ESG服务BU云原生交付中心、云基地

在云原生上的尝试、调研与分享

本期作者

肖晟鹏

专业踩坑的 bug工程师

在上期云原生专栏推文中 没有addon的生态,是个不够开放的生态 ,我们初步探索了 KubeVela 中的 addon,了解到 KubeVela 中为什么需要 addon,并在最后以一个简单的小例子说明了如何自定义 addon。

那么在本期推文中,我们将回应在上期推文文末留下的悬念,带你继续深入了解 addon 在 KubeVela 中的应用——如何用 addon 拓展 KubeVela 模块。


#1 什么是模块定义

为什么需要模块定义?

addon 部署应用的对象一般是一些只需要部署一次的应用,比如 KubeVela 的 UX,或者一些通用的中间件等。

但是在实际使用中,通常用户有着不一样的需求,比如:

  • 在部署服务 A 时,需要一个定制参数的中间件。

  • 在部署服务 B 时,需要不同的工作流节点。

在这种场景下,就可以通过 addon 带来模块定义,在部署应用的时候选择不同的组件类型,不同的工作流节点类型。

什么是模块定义?

KubeVela 模块定义,使用 CUE 交付完整的模块化功能,使得平台可以随着用户需求变化动态扩展功能,适应各类用户和场景,满足公司业务长期发展的迭代诉求。

以下面部署一个简单应用为例:

一个实例

apiVersion: core.oam.dev/v1beta1kind: Applicationmetadata: name: nginx-appspec: components: - name: nginx-server type: webservice properties: image: nginx:latest port: 80 traits: - type: ingress-1-20 properties: domain: dcone.digitalchina.com http: "/": 80

注意,这里使用了一个component(组件)的类型 webservice。

这就是组件定义(ComponentDefinition)。这种定义将任何类型的可部署制品封装成待交付的“组件”。

只要定义好之后,这种类型的组件就可以被用户在部署计划(Application)中引用、实例化并交付出去。

除此之外,还有下列定义可以在模块定义中查看相关信息:

  • 运维特征定义(TraitDefinition)

  • 应用策略定义(PolicyDefinition)

  • 工作流节点定义(WorkflowStepDefinition)

模块定义的多种引入方式

模块定义可以通过 cue 或者 yaml 配置文件引入,同样也可以使用 addon 的方式引入,此外将其做成一个addon 还便于更多的用户使用。

那么接下来我们就尝试使用addon

引入自己的模块定义

#2 addon拓展模块

模块定义的引入步骤

Step 1:创建addon目录结构

根据官方文档中 addon 的说明,创建 addon 目录结构。

目录结构

├── resources/

├── definitions/
│   ├── myredis.cue
├── schemas/
│   ├── component-uischema-myredis.yaml
├── README.md
├── metadata.yaml
└── template.yaml

Step 2:编写元数据文件和应用模板

metadata.yaml

name: myredisversion: 1.0.0description: Example adddon.icon: icon/niu.jpegurl: example.com tags: - only_example deployTo: runtimeCluster: false

template.yaml

apiVersion: core.oam.dev/v1beta1kind: Applicationmetadata: name: redis namespace: vela-systemspec:

这里我们并没有创建应用,所以不需要在组件资源 (resources) 目录中写配置文件,所以略过。

Step 3:编写模块定义文件

因为我们需要自定义我们自己的模块,所以需要在模块定义文件 (definitions) 目录下编写模块定义文件,这里我简单的用 cue 编写了一个 redis 的模块。

一个redis的模块定义文件

myredis.cueimport( "encoding/base64")"myredis": { annotations: {} attributes: workload: definition: { apiVersion: "apps/v1" kind: "Deployment" } description: "My Redis component." labels: {} type: "component"} template: { output: { apiVersion: "v1" kind: "ConfigMap" metadata: { name: "redis-config" labels: app: context.name } data: { "redis.conf": """ dir /data port 6379 bind 0.0.0.0 appendonly yes protected-mode no pidfile /data/redis-6379.pid requirepass """ + " "  + parameter.redisPassword } } outputs: { redis: { apiVersion: "apps/v1" kind: "Deployment" metadata: { name: "redis" labels: app: context.name } spec: { replicas: 1 selector: matchLabels: app: context.name template: { metadata: labels: app: context.name spec: { containers: [{ name: context.name command: ["sh", "-c", "redis-server /usr/local/etc/redis/redis.conf"] image: "redis:6.2.6" ports: [{ containerPort: 6379 }] volumeMounts: [{ name:      "data" mountPath: "/data" }, { name:      "config" mountPath: "/usr/local/etc/redis/redis.conf" subPath:   "redis.conf" }] }] volumes: [{ name: "data" hostPath: path: parameter.hostPath }, { name: "config" configMap: name: "redis-config" }] } } } } web: { apiVersion: "v1" kind:       "Service" metadata: { name: context.name labels: app: context.name } spec: { ports: [{ name: context.name port: 6379 targetPort: 6379 }] selector: app: context.name type: parameter.serviceType } } secret: { apiVersion: "v1" kind:       "Secret" metadata: { name: "myredis-secret" } type: "Opaque" data: { password: base64.Encode(null,parameter.redisPassword) ip: base64.Encode(null,context.name + "." + context.namespace + ".svc.cluster.local") port: base64.Encode(null,"6379") } } } parameter: { hostPath: *"/home/k8s/redis/data1" | string serviceType: *"ClusterIP" | "NodePort" redisPassword: *"123456" | string }}

说明:

  • 这里创建 secret 是为了部署 web 应用的时候,通过 kubevela 的 service-binding 将 redis 的 ip 和端口,通过环境变量的方式注入 web 容器

  • 因为 k8s 机制,secret 必须要 base64 编码,所以引入了 "encoding/base64" 包。

  • CUE 有很多 internal packages 可以被 KubeVela 使用,这样可以满足更多的开发需求。

看到这里就有读者一脑子问号了,这cue文件好麻烦啊,没有用过怎么办,有点学习成本啊。

什么是CUE

其实 cue 就是 Json 的超集,并且内置了 golang 的一些包。

具体的可以参考 cue,其最后也是会将 cue 文件转换为 yaml 文件执行,也就是说定义模块的时候,使用 yaml 也是可以的。

但是 yaml 文件不能做到参数的自定义化,比如定义一个变量,写一个判断语句等,而 cue 可以。特别是用户在使用一个模块的时候,会根据不同的需求去定义不同的配置,所以一般会使用较为灵活的 cue。

诶说了这么多 cue 的优点,但是看着这长长的文件还是有点头疼, 有没有一个快速生成模块定义模板的方法呢?

还真有!

模块定义“速成法”

考虑到维护人员不熟悉 cue 的情况下,KubeVela CLI 提供了 vela def init 这个命令,其可以将一个 yaml 文件转换成一个模块定义的 cue 文件。

一个实例

比如这里我有一个 yaml 文件,其内容就是一个 K8S 的manifest:

一个 K8S 的manifest文件

apiVersion: "apps/v1"kind: "Deployment"spec: selector: matchLabels: "app.oam.dev/component": "name" template: metadata: labels: "app.oam.dev/component": "name" spec: containers:  - name: "name" image: "image"

使用下方命令:

vela def init stateless -t component --template-yaml ./stateless.yaml -o stateless.cue

将其变成一个 ComponentDefinition 模板:

ComponentDefinition 模板

apiVersion: apps/v1kind: Deploymentmetadata: name: redis-deployment namespace: redis labels: app: redisspec: replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:6.2.6 command: - "sh" - "-c" - "redis-server /usr/local/etc/redis/redis.conf" ports: - containerPort: 6379 volumeMounts: - name: data mountPath: /data - name: config mountPath: /usr/local/etc/redis/redis.conf subPath: redis.conf volumes: - name: data hostPath: path: /home/k8s/redis/data - name: config configMap: name: redisconfig

这样就有一个模板,在此基础上进行修改。

修改完成后可以用 vela def vet 做一下格式检查和校验,这样一个模块定义的文件就完成了。

相关说明

  • 对schemas目录的说明

在 addon 目录结构中还有一个 模版参数展示增强文件 (schemas) 目录,其用于存放 X-Definitions 所对应的 UI-schema 文件,用于在 UX 中展示 X-Definitions 所需要填写参数时增强显示效果。

  • 对myredis.cue文件中相关参数显示的说明

在上面的 myredis.cue 文件中,有一些自定的变量参数parameter

hostPath serviceType redisPassword

这些是给用户在使用这个组件时传入的值。如果希望用户能够在 UX 中看到这些值,则需要配置对应的 yaml 文件。

只有正确配置组件中所有的 parameter 字段,才能够在 UX 中看到自定义的组件。

component-uischema-myredis.yaml

- jsonKey: hostPath lable: hostPath sort: 1- jsonKey: serviceType lable: Service Type sort: 2- jsonkey: redisPassword lable: Redis Password sort: 3

说明:

这个文件的文件名不重要,重要的是其内容中的 jsonKey 的值必须要与 cue 中 parameter 中定义的变量名一致。

这样就完成了一个简单的模块,

接下来我们就尝试将其安装在我们的KubeVela中

#3 启动Addon使用新的模块

一、启动Addon

在 CentOS7 上使用命令安装这个 addon:

# 将项目clone到本地$ cd /home$ git clone http://xxxxx # 进入代码根目录,使用CLI 启用插件$ cd /home/kubevela-addon$ vela addon enable .

使用 vela addon ls 查看插件:

使用 vela component 查看组件,可以看到我们自定义的组件:

二、部署应用、添加模块

Step1:相关文件准备

deploy.yaml

apiVersion: core.oam.dev/v1beta1kind: Applicationmetadata: name: redis-demo namespace: defaultspec: components: - name: redis type: myredis properties: serviceType: NodePort - name: web type: webservice properties: image: harbor.dev.wh.digitalchina.com/kubevela/kubevela-demo:latest port: 8080 traits: - type: service-binding properties: envMappings: REDIS_PASSWORD: secret: myredis-secret key: password REDIS_IP: secret: myredis-secret key: ip REDIS_PORT: secret: myredis-secret key: port - name: service type: k8s-objects properties: objects: - apiVersion: v1 kind: Service metadata: name: web labels: app: web spec: type: NodePort ports: - name: web port: 8080 targetPort: 8080 nodePort: 30080 selector: app.oam.dev/component: web

说明:

  • 这里的第一个 component 选择的 type 就是我们自定义的 myredis,并且设定 serviceType 值为 NodePort,这个值写在 myredis.cue 中,表示启动 redis 后,创建的 service 类型是 NodePort。

  • 这里的 webservice 应用使用的镜像,是一个标准的 SpringBoot 项目,内部引入了 Redisson 客户端访问 redis。

如下是 SpringBoot 配置文件:

SpringBoot配置文件

spring:... redis: redisson: config: | singleServerConfig: ... password: ${REDIS_PASSWORD} address: 'redis://${REDIS_IP}:${REDIS_PORT}' ...

我们使用环境变量的方式访问 reids。

Step2:应用部署

使用命令部署 application:

$ vela up -f deploy.yaml

使用 vela ls 查看部署的应用:

应用正常启动,下面使用 kubectl 工具查看 k8s 资源:

成功创建资源,和我们在 myredis.cue 中定义的资源一样。

最后我们访问项目,看看其能不能通过 service-binding 访问到 redis:

通过接口测试,项目正常访问 redis。

那么以上就是 如何用 addon

拓展 KubeVela 模块的故事了,

相信你对 addon 的应用又有了更加深入的了解~

原文链接: http://mp.weixin.qq.com/s?__biz=Mzg5MzUyOTgwMQ==&mid=2247502414&idx=1&sn=d7c8a31f927a44624d9a403b3092d6c1&chksm=c02ff1e8f75878feffd6906a6cb3f3227f5ae45a0c04a904d2ab4351417bcd52458f143d7095#rd