自定义组件

    提示

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

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

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

    YAML 文件:

    根据以上的 YAML 来生成 ComponentDefinition

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

    得到如下结果:

    stateless.cue

    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": "name"
    15. template: {
    16. metadata: labels: "app.oam.dev/component": "name"
    17. spec: containers: [{
    18. name: "name"
    19. image: "image"
    20. }]
    21. }
    22. }
    23. apiVersion: "apps/v1"
    24. kind: "Deployment"
    25. }
    26. outputs: {}
    27. parameter: {}
    28. }

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

    • The stateless is the name of component definition, it can be defined by yourself when initialize the component.
    • stateless.attributes.workload indicates the workload type of this component, it can help integrate with traits that apply to this kind of workload.
    • template is a CUE template, specifically:
      • The output and outputs fields define the resources that the component will be composed.
      • The parameter field defines the parameters of the component, i.e. the configurable properties exposed in the Application (and schema will be automatically generated based on them for end users to learn this component).

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

    1. stateless: {
    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. 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. parameter: {
    28. name: string
    29. image: string
    30. }
    31. }

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

    1. vela def vet stateless.cue

    期望输出

    1. Validation succeed.

    Apply above ComponentDefinition to your Kubernetes cluster to make it work:

    1. vela def apply stateless.cue

    expected output

    1. ComponentDefinition stateless created in namespace vela-system.

    Then the end user can check the schema and use it in an application now:

    1. vela show stateless

    expected output

    1. # Specification
    2. +-------+-------------+--------+----------+---------+
    3. | NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
    4. +-------+-------------+--------+----------+---------+
    5. | name | | string | true | |
    6. | image | | string | true | |
    7. +-------+-------------+--------+----------+---------+

    接着,让我们声明另一个名为 task 的组件,其原理类似。

    点击查看声明 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. $ vela def apply task.cue
    2. ComponentDefinition task created in namespace vela-system.

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

    1. apiVersion: core.oam.dev/v1beta1
    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 相关资源的要求。

    了解背后的 Kubernetes 最终资源信息

    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. name: countdown
    22. ... # 隐藏一些与本小节讲解无关的信息
    23. spec:
    24. parallelism: 1
    25. template:
    26. metadata:
    27. name: countdown
    28. spec:
    29. containers:
    30. - name: countdown
    31. image: 'centos:7'
    32. command:
    33. - bin/bash
    34. - '-c'
    35. - for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done
    36. restartPolicy: Never

    You can also use to show what the yaml results will be rendered for debugging.

    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. }

    自定义组件 - 图2提示

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

    在本文的最后列出了完整的 context 变量列表。

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

    提示

    Compare to using Helm, this approach gives your more flexibility as you can control the abstraction any time and integrate with traits, workflows in KubeVela better.

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

    1. output: {
    2. <template of main workload structural data>
    3. }
    4. outputs: {
    5. <unique-name>: {
    6. <template of auxiliary resource structural data>
    7. }
    8. }

    自定义组件 - 图4备注

    The reason for this requirement is KubeVela needs to know it is currently rendering a workload so it could do some “magic” by traits such like patching annotations/labels or other data during it.

    回到 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. ...
    5. outputs: third-resource: {
    6. apiVersion: "v1"
    7. kind: "Service"
    8. spec: {
    9. ...

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

    1. vela def apply webserver.cue

    期望输出

    1. ComponentDefinition webserver created in namespace vela-system.

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

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

    进行部署:

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

    1. vela status webserver-demo --tree --detail
    1. CLUSTER NAMESPACE RESOURCE STATUS APPLY_TIME DETAIL
    2. local ─── default ─┬─ Service/hello-webserver-auxiliaryworkload-685d98b6d9 updated 2022-10-15 21:58:35 Type: ClusterIP
    3. Cluster-IP: 10.43.255.55
    4. External-IP: <none>
    5. Port(s): 8000/TCP
    6. Age: 66s
    7. └─ Deployment/hello-webserver updated 2022-10-15 21:58:35 Ready: 1/1 Up-to-date: 1
    8. Available: 1 Age: 66s

    你可以通过自定义健康检查和状态信息,将自定义组件的真实状态反馈给最终用户。

    定义健康检查的字段为 <component-type-name>.attributes.status.healthPolicy.

    如果没有定义,它的值默认是 true,意味着在部署完对象后就将对象的状态设置为健康。为了让组件的状态及时、准确,通常你需要为组件定义监控状态,这个过程可以通过一个 CUE 表达式完成。

    在 CUE 里的关键词是 isHealth,CUE 表达式结果必须是 bool 类型。 KubeVela 运行时会一直检查 CUE 表达式,直至其状态显示为健康。每次控制器都会获取所有的 Kubernetes 资源,并将他们填充到 context 字段中。

    所以 context 字段会包含如下信息:

    1. context:{
    2. name: <component name>
    3. appName: <app name>
    4. output: <Kubernetes workload resource>
    5. outputs: {
    6. <resource1>: <Kubernetes trait resource1>
    7. <resource2>: <Kubernetes trait resource2>
    8. }
    9. }

    我们看看健康检查的例子:

    1. webserver: {
    2. type: "component"
    3. ...
    4. attributes: {
    5. status: {
    6. healthPolicy: #"""
    7. isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)
    8. """#
    9. }
    10. }
    11. }

    你也可以在健康检查中使用 parameter 中定义的参数,类似如下:

    1. webserver: {
    2. type: "component"
    3. ...
    4. attributes: {
    5. status: {
    6. healthPolicy: #"""
    7. isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == parameter.replicas)
    8. """#
    9. }
    10. }
    11. template: {
    12. parameter: {
    13. replicas: int
    14. }
    15. ...
    16. }

    健康检查的结果会输出到 Application 对象的 .status.services 字段中。

    1. apiVersion: core.oam.dev/v1beta1
    2. kind: Application
    3. status:
    4. ...
    5. services:
    6. - healthy: true
    7. name: myweb
    8. ...
    9. status: running

    自定义状态的字段未 <component-type-name>.attributes.status.customStatus, 自定义状态和健康检查的原理一致。

    在 CUE 中的关键词是 message。同时,CUE 表达式的结果必须是 string 类型。

    Application 对象的 CRD 控制器都会检查 CUE 表达式,直至显示健康通过。

    The example of custom status likes below:

    1. webserver: {
    2. type: "component"
    3. ...
    4. attributes: {
    5. status: {
    6. customStatus: #"""
    7. ready: {
    8. readyReplicas: *0 | int
    9. } & {
    10. if context.output.status.readyReplicas != _|_ {
    11. readyReplicas: context.output.status.readyReplicas
    12. }
    13. }
    14. message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
    15. """#
    16. }
    17. }
    18. }

    The message will be recorded into the corresponding component in .status.services of Application resource like below.

    1. apiVersion: core.oam.dev/v1beta1
    2. kind: Application
    3. status:
    4. ...
    5. services:
    6. - healthy: false
    7. message: Ready:1/1
    8. name: express-server
    Context VariableDescriptionType
    context.clusterVersion.majorThe major version of the runtime Kubernetes cluster.string
    context.clusterVersion.gitVersionThe gitVersion of the runtime Kubernetes cluster.string
    context.clusterVersion.platformThe platform information of the runtime Kubernetes cluster.string
    context.clusterVersion.minorThe minor version of the runtime Kubernetes cluster.int

    The cluster version context info can be used for graceful upgrade of definition. For example, you can define different API according to the cluster version.

    1. outputs: ingress: {
    2. if context.clusterVersion.minor < 19 {
    3. apiVersion: "networking.k8s.io/v1beta1"
    4. }
    5. if context.clusterVersion.minor >= 19 {
    6. apiVersion: "networking.k8s.io/v1"
    7. }
    8. kind: "Ingress"
    9. }

    Or use string contain pattern for this usage:

    1. import "strings"
    2. if strings.Contains(context.clusterVersion.gitVersion, "k3s") {
    3. provider: "k3s"
    4. }
    5. if strings.Contains(context.clusterVersion.gitVersion, "aliyun") {
    6. provider: "aliyun"
    7. }

    KubeVela is fully programmable via CUE, while it leverage Kubernetes as control plane and align with the API in yaml. As a result, the CUE definition will be converted as Kubernetes API when applied into cluster.

    The component definition will be in the following API format:

    1. apiVersion: core.oam.dev/v1beta1
    2. kind: ComponentDefinition
    3. metadata:
    4. name: <ComponentDefinition name>
    5. annotations:
    6. definition.oam.dev/description: <Function description>
    7. spec:
    8. workload: # Workload Capability Indicator
    9. definition:
    10. apiVersion: <Kubernetes Workload resource group>
    11. kind: <Kubernetes Workload types>
    12. schematic: # Component description
    13. cue: # Details of components defined by CUE language

    You can check the detail of this format .

    You can check the following resources for more examples: