github.com/oam-dev/kubevela@v1.9.11/design/vela-core/apply-workload-and-trait.md (about) 1 # Apply workload/trait through 3-way-merge-patch 2 3 - Owner: Yue Wang(@captainroy-hy), Jianbo Sun(@wonderflow) 4 - Date: 01/21/2021 5 - Status: [Implemented](https://github.com/kubevela/kubevela/pull/857) 6 7 8 ## Intro 9 10 When an ApplicationConfiguration is deployed, 11 vela-core will create(apply) corresponding workload/trait instances and keep them stay align with the `spec` defined in ApplicationConfiguration through periodical reconciliation in AppConfig controller. 12 13 In each round of reconciliation, if the configurations rendered from AppConfig are changed comparing to last round reconciliation, it's required to apply all changes to the workloads or traits. 14 Additionally, it also allows others (anything except AppConfig controller, e.g., trait controllers) to modify workload/trait instances. 15 16 17 ## Goals 18 19 Apply should handle three kinds of modification including 20 - add a field 21 - change a field 22 - remove a field by omitting it 23 24 Meanwhile, Apply should have no impact on changes made by others, namely, not eliminate or override those changes UNLESS the change is made upon fields that are rendered from AppConfig originally. 25 26 27 ## Implementation 28 29 We employed the same mechanism as `kubectl apply`, that is, computing a 3-way diff based on target object's current state, modified state, and last-appied state. 30 Specifically, a new annotation, `app.oam.dev/last-applied-configuration`, is introduced to record workload/trait's last-applied state. 31 32 Once there's a conflict on field, both changed by AppConfig and others, AppConfig's value will always override others' assignment. 33 34 35 ## Impact on existing system 36 37 Before this implementation, vela-core use `JSON-Merge` patch to apply workloads and `update` to apply traits. 38 That brought several defects shown in below samples. 39 This section introduced a comparison between how old mechanism and new applies workload/trait, also shows how new Apply overcomed the defects. 40 41 ### Apply Workloads 42 43 The reason why abandon json-merge patch is that, it cannot remove a field through unsetting value in the patched manifest. 44 45 #### Before 46 47 For example, apply below deployment as a workload. json-merge patch cannot remove `minReadySeconds` field through applying a modified manifest with `minReadySeconds` omitted . 48 ```yaml 49 # original workload manifest 50 apiVersion: apps/v1 51 kind: Deployment 52 ... 53 spec: 54 minReadySeconds: 60 55 replicas: 3 56 template: 57 spec: 58 containers: 59 - name: nginx 60 image: nginx:1.14.2 61 --- 62 # modified workload manifest 63 apiVersion: apps/v1 64 kind: Deployment 65 ... 66 spec: 67 # minReadySeconds: 60 <=== unset to remove field 68 replicas: 3 69 template: 70 spec: 71 containers: 72 - name: nginx 73 image: nginx:1.14.2 74 --- 75 # result 76 apiVersion: apps/v1 77 kind: Deployment 78 ... 79 spec: 80 minReadySeconds: 60 # <=== not removed 81 replicas: 3 82 template: 83 spec: 84 containers: 85 - name: nginx 86 image: nginx:1.14.2 87 ``` 88 #### After 89 90 By computing a 3-way diff, we can get a patch aware of the field set in last-applied manifest is omitted in the new modified manifest, namely, users wanna remove this field. 91 And an annotation, `app.oam.dev/last-applied-configuration`, is used to record last-applied-state of the resource for further use in computing 3-way diff next time. 92 93 ```yaml 94 # result 95 apiVersion: apps/v1 96 kind: Deployment 97 metadata: 98 annotations: # v=== record last-applied-state 99 app.oam.dev/last-applied-configuration: | 100 {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"nginx-deployment","labels":{"app":"nginx"}},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx:1.14.2"}]}}}} 101 ... 102 spec: 103 # minReadySeconds: 60 <=== removed successfully 104 replicas: 3 105 template: 106 spec: 107 containers: 108 - name: nginx 109 image: nginx:1.14.2 110 ``` 111 --- 112 113 ### Apply Traits 114 115 The reasons why abandon `update` 116 117 - update always eliminates all fields set by others (e.g., trait controllers) 118 - if trait contains immutable field (e.g., k8s Service), update fails 119 120 #### Before 121 For example, apply below Service as a trait. 122 ```yaml 123 # original trait manifest 124 apiVersion: v1 125 kind: Service 126 metadata: 127 name: my-service 128 spec: 129 selector: 130 app: myweb 131 ports: 132 - protocol: TCP 133 port: 80 134 --- 135 # after applying 136 apiVersion: v1 137 kind: Service 138 ... 139 spec: 140 clusterIP: 172.21.7.149 # <=== immutable field 141 ports: 142 - port: 80 143 protocol: TCP 144 targetPort: 80 145 selector: 146 app: myweb 147 sessionAffinity: None 148 type: ClusterIP 149 status: 150 loadBalancer: {} 151 --- 152 # update with original manifest fails 153 # reconciling also fails for cannot applying trait 154 ``` 155 Additionally, if a trait has no immutable field, update will eliminate all fields set by others. 156 ```yaml 157 # original trait manifest 158 kind: Bar 159 spec: 160 f1: v1 161 # someone add a new field to it 162 kind: Bar 163 spec: 164 f1: v1 165 f2: v2 # <=== newly set field 166 # after reconciling AppConfig 167 kind: Bar 168 spec: 169 f1: v1 170 # f2: v2 <=== removed 171 ``` 172 But as described in [Goals](#goals) section, we expect to keep these changes. 173 174 #### After 175 176 Applying traits works in the same way as workloads. We use annotation, `app.oam.dev/last-applied-configuration`, to record last-applied manifest. 177 178 - Because 3-way diff will ignore the fields not touched in last-applied-state, the immutable fields will not be involved into patch data. 179 - Because 3-way diff also will ignore the fields set by others (others are not supposed to modify the field rendered from AppConfig), the changes made by others will be retained. 180