Lab 35 - Static and dynamic storage
Lab 35 - Static and dynamic storage
Goal: replace hand-written PersistentVolumes with self-service: a CSI driver and a StorageClass. Chapter: PV, PVC and StorageClasses
On Day 2 (Lab 10) you wrote three static NFS PVs by hand. Check they are still there:
kubectl get pv # pv-storage1..3
Dynamic provisioning with the NFS CSI driver
Everything so far was static: you wrote a PV for every share, by hand. That does not scale - ten teams asking for volumes means ten PVs and a ticket each. A CSI driver plus a StorageClass turns that into self-service: the PVC asks, the driver creates.
A word on names. The nfs: volume type you used above is the in-tree NFS
plugin - compiled into Kubernetes itself. In-tree storage plugins are frozen and
being removed; every driver now lives out of tree and speaks CSI. So there is
no such thing as an “in-tree CSI driver”: what you install below,
csi-driver-nfs, is the out-of-tree replacement for the in-tree plugin. Same
NFS server, same exports - different plumbing, and it is the one with a future.
# 1 - install the driver (a DaemonSet + a controller Deployment, nothing exotic)
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm repo update
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs \
--namespace kube-system --version 4.12.0
kubectl get pods -n kube-system -l app.kubernetes.io/name=csi-driver-nfs -w # Ctrl-C when Running
# 2 - what did that add?
kubectl get csidrivers
kubectl get ds,deploy -n kube-system | grep nfs
Look at the shape of it: a DaemonSet (the node plugin, which does the actual
mounting - so it must be on every node, exactly as in Lab 09 (DaemonSets)) and a
Deployment (the controller, which creates and deletes volumes). The nodes
still need nfs-common, which you installed in step 1 - CSI does not replace
the kernel mount, it drives it.
The StorageClass
# 3 -
cd ~
cat > sc-nfs.yaml <<'YAML'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: support01
share: /storage3
reclaimPolicy: Retain
volumeBindingMode: Immediate
allowVolumeExpansion: true
mountOptions:
- nfsvers=4.1
YAML
kubectl apply -f sc-nfs.yaml
kubectl get sc
Read it as a recipe the cluster can follow without you:
| Field | Meaning |
|---|---|
provisioner | which CSI driver handles claims for this class |
parameters | driver-specific: the NFS server and the parent share |
reclaimPolicy | what happens to the PV when its PVC is deleted - Retain here |
volumeBindingMode | Immediate provisions at once; WaitForFirstConsumer waits for a Pod |
The reclaim policy is set on the class, and every PV it creates inherits it.
With Delete - the usual default - deleting a PVC deletes the data. Retain is
what you want for anything you would be sad to lose.
Free the export first: /storage3 is about to become the parent directory the
driver creates subdirectories in, so it must not be claimed by the static PV.
kubectl delete pv pv-storage3 if it is still Available.
Self-service: a PVC with no PV
# 4 - remove the static PV for /storage3, then just ask
kubectl delete pv pv-storage3 --ignore-not-found
cat > pvc-dyn.yaml <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dyn-data
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 300Mi
storageClassName: nfs-csi
YAML
kubectl apply -f pvc-dyn.yaml
kubectl get pvc dyn-data
kubectl get pv
No PV existed and now one does. Its name is pvc-<uuid>, its
RECLAIM POLICY is Retain, and it was created by the driver - not by you.
That is the whole difference between the first half of this lab and this one.
# 5 - where did it actually put the data?
PV=$(kubectl get pvc dyn-data -o jsonpath='{.spec.volumeName}')
echo $PV
kubectl get pv $PV -o jsonpath='{.spec.csi.volumeAttributes}{"\n"}'
ssh student@support01 'ls -l /storage3'
The driver created a subdirectory inside /storage3 named after the volume.
One export, many volumes - that is how a single NFS share serves a whole cluster.
# 6 - use it
cat > dynpod.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: dyn-writer
spec:
containers:
- name: box
image: busybox:1.36
command: ["sh", "-c", "echo \"dynamic volume, $(date)\" >> /data/log.txt; sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: dyn-data
YAML
kubectl apply -f dynpod.yaml
kubectl exec dyn-writer -- cat /data/log.txt
The Pod spec is identical to the static one. Applications never know or care whether the volume was provisioned by hand or by a driver - they reference a claim, and that is the point of the abstraction.
Retain in action: the data outlives the claim
# 7 - throw away the Pod *and* the claim
kubectl delete pod dyn-writer
kubectl delete pvc dyn-data
kubectl get pv
The PV is Released, not gone. With reclaimPolicy: Delete the driver would
have deleted the subdirectory on the NFS server by now; with Retain it left
everything alone and simply refuses to hand the volume to anybody else.
# 8 - prove the data is still on the server
ssh student@support01 'ls -l /storage3; find /storage3 -name log.txt -exec cat {} \;'
# 9 - hand the same volume to a new claim
kubectl patch pv $PV -p '{"spec":{"claimRef":null}}'
kubectl get pv $PV # Available again
cat > pvc-reuse.yaml <<YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dyn-data-v2
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 300Mi
storageClassName: nfs-csi
volumeName: $PV
YAML
kubectl apply -f pvc-reuse.yaml
kubectl get pvc dyn-data-v2
volumeName is what stops the provisioner from creating a new volume: the
claim names the PV it wants, so this is a static binding to a dynamically
created volume. Clearing claimRef is the manual step Retain deliberately
forces on you - it is the moment a human confirms the old data may be reused.
# 10 - a new pod, the old data
sed 's/dyn-writer/dyn-reader/; s/claimName: dyn-data$/claimName: dyn-data-v2/' dynpod.yaml \
| kubectl apply -f -
kubectl exec dyn-reader -- cat /data/log.txt # the line from step 13 is there
Two lines now: one written before you deleted the claim, one after. The Pod is new, the PVC is new, the data survived both.
Static or dynamic?
| Static PV | StorageClass + CSI | |
|---|---|---|
| Who creates the volume | you, per volume | the driver, on demand |
| Scales to many teams | no | yes |
| Needs a driver | no | yes |
| Good for | a fixed appliance, an existing share | everything else |
Production clusters run both: a default StorageClass for self-service, plus a handful of hand-written PVs for storage that already exists and must be mounted exactly as it is.
Clean up
kubectl delete pod dyn-reader --ignore-not-found
kubectl delete pvc dyn-data-v2 --ignore-not-found
kubectl patch pv $PV -p '{"spec":{"claimRef":null}}' 2>/dev/null
kubectl delete pv $PV --ignore-not-found # Retain means this is your job
ssh student@support01 'sudo rm -rf /storage3/pvc-*'
kubectl get pv,pvc
Retain means nobody cleans up after you. Released PVs and their data sit there
until someone deletes them - which is the safety you asked for and the tidiness
bill that comes with it.
Discovery
InfodiscoverTask A. Make nfs-csi the default StorageClass, then create a PVC
without storageClassName. Expected: it binds.
Keep
Leave the CSI driver and the nfs-csi class installed - Labs 36, 39, 41 and
42 need them.