From 87fca7b22a5ee70b7f616589f5f7944dcb8b80bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Mon, 8 Feb 2021 16:47:45 +0800 Subject: [PATCH] add trait with CUE --- .../defwithtemplate/manualscale.yaml | 2 +- docs/en/_sidebar.md | 1 + .../advanced-cue.md => cue/trait.md} | 220 +++++++----------- docs/en/cue/workload-type.md | 2 + docs/examples/registry/autoscale.yaml | 2 +- docs/examples/registry/metrics.yaml | 2 +- docs/examples/registry/patch-scaler.yaml | 18 ++ docs/examples/registry/rollout.yaml | 2 +- docs/examples/registry/route.yaml | 2 +- hack/vela-templates/cue/manualscale.cue | 2 +- 10 files changed, 109 insertions(+), 144 deletions(-) rename docs/en/{platform-engineers/advanced-cue.md => cue/trait.md} (80%) create mode 100644 docs/examples/registry/patch-scaler.yaml diff --git a/charts/vela-core/templates/defwithtemplate/manualscale.yaml b/charts/vela-core/templates/defwithtemplate/manualscale.yaml index 3fe214c37..03d4b5b74 100644 --- a/charts/vela-core/templates/defwithtemplate/manualscale.yaml +++ b/charts/vela-core/templates/defwithtemplate/manualscale.yaml @@ -13,7 +13,7 @@ spec: name: manualscalertraits.core.oam.dev workloadRefPath: spec.workloadRef template: | - output: { + outputs: scaler: { apiVersion: "core.oam.dev/v1alpha2" kind: "ManualScalerTrait" spec: { diff --git a/docs/en/_sidebar.md b/docs/en/_sidebar.md index 623bbf102..e01c3fcd6 100644 --- a/docs/en/_sidebar.md +++ b/docs/en/_sidebar.md @@ -10,6 +10,7 @@ - CUE - [CUE Basic](/en/cue/basic.md) - [Workload Type](/en/cue/workload-type.md) + - [Trait](/en/cue/trait.md) - Register Capability Modules - [Workload Type](/en/platform-engineers/workload-type.md) - [Trait](/en/platform-engineers/trait.md) diff --git a/docs/en/platform-engineers/advanced-cue.md b/docs/en/cue/trait.md similarity index 80% rename from docs/en/platform-engineers/advanced-cue.md rename to docs/en/cue/trait.md index 61c56713c..861be4687 100644 --- a/docs/en/platform-engineers/advanced-cue.md +++ b/docs/en/cue/trait.md @@ -1,17 +1,17 @@ # Using CUE to Extend Trait in advanced way -> WARNINIG: you are now reading a platform builder/administrator oriented documentation. +In this section we will introduce how to define a Trait with CUE template. -In the following tutorial, you will learn how to add a trait in a more advanced way without writing any CRD controller. -In general, the advanced way can help you build abstraction by composition or decomposition. - -## Trait generate multiple resources - -With the help of CUE template, we can combine multiple K8s resources into one trait. +## Basic Usage -You can use the keyword `outputs` to create multiple K8s objects. The format MUST be `outputs::`. +Defining a Trait with CUE template is a little different from Workload Type. +The different part is a trait must use `outputs` keyword instead of `output` to define resources that will be generated. -Let's look at an example, assume you hope to make a combo for K8s service and ingress, naming it as `ingress`. +With the help of CUE template, it is very nature to compose multiple K8s resources in one trait. + +The format MUST be `outputs::`. + +Below is an example that make a combo for K8s service and ingress, naming this trait as `ingress`. ```yaml apiVersion: core.oam.dev/v1alpha2 @@ -66,13 +66,7 @@ spec: } ``` -Apply this newly defined TraitDefinition into our system: - -```shell script -kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/registry/ingress.yaml -``` - -You can check it by using the application object like below: +It can be used in the application object like below: ```yaml apiVersion: core.oam.dev/v1alpha2 @@ -82,8 +76,6 @@ metadata: spec: components: - name: express-server - scopes: - healthscopes.core.oam.dev: testapp-default-health settings: cmd: - node @@ -99,13 +91,8 @@ spec: type: webservice ``` -Apply it: - -```shell script -kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/app1.yaml -``` - -Then you will see the deployment behind webservice along with the K8s service and ingress behind the ingress trait created. +After the application deployed, you will see the deployment(belong to webservice) along with the K8s service and +ingress(belong to ingress trait) created. ### Generate multiple resources by using for loop @@ -145,14 +132,7 @@ spec: } ``` - -Apply this newly defined TraitDefinition into our system: - -```shell script -kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/registry/for-loop.yaml -``` - -Use the newly created trait like below: +The usage of this trait could be: ```yaml apiVersion: core.oam.dev/v1alpha2 @@ -164,11 +144,7 @@ spec: - name: express-server type: webservice settings: - cmd: - - node - - server.js - image: oamdev/testapp:v1 - port: 8080 + ... traits: - name: expose properties: @@ -177,25 +153,15 @@ spec: myservice2: 8081 ``` -Apply it: - -```shell script -kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/app2.yaml -``` - -Then you will see the deployment behind webservice along with two K8s services created. - - ## Patch Trait -For the purpose of separate of concerns, we usually won't do decomposition some fields out as trait from the underlying workload. +For the purpose of separate of concerns, we usually want to decompose some fields out as trait from the underlying workload. -For example the [webservice workload] is implemented by K8s Deployment, but the workload doesn't care about the `replicas` field. -In this case, you can write a [ManualScalerTrait](https://github.com/oam-dev/kubevela/tree/master/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait) -CRD controller to control the `replicas` field after the deployment created. - -But now, you are more encouraged to use patch trait in KubeVela. With the help of patch trait, you don't need to write CRD -controller for this case anymore. +For example the [webservice workload] is implemented by K8s Deployment, but the workload doesn't care about the node affinity related fields. + +Users should be able to run this app without node affinity and they can add it as a trait later when they want. +This is exactly what patch trait do. +With the help of CUE template, you don't need to write a CRD controller for this case. The keyword is `patch`, object describe after the keyword will be patched into the workload. @@ -206,8 +172,8 @@ apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata: annotations: - definition.oam.dev/description: "Manually scale the app" - name: scaler + definition.oam.dev/description: "affinity specify node affinity and toleration" + name: node-affinity spec: appliesToWorkloads: - webservice @@ -215,33 +181,82 @@ spec: extension: template: |- patch: { - spec: replicas: parameter.replicas + spec: template: spec: { + if parameter.affinity != _|_ { + affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{ + matchExpressions: [ + for k, v in parameter.affinity { + key: k + operator: "In" + values: v + }, + ]}] + } + if parameter.tolerations != _|_ { + tolerations: [ + for k, v in parameter.tolerations { + effect: "NoSchedule" + key: k + operator: "Equal" + value: v + }] + } + } } + parameter: { - replicas: *1 | int + affinity?: [string]: [...string] + tolerations?: [string]: string } ``` -The patch trait rely on the workload object will always match the structure `spec.replicas` and the type of the field. -So we usually use it with the field `appliesToWorkloads` which can limit the trait can only be used by these specified workloads. +You can use it like: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + image: oamdev/testapp:v1 + traits: + - name: "node-affinity" + properties: + affinity: + server-owner: ["owner1","owner2"] + resource-pool: ["pool1","pool2","pool3"] + tolerations: + resource-pool: "broken-pool1" + server-owner: "old-owner" +``` + +The patch trait rely on the workload object will always match the structure `spec.template.spec.affinity` and +the type of these fields. +So we usually use it with the field `appliesToWorkloads` which can limit the trait only to be used +by these specified workloads. By default, the patch implemented in KubeVela relies on the CUE merge operation. It has these constraints: * New field will be added only when the schema doesn't conflict with each other, and the value not finalized. -For example, if the workload already define the `spec.replicas` is `5`, then the patch trait replicas value `1` will fail to patch. +For example, if a field already has a final value `replicas=5`, then the patch trait will conflict when patches `replicas=1`. +It only works when `replica` is not finalized before patch. * Array list in the patch will be merged into the workload by the order of index, you need to use strategy patch. ### Strategy Patch Trait -`strategy patch` is a special patch logic for patching array list supported in KubeVela, it's not native CUElang feature, -so you need to write annotation for using it. +`strategy patch` is a special patch logic for patching array list supported **only** in KubeVela, +it's not native CUE feature, so you need to write annotation for using it. -The annotation keyword is `//+patchKey=`. +The annotation grammar is `//+patchKey=`. -By adding this annotation, merging logic of two array list will not follow the CUE rule, instead of that, it will +By adding this annotation, merging logic of two lists will not follow the CUE rule, instead of that, it will regard the element type of the array list will always be object, and compare the object field with the specified key name. If the value of the key name equal, then the patch data will merge into that, if no equal found, the patch will append into the array list. @@ -277,7 +292,7 @@ with same name, it will be a sidecar container append into the `spec.template.sp ### Patch works with output -Patch can also work with output, if patch and output both exist in one trait, the patch part will execute first and then +Patch can also work with outputs, if patch and outputs both exist in one trait, the patch part will execute first and then the output object will be rendered out. ```yaml @@ -296,7 +311,7 @@ spec: extension: template: |- patch: {spec: template: metadata: labels: app: context.name} - output: { + outputs: service: { apiVersion: "v1" kind: "Service" metadata: name: context.name @@ -624,74 +639,3 @@ spec: appMountPath: "/usr/share/nginx/html" initMountPath: "/work-dir" ``` - -### Node affinity and anti-affinity - -Node affinity and anti-affinity is also common trait: - -```yaml -apiVersion: core.oam.dev/v1alpha2 -kind: TraitDefinition -metadata: - annotations: - definition.oam.dev/description: "affinity specify node affinity and toleration" - name: node-affinity -spec: - appliesToWorkloads: - - webservice - - worker - extension: - template: |- - patch: { - spec: template: spec: { - if parameter.affinity != _|_ { - affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{ - matchExpressions: [ - for k, v in parameter.affinity { - key: k - operator: "In" - values: v - }, - ]}] - } - if parameter.tolerations != _|_ { - tolerations: [ - for k, v in parameter.tolerations { - effect: "NoSchedule" - key: k - operator: "Equal" - value: v - }] - } - } - } - - parameter: { - affinity?: [string]: [...string] - tolerations?: [string]: string - } -``` - -You can use it like: - -```yaml -apiVersion: core.oam.dev/v1alpha2 -kind: Application -metadata: - name: testapp -spec: - components: - - name: express-server - type: webservice - settings: - image: oamdev/testapp:v1 - traits: - - name: "node-affinity" - properties: - affinity: - server-owner: ["owner1","owner2"] - resource-pool: ["pool1","pool2","pool3"] - tolerations: - resource-pool: "broken-pool1" - server-owner: "old-owner" -``` \ No newline at end of file diff --git a/docs/en/cue/workload-type.md b/docs/en/cue/workload-type.md index d9dff1f61..4548cddf0 100644 --- a/docs/en/cue/workload-type.md +++ b/docs/en/cue/workload-type.md @@ -122,6 +122,8 @@ K8s Deployment and Service. The main workload resource MUST be defined in keyword `output` while the auxiliary workload resources MUST be defined in keyword `outputs` with a resource name inside. +The format MUST be `outputs::`. + In the underlying OAM model, the `output` resource will become the `workload` object while the `outputs` resources will become traits. diff --git a/docs/examples/registry/autoscale.yaml b/docs/examples/registry/autoscale.yaml index d15cde6c0..671abbe10 100644 --- a/docs/examples/registry/autoscale.yaml +++ b/docs/examples/registry/autoscale.yaml @@ -22,7 +22,7 @@ spec: template: | import "strconv" - output: { + outputs: autoscaler: { apiVersion: "standard.oam.dev/v1alpha1" kind: "Autoscaler" spec: { diff --git a/docs/examples/registry/metrics.yaml b/docs/examples/registry/metrics.yaml index b1c2107f7..8110b1022 100644 --- a/docs/examples/registry/metrics.yaml +++ b/docs/examples/registry/metrics.yaml @@ -22,7 +22,7 @@ spec: url: https://prometheus-community.github.io/helm-charts version: 9.4.4 template: |- - output: { + outputs: metrics: { apiVersion: "standard.oam.dev/v1alpha1" kind: "MetricsTrait" spec: { diff --git a/docs/examples/registry/patch-scaler.yaml b/docs/examples/registry/patch-scaler.yaml new file mode 100644 index 000000000..e7775e00e --- /dev/null +++ b/docs/examples/registry/patch-scaler.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Manually scale the app" + name: patch-scaler +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: replicas: parameter.replicas + } + parameter: { + replicas: *1 | int + } \ No newline at end of file diff --git a/docs/examples/registry/rollout.yaml b/docs/examples/registry/rollout.yaml index 8b273e04a..301d34e70 100644 --- a/docs/examples/registry/rollout.yaml +++ b/docs/examples/registry/rollout.yaml @@ -21,7 +21,7 @@ spec: url: https://oam.dev/flagger/archives/ version: 1.1.0 template: |- - output: { + outputs: canary: { apiVersion: "flagger.app/v1beta1" kind: "Canary" spec: { diff --git a/docs/examples/registry/route.yaml b/docs/examples/registry/route.yaml index cba56f0ba..03579bd39 100644 --- a/docs/examples/registry/route.yaml +++ b/docs/examples/registry/route.yaml @@ -19,7 +19,7 @@ spec: url: https://kubernetes-charts.storage.googleapis.com/ version: 1.41.2 template: | - output: { + outputs: route: { apiVersion: "standard.oam.dev/v1alpha1" kind: "Route" spec: { diff --git a/hack/vela-templates/cue/manualscale.cue b/hack/vela-templates/cue/manualscale.cue index 5e0fd5c9a..9b6f3ca33 100644 --- a/hack/vela-templates/cue/manualscale.cue +++ b/hack/vela-templates/cue/manualscale.cue @@ -1,4 +1,4 @@ -output: { +outputs: scaler: { apiVersion: "core.oam.dev/v1alpha2" kind: "ManualScalerTrait" spec: {