1. Introduction
Containers are, by their essence, ephemeral. As a container orchestration framework, Kubernetes works within these bounds via isolated namespaces and temporary volumes. However, the system also builds on top of these concepts by providing persistent storage, available within a pod, a node, or even between nodes. Regardless of their application, persistent volumes have limited sizes. So, we might want to change their size. Although resizing a volume might not be an issue, applying the new size to pods that use it can be challenging.
In this tutorial, we explore persistent volumes, persistent volume claims, and how to resize a persistent volume (PV) and persistent volume claim (PVC) in Kubernetes. First, we go through the defining characteristics of a PV. Next, we create PV and PVC using definitions. After that, we confirm the status of both. Finally, we demonstrate ways to resize both PV and PVC.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15. Unless otherwise specified, it should work in most POSIX-compliant environments.
2. Persistent Volume (PV)
Volumes are a way to attach storage to a Kubernetes object.
There are two main storage types within Kubernetes:
- ephemeral
- persistent
Naturally, persistent volumes preserve their contents between pod, service, and container restarts.
2.1. Types
There are currently only six non-deprecated types of persistent volumes:
- csi: Container Storage Interface (CSI)
- fc: Fibre Channel (FC) storage
- hostPath: maps a given host node path
- iscsi: iSCSI (SCSI over IP) storage
- local: local node storage devices
- nfs: Network FileSystem (NFS) storage
Each comes with specifics, but the idea behind Kubernetes is to partially hide these. One way to do so comes down to the concept of a Storage Class, i.e., a type definition that serves as a higher-level storage definition.
Yet, we can use a simple PV as well.
2.2. Provisioning and Usage
When it comes to persistent volumes, we provision them in two ways:
- static: PV is an object, PVC finds and uses a specific PV
- dynamic: PVC attempts to dynamically create a volume during runtime
Either way, a PVC is Bound to a PV. In the case of dynamic provisioning, the relationship is always between the same PV-PVC pair.
Once we have a resolved PVC, pods can get it assigned and use it.
2.3. Reclaim Policy
After a volume has served its purpose via an associated claim, Kubernetes can perform one of three actions:
- Retain: consider PV Released, but prevent further claims, enabling manual intervention to inspect, free data, or make available
- Delete: delete and wipe the PV
- Recycle: wipe the PV and enable new claims
Effectively, Retain blocks further claims on the Released volume, instead forcing a new PV to be created for a particular storage medium. In fact, we can later change the status to Available.
2.4. Status
In general, a PV can be in one of several phases:
- Available: free, not bound to PVC
- Bound: bound to PVC
- Released: PVC deleted, PV expects manual intervention
- Failed: failed automatic reclamation
Usually, it’s easy to check the current state via the get subcommand of kubectl.
2.5. Access Modes
Volumes provide different access modes according to the way they can be mounted:
- ReadWriteOnce (RWO): read-write by a single node
- ReadOnlyMany (ROX): read-write by a single pod
- ReadWriteMany (RWX): read-only by many nodes
- ReadWriteOncePod (RWOP): read-write by many nodes
Still, we can only use one mode per mount and PVC.
3. Create Persistent Volume (PV)
To begin with, let’s create a persistent volume (PV):
$ kubectl apply --filename=<(echo '
apiVersion: v1
kind: PersistentVolume
metadata:
name: hostpath-vol0
namespace: default
spec:
storageClassName: xclass
capacity:
storage: 6Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/hostpath-vol0-source-path"
type: DirectoryOrCreate
')
persistentvolume/hostpath-vol0 created
In this case, we create a hostPath volume from the /mnt/hostpath-vol0-source-path/ path, which gets created if it doesn’t exist. Its storage class is xclass.
Importantly, the storage capacity is 6Gi and the access mode is ReadWriteOnce.
4. Create Persistent Volume Claim (PVC)
Although it can initially be a bit confusing, storage claims in Kubernetes are just storage requests. Such claims can be part of the dynamic pod definition or separate objects.
Let’s create a separate persistent volume claim (PVC) object:
$ kubectl apply --filename=<(echo '
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hostpath-vol0-claim
namespace: default
spec:
storageClassName: xclass
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
')
persistentvolumeclaim/hostpath-vol0-claim created
By specifying a storageClassName of xclass, we link this PVC to the PV of the same class. If all other specifics match and there is enough space, they should connect.
Notably, claims don’t directly specify a PV. Instead, a PVC is like a filter with criteria for any existing PV that suits the needs. There are different algorithms for resolving multiple matches into one.
In addition, let’s create a PVC without a storage class name:
$ kubectl apply --filename=<(echo '
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pod0-claim
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
')
persistentvolumeclaim/pod0-claim created
Although we didn’t create a volume to satisfy this claim beforehand, it should get Bound.
5. Check PV and PVC
At this point, we can check how the default namespace claims look via the get subcommand and the pvc type:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hostpath-vol0-claim Bound hostpath-vol0 6Gi RWO xclass 27m
pod0-claim Bound pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991 2Gi RWO standard 2m10s
The STATUS of both claims is Bound, meaning a satisfactory volume was found for each. Further, this VOLUME is hostpath-vol0 for hostpath-vol0-claim. On the other hand, pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991 is the assignment of pod0-claim.
Next, let’s check each PV:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
hostpath-vol0 6Gi RWO Retain Bound default/hostpath-vol0-claim xclass 29m
pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991 2Gi RWO Delete Bound default/pod0-claim standard 4m15s
As we can see, a new volume was created dynamically based on the second claim.
Should we no longer need either volume, hostpath-vol0 would be [Retain]ed, while pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991 should be [Delete]d.
6. Resize Existing Persistent Volume
Indeed, it’s fairly simple to resize a PV:
$ kubectl edit pv/hostpath-vol0
[...]
capacity:
storage: 7Gi
[...]
persistentvolume/hostpath-vol0 edited
Thus, by changing the storage under capacity, we resize the persistent volume.
The same works for the generated PV:
$ kubectl edit pv/pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991
[...]
capacity:
storage: 3Gi
[...]
persistentvolume/pvc-04ae85d9-6c3b-4f56-8ed0-7e666f965991 edited
This time, we successfully changed 2Gi to 3Gi.
However, even if we modify the PV capacity, each PVC remains the same.
7. Resize Existing Persistent Volume Claim
Since a PVC effectively functions as the storage for a given pod, we may sometimes want to expand it.
7.1. Direct Claim Resizing
Of course, we rarely want to create another claim or PV and move data around.
So, we might be tempted to directly modify the PVC dynamically:
$ kubectl edit pvc/hostpath-vol0-claim
[...]
resources:
requests:
storage: 2Gi
storageClassName: xclass
[...]
error: persistentvolumeclaims "hostpath-vol0-claim" could not be patched: persistentvolumeclaims "hostpath-vol0-claim" is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
You can run `kubectl replace -f /tmp/kubectl-edit-3476441128.yaml` to try this update again.
In this case, we attempt to change the storage of hostpath-vol0-claim from 1Gi to 2Gi. However, the edit fails with a hint.
Yet, we might be able to get around this limitation.
7.2. Change Volume Reclaim Policy
To work around the PVC resizing restraints, we can recreate the PVC with a new size.
However, to protect the underlying PV and its data from deletion, we first ensure the PV reclaim policy is Retain:
$ kubectl edit pv/hostpath-vol0
[...]
persistentVolumeReclaimPolicy: Retain
[...]
persistentvolume/hostpath-vol0 edited
In particular, we can check and change persistentVolumeReclaimPolicy. Importantly, this can happen at the StorageClass level as well.
7.3. Delete Current Claim
At this point, because of the reclaim policy, we should be able to remove the PVC without losing information on the PV:
$ kubectl delete pvc/hostpath-vol0-claim
persistentvolumeclaim "hostpath-vol0-claim" deleted
Let’s verify the PV status:
$ kubectl get pv/hostpath-vol0
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
hostpath-vol0 7Gi RWO Retain Released default/hostpath-vol0-claim xclass 107m
As expected, hostpath-vol0 is now Released.
7.4. Recreate Claim With Changed Capacity
Now, we create a new resized claim:
$ kubectl apply --filename=<(echo '
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hostpath-vol0-claim-resized
namespace: default
spec:
storageClassName: xclass
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
')
persistentvolumeclaim/hostpath-vol0-claim-resized created
Let’s also verify the STATUS:
$ kubectl get pvc/hostpath-vol0-claim-resized
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hostpath-vol0-claim-resized Pending xclass 43s
Notably, the new PVC is now live, but Pending. To get it Bound, we should make the original PV available.
7.5. Make Volume Available
To change the status of a PV, we can edit it:
$ kubectl edit pv/hostpath-vol0
[...]
claimRef: null
capacity: [...]
persistentvolume/hostpath-vol0 edited
The only change we make is to set claimRef to null and delete all lines that define the claimRef. Another way to do this change is a patch:
$ kubectl patch pv/hostpath-vol0 --patch='{"spec":{"claimRef": null}}'
persistentvolume/hostpath-vol0 patched
Either way, we should see the PVC is now Bound:
$ kubectl get pvc/hostpath-vol0-claim-resized
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hostpath-vol0-claim-resized Bound hostpath-vol0 7Gi RWO xclass 6m25s
As expected, hostpath-vol0 is the PV of the new PVC.
8. Summary
In this article, we explored persistent volumes, persistent volume claims, and ways to resize both.
In conclusion, although there isn’t a direct mechanism to resize a PVC, we can use the underlying PV with a new PVC and preserve the data in the process.