Shopbot can lose chat transcripts every time its Pod restarts if data sits only in the container filesystem or an emptyDir. Durable storage needs a life cycle that outlives any one Pod. Kubernetes separates what the app asks for from what the platform attaches.
Goals
- Request storage with a PersistentVolumeClaim (PVC).
- Understand how StorageClasses and CSI drivers create PersistentVolumes (PVs).
- Choose access modes and reclaim behaviour that match shopbot’s workload.
- Resize, snapshot, restore, and use claim templates for ephemeral volumes.
The three objects
Application manifests name a PersistentVolumeClaim. The claim asks for a size, access mode, and StorageClass. The cluster binds that claim to a PersistentVolume, which points at real storage. A StorageClass tells the CSI provisioner which technology and parameters to use. Developers rarely create PV objects by hand on modern clusters.
PVC
A PersistentVolumeClaim states size, access mode, and StorageClass. Ownership of the volume lives with the claim, not with one Pod.
kind: PersistentVolumeClaim
metadata:
name: shopbot-history
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
storageClassName: standard-ssdDynamic and static paths
Dynamic provisioning creates a PV when you create a PVC. The administrator installs CSI drivers and StorageClasses once; users only ship claims. Static provisioning is the admin publishing PVs up front—common for node-local disks or a fixed NFS export. One cluster can use both.
Default on most managed clusters. Create shopbot-history PVC; the StorageClass’s CSI driver allocates a disk and a PV. Delete the claim and the reclaim policy (often Delete) decides whether that disk disappears.
Claim storage for shopbot
A minimal claim for transcript history. Omit storageClassName only when the cluster defines a default class; otherwise set it explicitly.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shopbot-history
namespace: retail-chat
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: standard-ssdApply it, then list claims with kubectl get pvc -n retail-chat. Status may stay Pending until a Pod consumes the claim if the StorageClass uses WaitForFirstConsumer. Immediate binding provisions as soon as the PVC exists.
Mount the claim in the Pod
apiVersion: v1
kind: Pod
metadata:
name: shopbot-web
namespace: retail-chat
spec:
volumes:
- name: chat-history
persistentVolumeClaim:
claimName: shopbot-history
containers:
- name: shopbot
image: acme/shopbot:2.1
volumeMounts:
- name: chat-history
mountPath: /var/lib/shopbot/historyDelete the Pod and recreate it against the same claim: the files remain. Delete the claim only when you intend to release the volume. Multiple Pods may reference one claim if the access mode and backend allow it.
Pod shopbot-web
│ claimName: shopbot-history
▼
PVC shopbot-history ──bound──► PV pvc-…
│
▼
CSI disk / file shareAccess modes
Access modes describe concurrency, not filesystem permissions inside the container. Pick the mode your StorageClass actually supports—block disks are usually RWO; shared file services unlock RWX.
ReadWriteOnce
Common for block disks. Two shopbot replicas on different nodes will not both get the volume.
StorageClass and CSI
Inspect classes with kubectl get sc. Important fields include the provisioner name, reclaimPolicy (Delete vs Retain), volumeBindingMode, and allowVolumeExpansion. Several classes may share one CSI driver with different parameters (balanced SSD vs archival).
CSI drivers register as CSIDriver objects. Each driver typically runs a controller that provisions volumes and a DaemonSet-style node agent that attaches and mounts. You install drivers; you do not invent their low-level spec by hand.
kubectl get sc
kubectl get csidrivers
kubectl explain storageclassWhen the claim is deleted
Releasing a PVC does not always erase data the same way. The bound PV’s reclaim policy decides:
- Delete — common for dynamic volumes; PV and backend often go together.
- Retain — PV becomes Released; an admin must scrub or re-publish before another claim can use it.
Change reclaim policy on a PV before deleting a precious claim if the class default is Delete and you need a manual hold.
Static local volumes
Ultra-fast shopbot embedding caches sometimes need NVMe on a specific node. An admin creates a StorageClass with kubernetes.io/no-provisioner, then a PV that names a path and nodeAffinity so the scheduler places consumers on that node.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: shop-local-nvme
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: shop-node-a-nvme
spec:
capacity:
storage: 400Gi
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: shop-local-nvme
local:
path: /mnt/fast-shopbot
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["shop-node-a"]A PVC that asks for shop-local-nvme binds when the first Pod appears. After release under Retain, recreate or clear claimRef before reuse; deleting the PV object does not delete the files on disk—operators must clean the path themselves.
Grow, snapshot, restore
If the StorageClass allows expansion, raise spec.resources.requests.storage on the PVC. Some backends finish filesystem growth only after Pods using the claim restart. Shrinking is not supported.
Snapshots need a VolumeSnapshotClass and a CSI driver that implements snapshot APIs. You create a VolumeSnapshot that names the source PVC; the driver produces a cluster-scoped VolumeSnapshotContent. Restore by creating a new PVC whose dataSourceRef points at that snapshot (set apiGroup: snapshot.storage.k8s.io).
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: shopbot-history-snap-1
namespace: retail-chat
spec:
volumeSnapshotClassName: csi-snap
source:
persistentVolumeClaimName: shopbot-history
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shopbot-history-restore
namespace: retail-chat
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
dataSourceRef:
apiGroup: snapshot.storage.k8s.io
kind: VolumeSnapshot
name: shopbot-history-snap-1Cloning without a snapshot uses dataSourceRef with kind: PersistentVolumeClaim toward an existing claim—handy for seeding a read-only FAQ volume from a writer claim.
Ephemeral claims
An ephemeral volume embeds a PVC template in the Pod. Kubernetes creates a claim named like <pod>-<volume>, binds a PV, and deletes the claim when the Pod goes away. You get StorageClass features (size caps, snapshots where supported) without managing a long-lived PVC for scratch space.
volumes:
- name: scratch
ephemeral:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
storageClassName: standard-ssdPlatform note
On OpenShift or HyperShift-hosted bare-metal spokes, StorageClasses still come from the platform team (OCS/ODF, cloud CSI, local-storage operator). App YAML for shopbot stays portable: claim a class name, mount the claim, leave disk SKUs to the operators that own the cluster.
Mini-lab
kubectl get sc
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shopbot-history
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: shopbot-history-probe
spec:
volumes:
- name: chat-history
persistentVolumeClaim:
claimName: shopbot-history
containers:
- name: probe
image: alpine:3.20
command: ["sh", "-c", "echo ok > /data/ping.txt; sleep 3600"]
volumeMounts:
- name: chat-history
mountPath: /data
EOF
kubectl get pvc shopbot-history
kubectl exec shopbot-history-probe -- cat /data/ping.txtChecklist
- I request storage with a PVC, not a hard-coded disk ID in the Pod.
- I know when Pending means WaitForFirstConsumer versus a missing StorageClass.
- I can pick RWO / RWX / ROX / RWOP for shopbot’s concurrency needs.
- I understand Delete vs Retain before I remove a claim.
- I can expand a claim, snapshot when the driver allows it, and restore via dataSourceRef.
Where this leaves you
Shopbot’s history can survive Pod replacement. Next we return to configuration delivered as files—mounting ConfigMaps and Secrets—so settings and credentials travel beside this durable data path.