配置模块补丁

在我们在进行定义的编写时,有时需要对其他的组件或者运维特征进行修改、打补丁。我们可以在自定义运维特征和自定义工作流步骤中执行补丁操作。

在默认情况下,KubeVela 会将需要打补丁的值通过 CUE 的 merge 来进行合并。但是目前 CUE 无法处理有冲突的字段名。

比如,在一个组件实例中已经设置 replicas=5,那一旦有运维特征实例,尝试给 replicas 字段的值打补丁就会失败。所以我们建议你提前规划好,不要在组件和运维特征之间使用重复的字段名。

但在一些情况下,我们确实需要处理覆盖已被赋值的字段。比如,在进行多环境资源的差异化配置时,我们希望不同环境中的环境变量是不同的:如默认的环境变量为 MODE=PROD,测试环境中需要修改为 MODE=DEV DEBUG=true

此时,我们可以部署如下的应用:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: Application
  3. metadata:
  4. name: deploy-with-override
  5. spec:
  6. components:
  7. - name: nginx-with-override
  8. type: webservice
  9. properties:
  10. image: nginx
  11. env:
  12. - name: MODE
  13. value: prod
  14. policies:
  15. - name: test
  16. type: topology
  17. properties:
  18. clusters: ["local"]
  19. namespace: test
  20. - name: prod
  21. type: topology
  22. properties:
  23. clusters: ["local"]
  24. namespace: prod
  25. - name: override-env
  26. type: override
  27. properties:
  28. components:
  29. - name: nginx-with-override
  30. traits:
  31. - type: env
  32. properties:
  33. env:
  34. MODE: test
  35. DEBUG: "true"
  36. workflow:
  37. steps:
  38. - type: deploy
  39. name: deploy-test
  40. properties:
  41. policies: ["test", "override-env"]
  42. - type: deploy
  43. name: deploy-prod
  44. properties:
  45. policies: ["prod"]

部署完应用后,可以看到,在 test 命名空间下,nginx 应用的环境变量如下:

  1. spec:
  2. containers:
  3. - env:
  4. - name: MODE
  5. value: test
  6. - name: DEBUG
  7. value: "true"

而在 prod 命名空间下,nginx 应用的环境变量如下:

  1. spec:
  2. containers:
  3. - env:
  4. - name: MODE
  5. value: prod

deploy-test 会将 nginx 部署到 test namespace 下,同时,在 env 这个运维特征中,通过使用补丁策略,支持了覆盖相同变量的能力,从而为这个测试环境下的 nginx 加上 MODE=test DEBUG=true 的环境变量。而 prod namespace 下的 nginx 将保留原本的 MODE=prod 环境变量。

KubeVela 提供了一系列补丁策略来帮助你完成这类需求。在编写补丁型运维特征和工作流时,如果你发现值冲突的问题,可以结合使用这些补丁策略。值得注意的是,补丁策略并不是 CUE 官方提供的功能, 而是 KubeVela 扩展开发而来。

关于所有补丁策略的使用方法,请参考 补丁策略

在自定义运维特征中,使用补丁型特征是一种比较常用的形式。

它让我们可以修改、补丁某些属性给组件对象(工作负载或者其他运维特征)来完成特定操作,比如更新 sidecar 和节点亲和性(node affinity)的规则(并且,这个操作一定是在资源往集群部署前就已经生效)。

当我们的组件是从第三方提供并自定义而来的时候,由于它们的模版往往是固定不可变的,所以能使用补丁型特征就显得尤为有用了。

下面,我们通过一个节点亲和性(node affinity)的例子,讲解如何在运维特征中为组件打补丁:

  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. - deployments.apps
  10. podDisruptive: true
  11. schematic:
  12. cue:
  13. template: |
  14. patch: {
  15. spec: template: spec: {
  16. if parameter.affinity != _|_ {
  17. // +patchStrategy=retainKeys
  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. // +patchStrategy=retainKeys
  29. tolerations: [
  30. for k, v in parameter.tolerations {
  31. effect: "NoSchedule"
  32. key: k
  33. operator: "Equal"
  34. value: v
  35. }]
  36. }
  37. }
  38. }
  39. parameter: {
  40. affinity?: [string]: [...string]
  41. tolerations?: [string]: string
  42. }

具体来说,我们上面的这个补丁型特征,假定了使用它的组件对象将会使用 spec.template.spec.affinity 这个字段。因此,我们需要用 appliesToWorkloads 来指明,让当前运维特征被应用到拥有这个字段的对应工作负载实例上。同时,在指定了 // +patchStrategy=retainKeys 补丁策略的情况下,如果遇到值冲突,则会使用新的值去覆盖。

另一个重要的字段是 podDisruptive,这个补丁型特征将修改 Pod 模板字段,因此对该运维特征的任何字段进行更改,都会导致 Pod 重启。我们应该增加 podDisruptive 并且设置它的值为 true,以此告诉用户这个运维特征生效后将导致 Pod 重新启动。

现在用户只需要,声明他们希望增加一个节点亲和性的规则到组件实例当中:

  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: "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 1.4 版本之后生效。

你还可以通过在 Definition 中使用 patchOutputs,来为其他运维特征打补丁。如:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. name: patch-annotation
  5. spec:
  6. schematic:
  7. cue:
  8. template: |
  9. patchOutputs: {
  10. ingress: {
  11. metadata: annotations: {
  12. "kubernetes.io/ingress.class": "istio"
  13. }
  14. }
  15. }

上面的这个补丁型特征,假定了它绑定的组件还有别的运维特征,并且别的运维特征拥有 ingress 资源。该补丁型特征则会为这个 ingress 资源打上一个 istio 的 annotation。

我们可以部署如下应用来查看:

  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: "gateway"
  13. properties:
  14. domain: testsvc.example.com
  15. http:
  16. "/": 8000
  17. - type: "patch-annotation"
  18. properties:
  19. name: "patch-annotation-trait"

应用成功运行后,ingress 资源如下:

  1. apiVersion: networking.k8s.io/v1beta1
  2. kind: Ingress
  3. metadata:
  4. annotations:
  5. kubernetes.io/ingress.class: istio
  6. name: ingress
  7. spec:
  8. rules:
  9. spec:
  10. rules:
  11. - host: testsvc.example.com
  12. http:
  13. paths:
  14. - backend:
  15. service:
  16. name: express-server
  17. port:
  18. number: 8000
  19. path: /
  20. pathType: ImplementationSpecific

注意:为了能够 Patch 其他 trait 生成的资源,你要把这类 patchOutputs 的 trait 放到其他 trait 后面。

你甚至可以写一个 for 循环来 patch 所有的资源,例子如下:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. name: patch-for-argo
  5. spec:
  6. schematic:
  7. cue:
  8. template: |
  9. patch: {
  10. metadata: annotations: {
  11. "argocd.argoproj.io/compare-options": "IgnoreExtraneous"
  12. "argocd.argoproj.io/sync-options": "Prune=false"
  13. }
  14. }
  15. patchOutputs: {
  16. for k, v in context.outputs {
  17. "\(k)": {
  18. metadata: annotations: {
  19. "argocd.argoproj.io/compare-options": "IgnoreExtraneous"
  20. "argocd.argoproj.io/sync-options": "Prune=false"
  21. }
  22. }
  23. }
  24. }

这个例子对应了一个真实的场景.

当你在自定义工作流中使用 op.#ApplyComponent 时,你可以在其中的 patch 字段中为组件或者运维特征打补丁。

比如,在使用 Istio 进行渐进式的发布时,你可以通过在 op.#ApplyComponentpatch: workload 中,为其中的组件打上本次发布的 annotation;在 patch: traits: <trait-name> 中,变更本次渐进式发布的流量和路径。

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: WorkflowStepDefinition
  3. metadata:
  4. name: canary-rollout
  5. namespace: vela-system
  6. spec:
  7. schematic:
  8. cue:
  9. template: |-
  10. import ("vela/op")
  11. parameter: {
  12. batchPartition: int
  13. traffic: weightedTargets: [...{
  14. revision: string
  15. weight: int
  16. }]
  17. }
  18. comps__: op.#Load
  19. compNames__: [ for name, c in comps__.value {name}]
  20. comp__: compNames__[0]
  21. apply: op.#ApplyComponent & {
  22. value: comps__.value[comp__]
  23. patch: {
  24. workload: {
  25. // +patchStrategy=retainKeys
  26. metadata: metadata: annotations: {
  27. "rollout": context.name
  28. }
  29. }
  30. traits: "rollout": {
  31. spec: rolloutPlan: batchPartition: parameter.batchPartition
  32. }
  33. traits: "virtualService": {
  34. spec:
  35. // +patchStrategy=retainKeys
  36. http: [
  37. {
  38. route: [
  39. for i, t in parameter.traffic.weightedTargets {
  40. destination: {
  41. host: comp__
  42. subset: t.revision
  43. }
  44. weight: t.weight
  45. }]
  46. },
  47. ]
  48. }
  49. traits: "destinationRule": {
  50. // +patchStrategy=retainKeys
  51. spec: {
  52. host: comp__
  53. subsets: [
  54. for i, t in parameter.traffic.weightedTargets {
  55. name: t.revision
  56. labels: {"app.oam.dev/revision": t.revision}
  57. },
  58. ]}
  59. }
  60. }
  61. }
  62. applyRemaining: op.#ApplyRemaining & {
  63. exceptions: [comp__]
  64. }

部署完如上定义后,你可以声明如下的工作流:

  1. ...
  2. workflow:
  3. steps:
  4. - name: rollout-1st-batch
  5. type: canary-rollout
  6. properties:
  7. batchPartition: 0
  8. traffic:
  9. weightedTargets:
  10. - revision: reviews-v1
  11. weight: 90
  12. - revision: reviews-v2
  13. weight: 10
  14. - name: manual-approval
  15. type: suspend
  16. - name: rollout-rest
  17. type: canary-rollout
  18. properties:
  19. batchPartition: 1
  20. traffic:
  21. weightedTargets:
  22. - revision: reviews-v2
  23. weight: 100
  24. ...

在第一步和第三步中,我们分别在 traffic 中声明了不同的版本和流量,在 canary-rollout 这个步骤定义中,我们会将用户声明的版本和流量通过补丁的方式覆盖到原本的路由规则上,从而实现在工作流中的渐进式发布。

关于更多使用 KubeVela 完成 Istio 渐进式发布的细节和效果,请参考 基于 Istio 的渐进式发布

Last updated on 2023年8月4日 by Daniel Higuero