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