Patching Kubernetes Resources

A member of a Slack channel I frequent recently asked how you go about patching existing Kubernetes resources. After responding, I thought that others might benefit from the same information. In today’s example, we’re going to resize a PersistentVolumeClaim (PVC).

We’ll start off with a dynamic StorageClass.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: dynamic
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

Next, we’ll create a PersistentVolume (PV) based on the new StorageClass.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: dynamic-pv
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 500Mi
  local:
    path: /tmp/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - kind-control-plane
  storageClassName: dynamic
  volumeMode: Filesystem

And finally, we’ll create a PersistentVolumeClaim (PVC) that our Pod can use to claim some storage.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi
  storageClassName: dynamic
  volumeMode: Filesystem

If we check the status of our three new resources we should see that all three have been created and the PVC is in a Pending state.

$ k get sc
NAME                 PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
dynamic              kubernetes.io/no-provisioner   Retain          WaitForFirstConsumer   true                   83s
standard (default)   rancher.io/local-path          Delete          WaitForFirstConsumer   false                  64d
 
$ k get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
dynamic-pv   500Mi      RWO            Retain           Available           dynamic                 12s
 
$ k get pvc
NAME          STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
dynamic-pvc   Pending                                      dynamic        10s

The PVC is Pending because our binding mode is WaitForFirstConsumer and we haven’t created a Pod yet to consume it. Let’s fix that.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
    volumeMounts:
    - name: data
      mountPath: /var/www/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: dynamic-pvc
  dnsPolicy: ClusterFirst
  restartPolicy: Always

Now if we check the status of our PVC we can see that it’s bound to our PV.

k get pvc
NAME          STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
dynamic-pvc   Bound    dynamic-pv   500Mi      RWO            dynamic        3m25s

Now we can finally get to the meat of this post. In order to patch a resource, you need to pass the JSON path. It can look a little ugly, but this one isn’t so bad. First, let’s take a look at the PVC in JSON format.

Since 1.19 include the new managedFields section which, imho, provides a lot of bloat that the average user doesn’t need to know or care about, I’m excluding that from this snippet.

    "spec": {
        "accessModes": [
            "ReadWriteOnce"
        ],
        "resources": {
            "requests": {
                "storage": "100Mi"
            }
        },
        "storageClassName": "dynamic",
        "volumeMode": "Filesystem",
        "volumeName": "dynamic-pv"
    },
    "status": {
        "accessModes": [
            "ReadWriteOnce"
        ],
        "capacity": {
            "storage": "500Mi"
        },
        "phase": "Bound"
    }

If we further strip everything else we don’t need, this is what we’re left with.

    "spec": {
        "resources": {
            "requests": {
                "storage": "100Mi"
            }
        },
    },

So we really only have four steps to our data. The patch command is:
k patch <resource> -p <command>

For our example, that will start off as k patch pvc dynamic-pvc.

Now we turn the JSON path into a one-line command with our new value.

'{"spec":{"resources":{"requests":{"storage":"200Mi"}}}}'

The full command is:

$ k patch pvc dynamic-pvc -p '{"spec":{"resources":{"requests":{"storage":"200Mi"}}}}'
persistentvolumeclaim/dynamic-pvc patched

If you do a describe on the PVC you won’t notice anything special.

$ k describe pvc dynamic-pvc
Name:          dynamic-pvc
Namespace:     dynamic
StorageClass:  dynamic
Status:        Bound
Volume:        dynamic-pv
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      500Mi
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    nginx
Events:
  Type    Reason                Age                    From                         Message
  ----    ------                ----                   ----                         -------
  Normal  WaitForFirstConsumer  2m33s (x9 over 4m30s)  persistentvolume-controller  waiting for first consumer to be created before binding

Just like a Deployment, you can also record the actions taken by your patches. Use the same --record flag. Let’s patch the PVC again.

$ k patch pvc dynamic-pvc -p '{"spec":{"resources":{"requests":{"storage":"275Mi"}}}}' --record
persistentvolumeclaim/dynamic-pvc patched

Now we see an annotation has been added noting our action.

$ k describe pvc dynamic-pvc
Name:          dynamic-pvc
Namespace:     dynamic
StorageClass:  dynamic
Status:        Bound
Volume:        dynamic-pv
Labels:        <none>
Annotations:   kubernetes.io/change-cause: kubectl patch pvc dynamic-pvc --patch={"spec":{"resources":{"requests":{"storage":"275Mi"}}}} --record=true

There you go. Now you can use the same pattern to patch other resources throughout your Kubernetes clusters.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.