Version: v1.0

Patch Traits

Patch 是 trait 定义的一种非常常见的模式, 即应用操作员可以修改或者将路径属性设置为组件实例(通常是 workload )以启用某些操作功能例如 sidecar 或节点相似性规则(这应该在将资源应用于目标集群 之前 完成)。

当 component 定义由第三方 component 提供程序(例如,软件发行商)提供时,此模式非常有用,因此应用操作员无权更改其模板。

请注意,即使 patch trait 本身是由 CUE 定义的,它也可以修补任何 component,无论其基于什么原理(即 CUE,Helm 和任何其他受支持的方式)。

下面是 node-affinity trait 的例子:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "affinity specify node affinity and toleration"
  6. name: node-affinity
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {
  16. spec: template: spec: {
  17. if parameter.affinity != _|_ {
  18. affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{
  19. matchExpressions: [
  20. for k, v in parameter.affinity {
  21. key: k
  22. operator: "In"
  23. values: v
  24. },
  25. ]}]
  26. }
  27. if parameter.tolerations != _|_ {
  28. tolerations: [
  29. for k, v in parameter.tolerations {
  30. effect: "NoSchedule"
  31. key: k
  32. operator: "Equal"
  33. value: v
  34. }]
  35. }
  36. }
  37. }
  38. parameter: {
  39. affinity?: [string]: [...string]
  40. tolerations?: [string]: string
  41. }

上面的 patch trait 假定目标组件实例具有 spec.template.spec.affinity 字段。 因此,我们需要使用 applyToWorkloads 来强制执行该 trait,仅适用于具有此字段的那些 workload 类型。

另外一个重要的字段是 podDisruptive,此 patch trait 将修改到 Pod 模板字段,因此对该 trait 的任何字段进行更改都会导致 Pod 重新启动,我们应该增加 podDisruptive 并且设置它的值为 true 以此告诉用户应用此 trait 将导致 Pod 重新启动。

现在,用户可以声明他们想要将节点相似性规则添加到 component 实例,如下所示:

  1. apiVersion: core.oam.dev/v1alpha2
  2. kind: Application
  3. metadata:
  4. name: testapp
  5. spec:
  6. components:
  7. - name: express-server
  8. type: webservice
  9. properties:
  10. image: oamdev/testapp:v1
  11. traits:
  12. - type: "node-affinity"
  13. properties:
  14. affinity:
  15. server-owner: ["owner1","owner2"]
  16. resource-pool: ["pool1","pool2","pool3"]
  17. tolerations:
  18. resource-pool: "broken-pool1"
  19. server-owner: "old-owner"

已知局限性

默认情况下,KubeVela 中 patch trait 使用 CUE merge 操作。它具有以下已知约束

  • 无法处理冲突。
    • 例如,如果已将 component 实例的值设置为 replicas=5,则修改 replicas 字段的任何 patch trait 都将失败,也就是你不应在其 component 定义中公开 replicas 字段。
  • patch 中的数组列表将按照索引顺序合并。它无法处理数组列表成员的重复。但这可以通过下面的另一个功能解决。

策略 Patch

strategy patch 对修改数组列表很有用。

请注意,这不是标准的 CUE 功能,KubeVela 增强了 CUE 在这个场景的能力

使用 //+patchKey=<key_name> 注释,两个数组列表的合并逻辑将不遵循 CUE 行为。相反,它将列表视为对象并使用策略合并方法:

  • 如果找到重复的 key,则修改数据将与现有值合并;
  • 如果找不到重复项,则修改将追加到数组列表中。

策略 patch trait 的示例如下所示:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "add sidecar to the app"
  6. name: sidecar
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {
  16. // +patchKey=name
  17. spec: template: spec: containers: [parameter]
  18. }
  19. parameter: {
  20. name: string
  21. image: string
  22. command?: [...string]
  23. }

在上面的示例中,我们定义了 patchKeyname ,这是容器名称的参数 key 。 在这种情况下,如果 workload 中没有相同名称的容器,它将是一个 sidecar 容器,追加到 spec.template.spec.containers 数组列表中。 如果 workload 已经有一个具有与此 Sidecar trait 相同名称的容器,则将发生合并操作而不是追加操作(这将导致重复的容器)。

如果 patch and outputs 都存在于一个 trait 定义中,则将首先处理 patch 操作,然后呈现 outputs

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "expose the app"
  6. name: expose
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {spec: template: metadata: labels: app: context.name}
  16. outputs: service: {
  17. apiVersion: "v1"
  18. kind: "Service"
  19. metadata: name: context.name
  20. spec: {
  21. selector: app: context.name
  22. ports: [
  23. for k, v in parameter.http {
  24. port: v
  25. targetPort: v
  26. },
  27. ]
  28. }
  29. }
  30. parameter: {
  31. http: [string]: int
  32. }

因此,将 Service 附加到给定 component 实例的上述 trait 将首先为 workload 打上相应的标签,然后基于 outputs 中的模板呈现服 Service 资源。

Patch Trait 的更多使用案例

通常,patch trait 非常有用,可以将操作问题与 component 定义分开,下面有更多示例。

添加标签

例如,修改 component 实例通用标签(虚拟组)。

  1. apiVersion: core.oam.dev/v1alpha2
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "Add virtual group labels"
  6. name: virtualgroup
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {
  16. spec: template: {
  17. metadata: labels: {
  18. if parameter.scope == "namespace" {
  19. "app.namespace.virtual.group": parameter.group
  20. }
  21. if parameter.scope == "cluster" {
  22. "app.cluster.virtual.group": parameter.group
  23. }
  24. }
  25. }
  26. }
  27. parameter: {
  28. group: *"default" | string
  29. scope: *"namespace" | string
  30. }

然后可以像这样使用:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: Application
  3. spec:
  4. ...
  5. traits:
  6. - type: virtualgroup
  7. properties:
  8. group: "my-group1"
  9. scope: "cluster"

添加注释

与常见标签类似,你也可以使用注释来修补 component 实例。注释值应为 JSON 字符串。

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "Specify auto scale by annotation"
  6. name: kautoscale
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: false
  12. schematic:
  13. cue:
  14. template: |
  15. import "encoding/json"
  16. patch: {
  17. metadata: annotations: {
  18. "my.custom.autoscale.annotation": json.Marshal({
  19. "minReplicas": parameter.min
  20. "maxReplicas": parameter.max
  21. })
  22. }
  23. }
  24. parameter: {
  25. min: *1 | int
  26. max: *3 | int
  27. }

添加 Pod 环境变量

将系统环境注入 Pod 也是非常常见的例子。

这种情况取决于策略合并修改,因此不要忘记添加 +patchKey=name ,如下所示:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "add env into your pods"
  6. name: env
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {
  16. spec: template: spec: {
  17. // +patchKey=name
  18. containers: [{
  19. name: context.name
  20. // +patchKey=name
  21. env: [
  22. for k, v in parameter.env {
  23. name: k
  24. value: v
  25. },
  26. ]
  27. }]
  28. }
  29. }
  30. parameter: {
  31. env: [string]: string
  32. }

基于外部身份验证服务注入 ServiceAccount

在此示例中,从身份验证服务动态请求了服务帐户并将其修补到该服务中。

此示例将 UID 令牌放在 HTTP 头中,但如果愿意,也可以使用请求体。

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "dynamically specify service account"
  6. name: service-account
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. processing: {
  16. output: {
  17. credentials?: string
  18. }
  19. http: {
  20. method: *"GET" | string
  21. url: parameter.serviceURL
  22. request: {
  23. header: {
  24. "authorization.token": parameter.uidtoken
  25. }
  26. }
  27. }
  28. }
  29. patch: {
  30. spec: template: spec: serviceAccountName: processing.output.credentials
  31. }
  32. parameter: {
  33. uidtoken: string
  34. serviceURL: string
  35. }

processing.http 部分是高级功能,允许 trait 定义在渲染资源期间发送 HTTP 请求。有关更多详细信息,请参考特质定义中的执行HTTP请求 部分。

添加 InitContainer

InitContainer 对在 image 中预定义操作并在应用程序容器之前运行它很有用。

下面是一个例子:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. annotations:
  5. definition.oam.dev/description: "add an init container and use shared volume with pod"
  6. name: init-container
  7. spec:
  8. appliesToWorkloads:
  9. - webservice
  10. - worker
  11. podDisruptive: true
  12. schematic:
  13. cue:
  14. template: |
  15. patch: {
  16. spec: template: spec: {
  17. // +patchKey=name
  18. containers: [{
  19. name: context.name
  20. // +patchKey=name
  21. volumeMounts: [{
  22. name: parameter.mountName
  23. mountPath: parameter.appMountPath
  24. }]
  25. }]
  26. initContainers: [{
  27. name: parameter.name
  28. image: parameter.image
  29. if parameter.command != _|_ {
  30. command: parameter.command
  31. }
  32. // +patchKey=name
  33. volumeMounts: [{
  34. name: parameter.mountName
  35. mountPath: parameter.initMountPath
  36. }]
  37. }]
  38. // +patchKey=name
  39. volumes: [{
  40. name: parameter.mountName
  41. emptyDir: {}
  42. }]
  43. }
  44. }
  45. parameter: {
  46. name: string
  47. image: string
  48. command?: [...string]
  49. mountName: *"workdir" | string
  50. appMountPath: string
  51. initMountPath: string
  52. }

用法可以是:

  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. image: oamdev/testapp:v1
  11. traits:
  12. - type: "init-container"
  13. properties:
  14. name: "install-container"
  15. image: "busybox"
  16. command:
  17. - wget
  18. - "-O"
  19. - "/work-dir/index.html"
  20. - http://info.cern.ch
  21. mountName: "workdir"
  22. appMountPath: "/usr/share/nginx/html"
  23. initMountPath: "/work-dir"