Correct Comparison of Kubernetes Apply, Replace, and Patch

Kubernetes offers several options for updating resources: apply, edit, patch, and replace. There is some confusion about what each of them does and when to use them. Let's clarify.

Correct Comparison of Kubernetes Apply, Replace, and Patch

If search on Google the phrase "kubernetes apply vs replace" returns an answer on StackOverflow, which is not correct. When searching for "kubernetes apply vs patch," the first link is the documentation for kubectl patch, which does not include a comparison of Apply and patch. This article will explore the different options as well as the proper usage of each.

During the lifecycle of a Kubernetes resource (service, deployment, ingress, etc.), it is sometimes necessary to change, add, or remove certain properties of that resource. For example, adding an annotation, increasing or decreasing the number of replicas.

Kubernetes CLI

If you are already working with Kubernetes clusters through the CLI, you are familiar with Apply and edit. The command Apply reads the resource specification from a file and performs an "upsert" in the Kubernetes cluster, meaning it creates the resource if it does not exist and updates it if it does. The command edit reads the resource through the API, then writes the resource specification to a local file, which is then opened in a text editor. After you edit and save the file, kubectl will send the changes back through the API, which will carefully apply those changes to the resource.

Not everyone is aware that the commands patch and replace. The command patch allows you to modify part of the resource specification by providing only the changed part in the command line. The command replace works just like edit, but everything has to be done manually: you need to download the current version of the resource specification, for example, using kubectl get -o yaml, edit it, and then use replace to update the resource with the modified specification. The command replace will not work if there have been any changes between reading and replacing the resource.

Kubernetes API

You are likely familiar with the methods CoreV1().Pods().Update(), replaceNamespacedService or patch_namespaced_deployment, if you work with clusters through the client library for the Kubernetes API using some programming language. The library handles these methods using HTTP protocol requests with the methods PUT and PATCH. Meanwhile, update and replace use PUT, and patch, as trivial as it may seem, uses PATCH.

It should be noted that kubectl also works with clusters via API. In other words, kubectlis a wrapper over the client library for the Go language, significantly enabling the provision of subcommands in a more compact and readable form in addition to the standard API capabilities. For example, as you may have noticed, the method Apply was not mentioned above in the previous paragraph. Currently (May 2020, translator's note) all the logic kubectl apply, i.e. creating non-existent resources and updating existing ones, operates entirely on the code side kubectl. Efforts are being made to move the logic Apply to the API side, but this is still in beta testing. I will elaborate below.

Patch by default

It is best to apply patch, if you wish to update a resource. This is how both the client libraries on top of the Kubernetes API and kubectl (not surprisingly, since it is a wrapper for the client library, translator's note).

To work strategically

All commands kubectl Apply, edit and patch use the method PATCH in HTTP requests to update an existing resource. If you delve deeper into the implementation of the commands, you'll find that all use the approach strategic-merge patching for resource updates, although the command patch may use other approaches (more about this below). The strategic-merge patching approach attempts to "do everything right" when merging the provided specification with the existing one. More specifically, it tries to merge both objects and arrays, meaning changes are generally additive. For example, running the command patch with a new environment variable in the pod container specification results in that environment variable being added to the existing environment variables rather than overwriting them. To remove using this approach, you should forcibly set the parameter's value to null in the provided specification. Which of the commands kubectl is best for updating?

If you are creating and managing your resources using kubectl apply, it's always better to use kubectl apply, so that kubectl can manage the configuration and correctly track the requested changes from application to application. The advantage of always using Apply is that it tracks the previously applied specification, allowing it to know when properties of the specification and array elements are explicitly removed. This enables the use of Apply to remove properties and elements from an array, while standard strategic merging will not work. Commands edit and patch do not update the notes that kubectl apply it uses to track its changes, so any modifications tracked and made via the Kubernetes API but made through commands edit and patch, are invisible to subsequent commands Apply, which means Apply does not remove them, even if they do not appear in the input specification for Apply (The documentation states that edit and patch makes updates to the notes used Apply, but in practice - it does not).

If you are not using the command Apply, you can use both editand patch, choosing the command that best fits the change being made. When adding and modifying specification properties, both approaches are roughly the same. When deleting specification properties or elements from an array edit behaves like a one-time run Apply, including tracking what the specification was before and after editing it, allowing properties and elements of the array to be explicitly removed from the resource. You need to explicitly set the property value to null in the specification for patch, to remove it from the resource. Deleting an array element using strategic-merge patching is more complex, as it requires the use of merge directives. See other update approaches below for selecting more acceptable alternatives.

To implement update methods in the client library that behave similarly to the above commands kubectl, you should set content-type downward API support (simultaneously with this in application/strategic-merge-patch+json. If you want to remove properties in the specification, you need to explicitly set their values to null similar to kubectl patch. If you need to remove array elements, you should include merge directives in the update specification or use another update approach.

Other update approaches

Two other update approaches are supported in Kubernetes: JSON merge patch and JSON patchThe JSON merge patch approach accepts a partial Kubernetes specification as input and supports merging objects similarly to the strategic-merge patching approach. The difference between them is that it only supports array replacements, including the container array in the pod specification. This means that when using JSON merge patch, you need to provide complete specifications for all containers if any property of any container is changed. Thus, this approach is useful for removing elements from an array in the specification. In the command line, you can choose JSON merge patch using kubectl patch --type=mergeWhen working with the Kubernetes API, you should use the request method PATCH and set content-type downward API support (simultaneously with this in application/merge-patch+json.

The JSON patch approach, instead of providing a partial resource specification, uses a list of changes you want to apply to the resource in the form of an array, where each element of the array represents a description of the change being made to the resource. This approach is a more flexible and powerful way to express the changes being made but comes at the cost that the list of changes is in a separate, non-Kubernetes format, instead of sending a partial resource specification. In kubectl you can choose JSON patch using kubectl patch --type=jsonWhen using the Kubernetes API, this approach works with the request method PATCH and set content-type downward API support (simultaneously with this in application/json-patch+json.

Need assurance — use replace

In some cases, you need assurance that the resource will not be changed between the time of reading the resource and updating it. In other words, you should ensure that all changes are atomic. In this case, to update resources, you should use replace. For example, if there is a ConfigMap with a counter being updated by multiple sources, you should ensure that the two sources do not update the counter simultaneously, which would lead to loss of updates. To demonstrate, imagine a sequence of events using the approach patch:

  • A and B get the current state of the resource from the API
  • Each of them locally updates the specification, incrementing the counter by one and also adding "A" or "B" respectively to the "updated-by" note
  • A updates the resource slightly faster
  • B updates the resource

As a result, update A is lost. The last operation patch wins, the counter increases by one instead of two, and the updated-by note ends with "B" and does not contain "A". Let's compare the above with what happens when updates are made using the approach replace:

  • A and B get the current state of the resource from the API
  • Each of them locally updates the specification, incrementing the counter by one and also adding "A" or "B" respectively to the "updated-by" note
  • A updates the resource slightly faster
  • B attempts to update the resource, but the update is rejected by the API because the version of the resource in the specification replace does not match the current version of the resource in Kubernetes, as the version of the resource was incremented during the replace operation by A.

In the above case, B will have to re-fetch the resource, make changes to the new state, and attempt again to do replace. As a result, the counter will increase by two, and the updated-by note will contain "AB" at the end.

The above example implies that during the execution replace a complete replacement of the entire resource occurs. The specification used for replace, must not be partial, or parts as in Apply, but complete, including the addition of resourceVersion in the specification metadata. If you did not include resourceVersion or the version you provided is not current, the replacement will be rejected. Thus, the best approach to use replace is to read the resource, update it, and immediately replace it. Using kubectl, it might look like this:

$ kubectl get deployment my-deployment -o json 
    | jq '.spec.template.spec.containers[0].env[1].value = "new value"' 
    | kubectl replace -f -

It should be noted that the following two commands, executed sequentially, will succeed since deployment.yaml does not contain the property .metadata.resourceVersion

$ kubectl create -f deployment.yaml
$ kubectl replace -f deployment.yaml

This seems to contradict what was said above, i.e. "adding resourceVersion in the specification metadata." Is it incorrect to assert this? No, it is not, because if kubectl notices that you did not specify resourceVersion, it will read it from the resource and add it to the specification you provided and only then perform replace. Since this is potentially dangerous, if relying on atomicity, this magic works entirely on the side of kubectl, one should not rely on it when using client libraries that interact with the API. In this case, you will have to read the current resource specification, update it, and then perform PUT the request.

Cannot patch – doing replace

Sometimes it is necessary to make changes that cannot be processed by the API. In these cases, you can forcibly replace a resource by deleting and recreating it. This is done using kubectl replace --force. Running the command immediately deletes the resources and then recreates them with the provided specification. The API does not have a "force replace" handler, and to do this via the API, you need to perform two operations. First, you need to delete the resource, setting its gracePeriodSeconds to zero (0) and propagationPolicy to "Background", and then recreate this resource with the desired specification.

Attention: this approach is potentially dangerous and may lead to an undefined state.

Apply on the server side.

As mentioned above, Kubernetes developers are working on implementing logic Apply from kubectl in the Kubernetes API. The logic Apply is available in Kubernetes 1.18 through kubectl apply --server-side or via the API, using the method PATCH with content-type application/apply-patch+YAML.

Note: JSON is also valid YAML, so you can send the specification in JSON format even if content-type it will be application/apply-patch+yaml.

In addition to the fact that the logic kubectl becomes available for everyone via the API, Apply on the server side tracks the responsible parties for the fields in the specification, thus allowing safe concurrent access for conflict-free editing. In other words, if Apply on the server side receives wider distribution, there will be a universal safe resource management interface for various clients, such as kubectl, Pulumi, Terraform, GitOps, as well as custom scripts using client libraries.

Summary

I hope this brief overview of different ways to update resources in clusters was helpful to you. It is useful to know that the debate is not simply about apply versus replace, as you can update a resource using apply, edit, patch, or replace. Each approach has its own area of application. For atomic changes, replace is preferable; otherwise, you should use a strategic-merge patch via apply. Ultimately, I hope you understand that you shouldn't rely solely on Google or StackOverflow when looking for "kubernetes apply vs replace". At least until this article replaces the current answer.

Correct Comparison of Kubernetes Apply, Replace, and Patch

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster