Kubectl Patch – Why we need and How to use it

0
3346
kubectl patch

In any running applications, sometimes we may need to change the configurations. Same applies for the Kubernetes, here either you can update it directly in the Yaml file or you can kubectl patch command. In this article let us learn about Kubernetes patch in detail.

We all aware, the kubectl command line utility offers a lot of functionality to us to help to manage our Kubernetes resources. Part of that, we have kubectl patch command, which allows us to change running configurations immediately.

In this guide, we will see how we can use the kubectl patch command to manipulate our Kubernetes resources, and we will learn the different options presented to you by this command. You will then learn some best practices that you can follow to ensure that your usage of this command is as sustainable as possible.

Why Do We Need kubectl patch?

kubectl gives us several different ways to update running configurations. The main ways are the apply, edit, and patch commands. Like if we need to update API objects in place, kubectl patch is one of the commands available to us that we will likely want to consider using.

The patch command lets us to apply a change to our running configuration by specifying only small patch value that we wish to change and then using one of three different patch types to apply this change. There are three patch types each vary slightly in how they apply the change, and as such, each is suitable for different use cases. Applying small changes like this can be useful if we want to test configuration changes or perform some testing, but do not want to update the whole configuration file. However, if we decide to keep the changes permanently, it is good idea to manage via Yaml in local or store in GIT for better tracking or follow via GitOps way.

kubectl patch

The best way to learn any command line utilities are by using it. If you want to follow along with the examples in this article, you will need a Kubernetes cluster to experiment with. You can either create one with a cloud provider such as AKS, or set up a local cluster using one of the various tools available to do so.

If you are using Mac or Windows, you can get a simple single-node cluster via Docker Desktop. If you are running a Linux-based OS, there are several options available to you, such as minikube and MicroK8s.

Using kubectl patch

To understand about the kubectl patch command, lets create one sample resource to try our changes. The following configuration file will create a simple deployment with a single replica,

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: patch-test
spec:
  replicas: 1
  selector:
    matchLabels:
    app: nginx
  template:
    metadata:
    labels:
        app: nginx
    spec:
    containers:
    - name: nginx-container
        image: nginx

Run the following command to apply the manifest.

# kubectl apply -f deployment.yaml

Now you could see the pads are deployed, you can see using “kubectl get pods” .

The first way to use patch is by running the command with your patch object inline. In this case, if you wanted to patch the deployment to have two replicas, it would look something like this:

# kubectl patch deployment patch-demo -p '{"spec": {"replicas": 2}}'

If you run kubectl get pods again, you will see two replicas, where previously there was only one. This approach can be useful for small changes or cases where repeatability is not a key consideration. Some real use case for doing your patches inline is that sometimes when trying to delete a PersistentVolume or some others resources like deleting namespace, or some pods or if you are using ArgoCD, applications will seemingly hang indefinitely. You can easily resolve these issues by running inline patches as follows:

# kubectl patch {{ resource }} {{ resource_name }} -p '{"metadata":{"finalizers":null}}'
# kubectl patch pvc pvc_name -p '{"metadata":{"finalizers":null}}'
# kubectl patch pv pv_name -p '{"metadata":{"finalizers":null}}'
# kubectl patch pod pod_name -p '{"metadata":{"finalizers":null}}'

In many instances, however, it is a good idea to apply your patches from a patch file, which is a partial YAML file saved to your disk/or you can store in Git repo. If you were to do this with the above example of increasing the replicas in your deployment to two, you would first need to create a new file called patch-file.yaml or your own naming pattern, which would contain your patch object:

spec:
  replicas: 2

From the terminal, you can now patch your deployment by providing the file, instead of the object itself:

# kubectl patch deployment patch-test --patch-file patch-file.yaml

Different Patch Types

A few different types of patches can be performed when applying configs with the patch command. In the above examples, kubectl uses the strategic merge type by default. The available types are:

  • json: A JSON patch allows you to provide an operation or operations to carry out on the object.
  • json<strong> </strong>merge: A JSON merge patch allows you to naively override keys in an object.
  • strategic: A strategic merge patch will either replace or merge values based on the object’s patchStrategy as defined in the Kubernetes source code. This is the default patch type.

Lets see one by one patch types with some examples.

Strategic Patch

Let’s start by applying a patch with the default mode: strategic.

Start by retrieving detailed information about the nginx containers we’re running. This command retrieves the current pods and parses out the container types, then it filters for names that include nginx.

# kubectl get pods -o jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |grep nginx

As expected, we’re running two instances of nginx’s latest image. Let’s patch the deployment with a specific version of the container.

Start by creating a new YAML file named nginx_patch.yaml:

spec:
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:1.23.0-alpine

This file contains just enough information for patch to figure out what we’re trying to do: replace image: nginx:latest with image: nginx:1.23.0-alpine.

Let’s apply this patch with kubectl patch:

# kubectl patch deployment nginx-deployment --patch-file nginx_patch.yaml
deployment.apps/patch-test patched

Wait a few seconds, and then retrieve the container details again.

# kubectl get pods -o jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |grep nginx

Kubectl updated the configuration and restarted the containers for us.

We can retrieve the YAML configuration for one of the pods and see the change:

# kubectl get pod patch-test-rsq213s-42hc3 -o yaml
apiVersion: v1
kind: Pod
metadata:
spec:
  containers:
  - image: nginx:1.23.0-alpine
    imagePullPolicy: Always
    name: nginx

With a strategic patch, kubectl does the work of figuring out how to apply the changes we supplied it. Here, we replaced a node in a list with a new value. Depending on the change and the underlying type, the changes can get more complicated, so the default merge type is often your best option.

Merge

With a merge patch, kubectl will replace the entire targeted configuration node with the supplied code. This patch type is often called a JSON merge, even though you can supply it with YAML configuration, and it doesn’t merge the target configuration node: it replaces it. This is because it follows the JSON merge semantics defined in RFC 7386.

Example;

We want to add Redis to our deployment. So, here’s a file with a Redis container in the containers list. It’s in a file name nginx_merge_patch.yaml:

spec:
  template:
    spec:
      containers:
      - name: redis
        image: redis:latest
        ports:
        - containerPort: 6379

Apply this patch as a merge with the –type command line argument.

# kubectl patch deployment nginx-deployment --type merge --patch-file nginx_merge_patch.yaml
deployment.apps/nginx-deployment patched
# kubectl get pods -o wide
# kubectl get pods -o jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |grep nginx

The nginx containers are gone? Kubectl removed them and replaced them with Redis!

Let’s look at the runtime config for one pod:

# kubectl get pod nginx-deployment-7c99566855-4t5g7 -o yaml |less
spec:
  containers:
  - image: redis:latest
    imagePullPolicy: Always
    name: redis
    ports:
    - containerPort: 6379
      protocol: TCP

Yes, that’s exactly what the configuration shows. JSON merge replaced the containers list with the new one.

What happens when we reapply the previous patch with the default type?

# kubectl patch deployment nginx-deployment --patch-file nginx_patch.yaml
# kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |grep nginx

So, we have seen the difference between strategic and merge patch types at work. The merge replaced the containers list, strategic added the changes to the list instead.

JSON Patch

There are some important differences between each of the types. Notably, a JSON patch uses a slightly more complicated syntax, whereby you specify a sequence of atomic operations that are executed one after another. For the above example of setting the number of replicas, it might look something like this:

A JSON patch follows the methodology defined in RFC 6902. Instead of supplying kubectl with configuration information, you give it a list of directives.

With JSON:

- op: replace
  path: "/spec/replicas"
  value: 2

or with CLI:

# kubectl patch deployment patch-test --type JSON --patch-file nginx_patch.yaml
deployment.apps/patch-test patched

The key difference with JSON patches is that instead of specifying a partial fragment of the config that you wish to combine with the existing config, you simply specify instructions to carry out. This type of patch is good for more granular operations, as you can specify each atomic change individually. You can also perform multiple operations together. If you wanted to add a Redis container to your pod, and a new corresponding label, you could perform a JSON patch with the following:

- op: add
  path: "/spec/template/spec/containers/1"
  value:
    name: redis-container
    image: redis
- op: add
  path: "/spec/template/metadata/labels/cache"
  value: redis

The conceptually simpler JSON merge patch is good for small replacement operations but has some limitations. If you want to update a list, you need to specify the entire new list, not just new items. Additionally, you can delete a key by setting its value to null, but as a result, it is not possible to update a value to have a null literal value.

Best Practices

When using the patch command, it’s easy to get carried away and potentially create a bit of a mess, where your running configs don’t match what you have on disk, and you aren’t sure what has changed. To avoid this kind of scenario, there are some best practices that you can follow to keep things nice and tidy.

Prefer Patch Files

Inline patching can be useful if you’re just trying something out, or if you are making a throwaway change that you don’t care about. When working with resources that you care about, though, it’s often wise to use patch files for a few reasons:

Keeping patch files can give you an idea of the changes that your resources have gone through without requiring you to inspect the running configuration.

There’s a chance that your patch might not be exactly right and could require some tweaks. In this case, updating and reapplying the patch file is more convenient than doing multiple inline patches.

Know When to Use kubectl patch

There are multiple different ways that you can update configurations in Kubernetes. In addition to patch, there is also kubectl apply and kubectl edit. Suppose you create and manage your resources using apply. In that case, it is generally a good idea to use this when updating your configurations, as apply keeps track of the previously applied configuration via annotation. This annotation is not updated by patch, so you miss some functionality there.

Use kubectl patch Sparingly

The patch command can be very useful for testing and experimentation. Still, even with patch files, it is generally a good idea to encode changes you will be keeping into your base config files. This is because when you recreate your resources by applying your config, your patches will not be automatically reapplied. This can be inconvenient, especially if you have multiple patches you need to apply. Reflecting changes, you wish to keep in your base configs makes future maintainability much easier.

Examples

To add a new port named metrics to an existing deployment:

# kubectl patch deployment internal-nginx-ingress-controller -n kube-system --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/ports/-", "value": {"containerPort": 10254,"name": "metrics"}}]'

Partially update a node using strategic merge patch

# kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'

Partially update a node identified by the type and name specified in “node.json” using strategic merge patch

# kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'

Update a container’s image; spec.containers[*].name is required because it’s a merge key

# kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'

Update a container’s image using a json patch with positional arrays

# kubectl patch pod valid-pod -type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'

Conclusion

The patch command is a useful tool to be familiar with and can ease the debugging and experimentation process significantly. However, it is important to know when to use it and whether there is a better alternative available. So it’s important to be familiar with the patch command to use it effectively, it’s just as important to know when it is time to reach for a different tool.

Google search engine