04 - Storage: PVs, PVCs, StorageClasses¶
Storage model¶
OpenShift inherits Kubernetes storage:
- PersistentVolume (PV) - cluster-scoped storage resource (an actual disk / NFS share / cloud volume).
- PersistentVolumeClaim (PVC) - namespace-scoped request for storage.
- StorageClass (SC) - template for dynamically provisioning PVs.
- CSI driver - Container Storage Interface driver that knows how to talk to the actual storage backend.
Access modes¶
| Mode | Meaning |
|---|---|
| ReadWriteOnce (RWO) | Mount on one node read-write |
| ReadOnlyMany (ROX) | Many nodes mount read-only |
| ReadWriteMany (RWX) | Many nodes mount read-write (requires NFS, CephFS, etc.) |
| ReadWriteOncePod (RWOP) | Single pod read-write |
EBS / Azure Disk / vSphere volumes are RWO. NFS / Ceph FS / Azure File can be RWX.
Reclaim policies¶
When a PVC is deleted, what happens to the PV / underlying storage:
- Delete - PV and underlying storage destroyed (typical for dynamic provisioning)
- Retain - PV kept; admin must manually clean up
- Recycle - deprecated, do not use
Storage classes (cloud common)¶
oc get storageclass
oc get sc # alias
oc describe sc gp3-csi
| Cloud / Platform | Common SC name | Backend |
|---|---|---|
| ROSA on AWS | gp3-csi, gp2-csi | EBS via AWS EBS CSI driver |
| ARO on Azure | managed-csi, managed-csi-encrypted | Azure Disk |
| OpenShift on vSphere | thin-csi | vSphere CSI |
| OCS / ODF | ocs-storagecluster-ceph-rbd, ocs-storagecluster-cephfs | Ceph RBD (RWO) / CephFS (RWX) |
Set default storage class¶
oc patch storageclass gp3-csi \
-p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# Remove default annotation from another
oc patch storageclass gp2-csi \
-p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
PersistentVolumeClaim¶
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: myapp
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
storageClassName: gp3-csi # optional; uses default if omitted
oc apply -f pvc.yaml
oc get pvc
oc describe pvc data
If the StorageClass exists and supports the requested access mode, a PV is dynamically provisioned and the PVC reaches Bound status.
Mounting a PVC in a pod / deployment¶
Method 1: oc set volume¶
oc set volume deploy/web \
--add --type=pvc \
--claim-name=data \
--mount-path=/var/lib/data \
--name=data-volume
Method 2: Deployment YAML¶
spec:
template:
spec:
containers:
- name: web
image: nginx
volumeMounts:
- name: data
mountPath: /var/lib/data
volumes:
- name: data
persistentVolumeClaim:
claimName: data
Quick PVC + mount (one command)¶
oc set volume deploy/web \
--add --type=pvc \
--claim-class=gp3-csi \
--claim-size=5Gi \
--mount-path=/var/lib/data
This both creates a PVC and mounts it. Useful for the exam.
Static provisioning (NFS example)¶
Sometimes you'll be given an NFS share to use directly.
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-shared
spec:
capacity:
storage: 10Gi
accessModes: [ReadWriteMany]
persistentVolumeReclaimPolicy: Retain
nfs:
server: nfs.example.com
path: /exports/shared
storageClassName: '' # empty = no class
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-shared
namespace: myapp
spec:
accessModes: [ReadWriteMany]
resources:
requests: { storage: 10Gi }
storageClassName: ''
volumeName: nfs-pv-shared # bind to specific PV
Volume expansion¶
If the StorageClass has allowVolumeExpansion: true, you can grow a PVC:
oc edit pvc data
# Change spec.resources.requests.storage from 5Gi to 10Gi
The CSI driver expands the underlying volume. The pod may need a restart for the filesystem to grow (oc rollout restart deploy/web).
oc get pvc data
# STATUS should be Bound; CAPACITY shows new size after expansion completes
ReadWriteMany (RWX) options¶
When you need many pods to write to the same volume:
| Backend | OpenShift StorageClass |
|---|---|
| NFS | Self-hosted or cloud NFS (FSx ONTAP, Azure Files) |
| CephFS | OpenShift Data Foundation ocs-storagecluster-cephfs |
| AWS EFS | AWS EFS CSI Driver Operator |
| Azure Files | azurefile-csi |
For most exam scenarios, RWO with EBS-class storage is what you'll use.
Common storage exam tasks¶
Provision 5 GB persistent storage for a deployment¶
oc set volume deploy/web \
--add --type=pvc \
--claim-class=gp3-csi \
--claim-size=5Gi \
--claim-mode=ReadWriteOnce \
--mount-path=/var/lib/data
Make gp3-csi the default storage class¶
oc patch storageclass gp3-csi \
-p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# Verify
oc get sc # one should have (default) suffix
Mount an existing PVC into a pod¶
spec:
containers:
- name: app
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: existing-pvc
Expand a PVC from 5 GB to 10 GB¶
oc edit pvc data
# change resources.requests.storage to 10Gi
# Or:
oc patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
oc rollout restart deploy/web # so pod sees new size
Set up an NFS mount with a specific PV¶
Apply the static PV + PVC YAML pair above.
Troubleshooting storage¶
PVC stuck in Pending¶
oc describe pvc <name>
Look at Events. Common causes:
- No StorageClass with name
<name>exists - StorageClass exists but doesn't support requested access mode (e.g., asking for RWX from EBS)
- Cluster CSI driver in
openshift-cluster-csi-driversis degraded
Pod stuck in Pending because PVC unbound¶
oc describe pod <name> shows volume not yet bound. Fix the PVC.
Pod stuck in ContainerCreating due to mount failure¶
oc describe pod <name>
Common causes:
- SELinux context wrong on bind mount (use
:Zif running locally) - NFS server unreachable
- AccessMode not satisfied by underlying volume
Filesystem full despite PVC expanded¶
The PVC may show new capacity but the filesystem inside the pod hasn't been resized. Restart the pod:
oc rollout restart deploy/web
Verification¶
After storage changes:
oc get pvcshowsBoundstatus with expected sizeoc get pvshows the underlying PVoc rsh <pod>thendf -h /mountpointshows expected sizeoc rsh <pod>thentouch /mountpoint/testcreates a file (write works)- After pod restart (
oc rollout restart), the file persists (data survives)