自定义运维特征

本节介绍如何自定义运维特征,为用户的组件增添任何需要的运维特征能力。开始这一部分之前,请确保你已经对核心概念 以及 如何管理模块定义有了基本的了解。

通过 Trait 生成资源对象

通过 Trait 生成资源的用法和 Component 基本类似,这种场景通常用于生成运维的对象,比如用于服务访问的 Ingress、Service,或者用于扩缩容的 HPA 等对象。

同样的,我们使用 vela def init命令来生成一个框架:

  1. vela def init my-route -t trait --desc "My ingress route trait." > myroute.cue

注意: 在 vela CLI(<=1.4.2)的版本中有一个已知问题,vela def init 命令会生成一个错误的 definitionRef: "" 字段,这一行需要删除。

期望生成的内容如下:

  1. $ cat myroute.cue
  2. "my-route": {
  3. annotations: {}
  4. attributes: {
  5. appliesToWorkloads: []
  6. conflictsWith: []
  7. podDisruptive: false
  8. workloadRefPath: ""
  9. }
  10. description: "My ingress route trait."
  11. labels: {}
  12. type: "trait"
  13. }
  14. template: {
  15. patch: {}
  16. parameter: {}
  17. }

与组件定义有所不同,在用法上,你需要把所有的运维特征定义在 outputs 里(注意,不是 output),格式如下:

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

我们下面使用一个 ingressService 组成一个称为 my-route 的运维特征作为示例讲解:

  1. "my-route": {
  2. annotations: {}
  3. attributes: {
  4. appliesToWorkloads: []
  5. conflictsWith: []
  6. podDisruptive: false
  7. workloadRefPath: ""
  8. }
  9. description: "My ingress route trait."
  10. labels: {}
  11. type: "trait"
  12. }
  13. template: {
  14. parameter: {
  15. domain: string
  16. http: [string]: int
  17. }
  18. // 我们可以在一个运维特征 CUE 模版定义多个 outputs
  19. outputs: service: {
  20. apiVersion: "v1"
  21. kind: "Service"
  22. spec: {
  23. selector:
  24. app: context.name
  25. ports: [
  26. for k, v in parameter.http {
  27. port: v
  28. targetPort: v
  29. },
  30. ]
  31. }
  32. }
  33. outputs: ingress: {
  34. apiVersion: "networking.k8s.io/v1beta1"
  35. kind: "Ingress"
  36. metadata:
  37. name: context.name
  38. spec: {
  39. rules: [{
  40. host: parameter.domain
  41. http: {
  42. paths: [
  43. for k, v in parameter.http {
  44. path: k
  45. backend: {
  46. serviceName: context.name
  47. servicePort: v
  48. }
  49. },
  50. ]
  51. }
  52. }]
  53. }
  54. }
  55. }

将这个运维特征通过如下命令部署到控制平面上:

  1. vela def apply myroute.cue

然后最终用户就立即可以发现并使用这个运维特征了,这个运维特征没有限制,可以作用于任意 Application

我们通过如下的 vela up 命令将它启动起来:

  1. cat <<EOF | vela up -f -
  2. apiVersion: core.oam.dev/v1beta1
  3. kind: Application
  4. metadata:
  5. name: testapp
  6. spec:
  7. components:
  8. - name: express-server
  9. type: webservice
  10. properties:
  11. cmd:
  12. - node
  13. - server.js
  14. image: oamdev/testapp:v1
  15. port: 8080
  16. traits:
  17. - type: my-route
  18. properties:
  19. domain: test.my.domain
  20. http:
  21. "/api": 8080
  22. EOF

然后 KubeVela 在服务端就会将其生成 Kubernetes 资源,通过 CUE 我们可以完成很多复杂的玩法。

一次渲染多个资源

你可以在 outputs 里定义 For 循环。

注意在 For 循环里的 parameter 字段必须是 map 类型。

看看如下这个例子,在一个 TraitDefinition 对象里渲染多个 Service

  1. "expose": {
  2. type: "trait"
  3. }
  4. template: {
  5. parameter: {
  6. http: [string]: int
  7. }
  8. outputs: {
  9. for k, v in parameter.http {
  10. "\(k)": {
  11. apiVersion: "v1"
  12. kind: "Service"
  13. spec: {
  14. selector:
  15. app: context.name
  16. ports: [{
  17. port: v
  18. targetPort: v
  19. }]
  20. }
  21. }
  22. }
  23. }
  24. }

这个运维特征可以这样使用:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: Application
  3. metadata:
  4. name: testapp
  5. spec:
  6. components:
  7. - name: express-server
  8. type: webservice
  9. properties:
  10. ...
  11. traits:
  12. - type: expose
  13. properties:
  14. http:
  15. myservice1: 8080
  16. myservice2: 8081

自定义运维特征里执行 HTTP Request

TraitDefinition 对象可以发送 HTTP 请求并获取应答,让你可以通过关键字 processing 来渲染资源。

你可以在 processing.http 里定义 HTTP 请求的 method, url, body, headertrailer,然后返回的数据将被存储在 processing.output 中。

请确保目标 HTTP 服务器返回的数据是 JSON 格式

接着,你就可以通过 patch 或者 output/outputs 里的 processing.output 来引用返回数据了。

下面是一个示例:

  1. "auth-service": {
  2. type: "trait"
  3. }
  4. template: {
  5. parameter: {
  6. serviceURL: string
  7. }
  8. processing: {
  9. output: {
  10. token?: string
  11. }
  12. // The target server will return a JSON data with `token` as key.
  13. http: {
  14. method: *"GET" | string
  15. url: parameter.serviceURL
  16. request: {
  17. body?: bytes
  18. header: {}
  19. trailer: {}
  20. }
  21. }
  22. }
  23. patch: {
  24. data: token: processing.output.token
  25. }
  26. }

在上面这个例子中,TraitDefinition 对象发送请求来获取 token 的数据,然后将这些数据补丁给组件实例。

数据传递

TraitDefinition 对象可以读取特定 ComponentDefinition 对象生成的 API 资源(渲染自 outputoutputs)。

KubeVela 保证了 ComponentDefinition 一定会在 TraitDefinition 之前渲染

具体来说,context.output 字段包含了所有渲染后的工作负载 API 资源,然后 context.outputs.<xx> 则包含渲染后的其它类型 API 资源。

下面是一个数据传递的例子:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: ComponentDefinition
  3. metadata:
  4. name: worker
  5. spec:
  6. workload:
  7. definition:
  8. apiVersion: apps/v1
  9. kind: Deployment
  10. schematic:
  11. cue:
  12. template: |
  13. output: {
  14. apiVersion: "apps/v1"
  15. kind: "Deployment"
  16. spec: {
  17. selector: matchLabels: {
  18. "app.oam.dev/component": context.name
  19. }
  20. template: {
  21. metadata: labels: {
  22. "app.oam.dev/component": context.name
  23. }
  24. spec: {
  25. containers: [{
  26. name: context.name
  27. image: parameter.image
  28. ports: [{containerPort: parameter.port}]
  29. envFrom: [{
  30. configMapRef: name: context.name + "game-config"
  31. }]
  32. if parameter["cmd"] != _|_ {
  33. command: parameter.cmd
  34. }
  35. }]
  36. }
  37. }
  38. }
  39. }
  40. outputs: gameconfig: {
  41. apiVersion: "v1"
  42. kind: "ConfigMap"
  43. metadata: {
  44. name: context.name + "game-config"
  45. }
  46. data: {
  47. enemies: parameter.enemies
  48. lives: parameter.lives
  49. }
  50. }
  51. parameter: {
  52. // +usage=Which image would you like to use for your service
  53. // +short=i
  54. image: string
  55. // +usage=Commands to run in the container
  56. cmd?: [...string]
  57. lives: string
  58. enemies: string
  59. port: int
  60. }
  61. ---
  62. apiVersion: core.oam.dev/v1beta1
  63. kind: TraitDefinition
  64. metadata:
  65. name: ingress
  66. spec:
  67. schematic:
  68. cue:
  69. template: |
  70. parameter: {
  71. domain: string
  72. path: string
  73. exposePort: int
  74. }
  75. // trait template can have multiple outputs in one trait
  76. outputs: service: {
  77. apiVersion: "v1"
  78. kind: "Service"
  79. spec: {
  80. selector:
  81. app: context.name
  82. ports: [{
  83. port: parameter.exposePort
  84. targetPort: context.output.spec.template.spec.containers[0].ports[0].containerPort
  85. }]
  86. }
  87. }
  88. outputs: ingress: {
  89. apiVersion: "networking.k8s.io/v1beta1"
  90. kind: "Ingress"
  91. metadata:
  92. name: context.name
  93. labels: config: context.outputs.gameconfig.data.enemies
  94. spec: {
  95. rules: [{
  96. host: parameter.domain
  97. http: {
  98. paths: [{
  99. path: parameter.path
  100. backend: {
  101. serviceName: context.name
  102. servicePort: parameter.exposePort
  103. }
  104. }]
  105. }
  106. }]
  107. }
  108. }

在渲染 worker ComponentDefinition 时,具体发生了:

  1. 渲染的 Deployment 资源放在 context.output 中。
  2. 其它类型资源则放进 context.outputs.<xx> 中,同时 <xx> 是在特指 template.outputs 的唯一名字

因而,TraitDefinition 对象可以从 context 里读取渲染后的 API 资源(比如 context.outputs.gameconfig.data.enemies 这个字段)。

使用 Patch Trait 对参数增补或覆盖

除了利用 Trait 生成资源以外,一个更高级的用法是对组件生成的参数做增补或修改。

什么场景下会使用这种功能?

  1. 组件由其他人定义,运维人员对参数做修改。
  2. 组件由第三方组织定义,我们不拥有修改能力(不维护),只在部署时使用。

针对上述场景,KubeVela 通过 patch 功能来支撑,因为 Patch 的能力针对 Trait 和 Workflow 均适用,我们通过这篇 Patch 文档统一介绍。

下一步