Persistent Storage¶
Your cluster has one StorageClass, it is the default, and it works like any other.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: kubevirt # the default; omitting it works too
resources:
requests:
storage: 5Gi
That is the whole interface. The rest of this page is what is behind it, because two things about it are not ordinary: your quota is enforced from outside your cluster, and the volume outlives your worker nodes.
What your cluster ships with¶
Everything below is delivered and maintained by the platform. Editing or deleting it is not useful — it is re-applied.
| Object | Name | |
|---|---|---|
StorageClass |
kubevirt |
The default. reclaimPolicy: Delete. |
CSIDriver |
csi.kubevirt.io |
|
VolumeSnapshotClass |
kubevirt-csi-snapclass |
See Volume Snapshots. |
DaemonSet |
kubevirt-csi-node in kube-system |
The node half of the driver. |
| Snapshot controller and its CRDs | kube-system |
kubectl get storageclass,volumesnapshotclass,csidrivers
kubectl get ds kubevirt-csi-node -n kube-system
You hold no credential for the storage¶
The volume you get is on the platform's storage — a Ceph cluster, an enterprise array, whatever your provider runs. The half of the CSI driver that talks to it runs on the under cluster, not in your cluster. What runs in your cluster is only the node component that mounts what has already been provisioned.
So there is no Secret in your cluster holding array credentials, no backend endpoint to configure, and nothing to rotate.
It also means a compromise of your cluster — cluster-admin taken, a node escaped onto — yields no key to the storage system behind it.
The consequence to plan around is the other side of that: you cannot see the backend, so you cannot tell from inside your cluster whether a volume is stuck because of the storage system. That diagnosis belongs to your platform team.
The volume is not on your node¶
A claim becomes a volume on the under cluster, which is then hot-plugged as a disk onto whichever worker VM your pod was scheduled to.
Two things follow, and both are usually what you want:
Your data survives the loss of a worker. The bytes were never on the node. If a worker VM dies, the pod is rescheduled and the same volume is hot-plugged onto its new home.
Attachment follows the pod, not the cluster.
A volume nothing is mounting is attached to nothing.
That is the normal steady state, not a fault — a PersistentVolumeClaim that is Bound with no pod using it is entirely healthy.
Plan for ReadWriteOnce.
A hot-plugged disk is attached to one VM at a time, so a claim that several pods must write to at once is not something to assume — ask your platform team what the backend behind the class offers before designing around it.
Your quota is enforced when you create the claim¶
Storage is finite and shared, so your tenant has a fixed budget for it. That budget lives on the under cluster, where your platform team set it, and it is aggregated across every Environment your tenant owns — you cannot get more storage by asking for another Environment.
The part worth knowing is when you find out.
An over-quota PersistentVolumeClaim is rejected at creation, in the terminal you applied from, by an admission webhook that reads the quota on your behalf:
$ kubectl apply -f pvc.yaml
Error from server: error when creating "pvc.yaml": admission webhook denied the request: ...
This is deliberate, and it is the difference between a platform you can reason about and one you cannot.
The alternative — a claim accepted and then Pending forever — leaves you unable to tell an exhausted quota from a broken storage backend without opening a ticket.
Reading what is left¶
The quota object lives in your Environment on the under cluster, so this one is run with your Environment credentials rather than your cluster's kubeconfig:
$ kubectl get resourcequota -n dynamo-prod
NAME AGE REQUEST
capsule-dynamo-0 47h tenant-storage-class.storageclass.storage.k8s.io/requests.storage: 2Gi/100Gi
Capsule names it capsule-<tenant>-<index> and maintains it — it is not yours to edit, and raising it is a request to your platform team.
The resource key names the platform's own storage class, not the kubevirt class you write in your claims.
Read the number off this object rather than summing what your clusters asked for: the scope is the tenant, so volumes in every cluster in every Environment count against the same total.
A denial you should report rather than work around
The same webhook that checks your quota is reached over the network from your cluster's control plane.
If it is unreachable, PersistentVolumeClaim creation is denied rather than merely unmetered — the webhook is registered to fail closed.
A denial whose message is about reaching the webhook rather than about your quota is a platform-side problem. Send it to your platform team; there is nothing to fix from your side.
Growing a volume¶
Expansion is supported by the driver, so an ordinary edit to the claim is the path:
Two conditions, both worth checking before you rely on it:
# Expansion has to be enabled on the class
kubectl get storageclass kubevirt -o jsonpath='{.allowVolumeExpansion}{"\n"}'
The new size also has to fit your remaining quota — growing a volume consumes budget exactly like creating one, and is refused the same way.
Shrinking is not possible. Kubernetes does not support it, and neither does anything below.
Before you need the data to outlive the cluster¶
The kubevirt class is created with reclaimPolicy: Delete, and a class's reclaim policy is immutable.
That means the default is: your volume is deleted with the cluster that claimed it.
For most things that is right. For the ones where it is not, there are two mechanisms, and they solve different problems:
| Protects against | See | |
|---|---|---|
VolumeSnapshot |
Losing the data — a bad migration, a corrupted database, a deletion you regret. | Volume Snapshots |
Retain on the PersistentVolume |
Losing the cluster — the volume survives teardown and can be re-bound by a restore. | Cluster Backup & Restore |
Retain cannot be set on the kubevirt class, but it is mutable on an individual volume:
Do this while the volume exists. Nothing flips it for you, and it cannot be applied to a volume that has already been deleted.
If you want Retain to be the default for a whole class of workload rather than a patch you have to remember, ask your platform team for a second StorageClass — it is a manifest for your cluster, not a platform change.
When a claim does not come up¶
The first question is which of the two failures you have, because they point in opposite directions.
Rejected at kubectl apply — the webhook refused it. Your quota, or the webhook being unreachable. The message says which.
Accepted, then Pending — the claim is in, and provisioning on the under cluster has not completed.
A claim that stays Pending with no useful event is a platform-side diagnosis: the volume is a DataVolume in your Environment, and whether it reached its backend is something only your platform team can see.
Give them the claim's namespace, name and UID — the object on their side carries the same UID in its name.
See Also: Volume Snapshots · Exposing Workloads · Storage for the platform-side model