Dynamic provisioning needs a provisioner. Without one, or when the storage already exists, an administrator writes the PV.
A PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: archive-pv
spec:
capacity:
storage: 2Gi
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath:
path: /mnt/archivecapacity.storage how much it offers
accessModes RWO / ROX / RWX it supports
persistentVolumeReclaimPolicy Retain or Delete when the claim goes away
storageClassName "" means "no class": static binding only
hostPath / local / csi where the bytes arePVs are cluster-scoped. kubectl get pv needs no namespace.
Binding by hand
A claim with storageClassName: "" never triggers a provisioner. The binder looks for an Available PV where:
pv.storageClassName == "" class matches
pv.capacity >= pvc.request big enough
pv.accessModes ⊇ pvc.accessModes every mode the claim wants
pv.claimRef unset, or names this claimAmong the matches it picks the smallest. If nothing matches, the claim stays Pending and describe pvc shows:
Normal FailedBinding persistentvolume-controller no persistent volumes available for this claim and no storage class is setCreating a fitting PV later binds the claim on the next pass. Nothing has to be retried.
Released
Delete the claim of a Retain PV:
before archive-pv 2Gi RWO Retain Bound depths-abc/archive
after archive-pv 2Gi RWO Retain Released depths-abc/archiveThe data is still on disk. spec.claimRef still names the old claim, so a new claim cannot bind. To reuse the volume, clear the reference:
kubectl patch pv archive-pv -p '{"spec":{"claimRef":null}}'The phase returns to Available and the next matching claim binds to it, data included.
Pre-binding
A PV can name its claim in advance:
spec:
claimRef:
namespace: depths-abc
name: archiveOnly that claim can bind it. Use this when a specific dataset must land on a specific disk.