版本:v1.3

自定义组件入门

在阅读本部分之前,请确保你已经了解 KubeVela 中 组件定义(ComponentDefinition 的概念且学习掌握了 CUE 的基本知识

本节将以组件定义的例子展开说明,介绍如何使用 CUE 通过组件定义 ComponentDefinition 来自定义应用部署计划的组件。

我们可以通过 vela def init 来根据已有的 YAML 文件来生成一个 ComponentDefinition 模板。

YAML 文件:

  1. apiVersion: "apps/v1"
  2. kind: "Deployment"
  3. spec:
  4. selector:
  5. matchLabels:
  6. "app.oam.dev/component": "name"
  7. template:
  8. metadata:
  9. labels:
  10. "app.oam.dev/component": "name"
  11. spec:
  12. containers:
  13. - name: "name"
  14. image: "image"

根据以上的 YAML 来生成 ComponentDefinition

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

得到如下结果:

  1. $ cat stateless.cue
  2. stateless: {
  3. annotations: {}
  4. attributes: workload: definition: {
  5. apiVersion: "<change me> apps/v1"
  6. kind: "<change me> Deployment"
  7. }
  8. description: ""
  9. labels: {}
  10. type: "component"
  11. }
  12. template: {
  13. output: {
  14. spec: {
  15. selector: matchLabels: "app.oam.dev/component": "name"
  16. template: {
  17. metadata: labels: "app.oam.dev/component": "name"
  18. spec: containers: [{
  19. name: "name"
  20. image: "image"
  21. }]
  22. }
  23. }
  24. apiVersion: "apps/v1"
  25. kind: "Deployment"
  26. }
  27. outputs: {}
  28. parameters: {}
  29. }

在这个自动生成的模板中:

  • 需要 .spec.workload 来指示该组件的工作负载类型。
  • .spec.schematic.cue.template 是一个 CUE 模板:
    • output 字段定义了 CUE 要输出的抽象模板。
    • parameter 字段定义了模板参数,即在应用部署计划(Application)中公开的可配置属性(KubeVela 将基于 parameter 字段自动生成 Json schema)。

下面我们来给这个自动生成的自定义组件添加参数并进行赋值:

  1. stateless: {
  2. annotations: {}
  3. attributes: workload: definition: {
  4. apiVersion: "<change me> apps/v1"
  5. kind: "<change me> Deployment"
  6. }
  7. description: ""
  8. labels: {}
  9. type: "component"
  10. }
  11. template: {
  12. output: {
  13. spec: {
  14. selector: matchLabels: "app.oam.dev/component": parameter.name
  15. template: {
  16. metadata: labels: "app.oam.dev/component": parameter.name
  17. spec: containers: [{
  18. name: parameter.name
  19. image: parameter.image
  20. }]
  21. }
  22. }
  23. apiVersion: "apps/v1"
  24. kind: "Deployment"
  25. }
  26. outputs: {}
  27. parameters: {
  28. name: string
  29. image: string
  30. }
  31. }

修改后可以用 vela def vet 做一下格式检查和校验。

  1. $ vela def vet stateless.cue
  2. Validation succeed.

接着,让我们声明另一个名为 task 的组件。

  1. vela def init task -t component -o task.cue

得到如下结果:

  1. $ cat task.cue
  2. task: {
  3. annotations: {}
  4. attributes: workload: definition: {
  5. apiVersion: "<change me> apps/v1"
  6. kind: "<change me> Deployment"
  7. }
  8. description: ""
  9. labels: {}
  10. type: "component"
  11. }
  12. template: {
  13. output: {}
  14. parameter: {}
  15. }

修改该组件定义:

  1. task: {
  2. annotations: {}
  3. attributes: workload: definition: {
  4. apiVersion: "batch/v1"
  5. kind: "Job"
  6. }
  7. description: ""
  8. labels: {}
  9. type: "component"
  10. }
  11. template: {
  12. output: {
  13. apiVersion: "batch/v1"
  14. kind: "Job"
  15. spec: {
  16. parallelism: parameter.count
  17. completions: parameter.count
  18. template: spec: {
  19. restartPolicy: parameter.restart
  20. containers: [{
  21. image: parameter.image
  22. if parameter["cmd"] != _|_ {
  23. command: parameter.cmd
  24. }
  25. }]
  26. }
  27. }
  28. }
  29. parameter: {
  30. count: *1 | int
  31. image: string
  32. restart: *"Never" | string
  33. cmd?: [...string]
  34. }
  35. }

将以上两个组件定义部署到集群中:

  1. $ vela def apply stateless.cue
  2. ComponentDefinition stateless created in namespace vela-system.
  3. $ vela def apply task.cue
  4. ComponentDefinition task created in namespace vela-system.

这两个已经定义好的组件,最终会在应用部署计划中实例化,我们引用自定义的组件类型 stateless,命名为 hello。同样,我们也引用了自定义的第二个组件类型 task,并命令为 countdown

然后把它们编写到应用部署计划中,如下所示:

  1. apiVersion: core.oam.dev/v1alpha2
  2. kind: Application
  3. metadata:
  4. name: website
  5. spec:
  6. components:
  7. - name: hello
  8. type: stateless
  9. properties:
  10. image: oamdev/hello-world
  11. name: mysvc
  12. - name: countdown
  13. type: task
  14. properties:
  15. image: centos:7
  16. cmd:
  17. - "bin/bash"
  18. - "-c"
  19. - "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"

以上,我们就完成了一个自定义应用组件的应用交付全过程。值得注意的是,作为管理员的我们,可以通过 CUE 提供用户所需要的任何自定义组件类型,同时也为用户提供了模板参数 parameter 来灵活地指定对 Kubernetes 相关资源的要求。

Details

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: backend
  5. ... # 隐藏一些与本小节讲解无关的信息
  6. spec:
  7. template:
  8. spec:
  9. containers:
  10. - name: mysvc
  11. image: oamdev/hello-world
  12. metadata:
  13. labels:
  14. app.oam.dev/component: mysvc
  15. selector:
  16. matchLabels:
  17. app.oam.dev/component: mysvc
  18. ---
  19. apiVersion: batch/v1
  20. kind: Job
  21. metadata:
  22. name: countdown
  23. ... # 隐藏一些与本小节讲解无关的信息
  24. spec:
  25. parallelism: 1
  26. completions: 1
  27. template:
  28. metadata:
  29. name: countdown
  30. spec:
  31. containers:
  32. - name: countdown
  33. image: 'centos:7'
  34. command:
  35. - bin/bash
  36. - '-c'
  37. - for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done
  38. restartPolicy: Never

除了上面这个例子外,一个组件的定义通常也会由多个 Kubernetes API 资源组成。例如,一个由 DeploymentService 组成的 webserver 组件。CUE 同样能很好的满足这种自定义复合组件的需求。

我们会使用 output 这个字段来定义工作负载类型的模板,而其他剩下的资源模板,都在 outputs 这个字段里进行声明,格式如下:

  1. outputs: <unique-name>:
  2. <full template data>

回到 webserver 这个复合自定义组件上,它的 CUE 文件编写如下:

  1. webserver: {
  2. annotations: {}
  3. attributes: workload: definition: {
  4. apiVersion: "apps/v1"
  5. kind: "Deployment"
  6. }
  7. description: ""
  8. labels: {}
  9. type: "component"
  10. }
  11. template: {
  12. output: {
  13. apiVersion: "apps/v1"
  14. kind: "Deployment"
  15. spec: {
  16. selector: matchLabels: {
  17. "app.oam.dev/component": context.name
  18. }
  19. template: {
  20. metadata: labels: {
  21. "app.oam.dev/component": context.name
  22. }
  23. spec: {
  24. containers: [{
  25. name: context.name
  26. image: parameter.image
  27. if parameter["cmd"] != _|_ {
  28. command: parameter.cmd
  29. }
  30. if parameter["env"] != _|_ {
  31. env: parameter.env
  32. }
  33. if context["config"] != _|_ {
  34. env: context.config
  35. }
  36. ports: [{
  37. containerPort: parameter.port
  38. }]
  39. if parameter["cpu"] != _|_ {
  40. resources: {
  41. limits:
  42. cpu: parameter.cpu
  43. requests:
  44. cpu: parameter.cpu
  45. }
  46. }
  47. }]
  48. }
  49. }
  50. }
  51. }
  52. // an extra template
  53. outputs: service: {
  54. apiVersion: "v1"
  55. kind: "Service"
  56. spec: {
  57. selector: {
  58. "app.oam.dev/component": context.name
  59. }
  60. ports: [
  61. {
  62. port: parameter.port
  63. targetPort: parameter.port
  64. },
  65. ]
  66. }
  67. }
  68. parameter: {
  69. image: string
  70. cmd?: [...string]
  71. port: *80 | int
  72. env?: [...{
  73. name: string
  74. value?: string
  75. valueFrom?: {
  76. secretKeyRef: {
  77. name: string
  78. key: string
  79. }
  80. }
  81. }]
  82. cpu?: string
  83. }
  84. }

可以看到:

  1. 最核心的工作负载,我们按需要在 output 字段里,定义了一个要交付的 Deployment 类型的 Kubernetes 资源。
  2. Service 类型的资源,则放到 outputs 里定义。以此类推,如果你要复合第三个资源,只需要继续在后面以键值对的方式添加:
  1. outputs: service: {
  2. apiVersion: "v1"
  3. kind: "Service"
  4. spec: {
  5. ...
  6. outputs: third-resource: {
  7. apiVersion: "v1"
  8. kind: "Service"
  9. spec: {
  10. ...

在理解这些之后,将上面的组件定义对象保存到 CUE 文件中,并部署到你的 Kubernetes 集群。

  1. $ vela def apply webserver.cue
  2. ComponentDefinition webserver created in namespace vela-system.

然后,我们使用它们,来编写一个应用部署计划:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: Application
  3. metadata:
  4. name: webserver-demo
  5. namespace: default
  6. spec:
  7. components:
  8. - name: hello-world
  9. type: webserver
  10. properties:
  11. image: oamdev/hello-world
  12. port: 8000
  13. env:
  14. - name: "foo"
  15. value: "bar"
  16. cpu: "100m"

进行部署:

  1. $ vela up -f webserver.yaml

最后,它将在运行时集群生成相关 Kubernetes 资源如下:

  1. $ kubectl get deployment
  2. NAME READY UP-TO-DATE AVAILABLE AGE
  3. hello-world-v1 1/1 1 1 15s
  4. $ kubectl get svc
  5. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  6. hello-world-trait-7bdcff98f7 ClusterIP <your ip> <none> 8000/TCP 32s

KubeVela 让你可以在运行时,通过 context 关键字来引用一些信息。

最常用的就是应用部署计划的名称 context.appName 和组件的名称 context.name

  1. context: {
  2. appName: string
  3. name: string
  4. }

举例来说,假设你在实现一个组件定义,希望将容器的名称填充为组件的名称。那么这样做:

  1. parameter: {
  2. image: string
  3. }
  4. output: {
  5. ...
  6. spec: {
  7. containers: [{
  8. name: context.name
  9. image: parameter.image
  10. }]
  11. }
  12. ...
  13. }

注意,context 的信息会在资源部署到目标集群之前就自动注入了

Context 变量名说明
context.appRevision应用部署计划的版本
context.appRevisionNum应用部署计划的版本号(int 类型), 比如说如果 context.appRevisionapp-v1 的话,context.appRevisionNum 会是 1
context.appName应用部署计划的名称
context.name组件的名称
context.namespace应用部署计划的命名空间
context.output组件中渲染的工作负载 API 资源,这通常用在运维特征里
context.outputs.<resourceName>组件中渲染的运维特征 API 资源,这通常用在运维特征里

Last updated on 2022年11月1日 by Tianxin Dong