Lab 18 – Perzistentní úložiště na NFS
Lab 18 – Perzistentní úložiště na NFS
Cíl: dát podu úložiště, které ho přežije – bez StorageClass a bez CSI driveru, přesně jak vypadá on-premise cluster. Kapitola: Perzistentní úložiště
support01 exportuje přes NFS /storage1, /storage2 a /storage3. Dynamické
provisioning tu není, takže PersistentVolume napíšete vy.
Příprava uzlů
# 1 – každý uzel, který připojuje NFS, potřebuje klientské nástroje
for n in worker01 worker02 worker03; do
ssh student@$n 'dpkg -l nfs-common >/dev/null 2>&1 && echo "$(hostname): ok" || sudo apt-get install -y nfs-common'
done
# 2 – ověřte export z uzlu, nejdřív mimo Kubernetes
ssh student@worker01 'sudo mkdir -p /mnt/t && sudo mount -t nfs support01:/storage1 /mnt/t && df -h /mnt/t && sudo umount /mnt/t'
Vždy si mount vyzkoušejte ručně, než začnete vinit Kubernetes. Pod zaseknutý
v ContainerCreating s chybou NFS obvykle znamená chybějící nfs-common,
špatnou cestu exportu nebo práva exportu.
PersistentVolume
# 3 – jeden PV na každý export
cd ~
cat > pvs.yaml <<'YAML'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-storage1
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteMany"]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: support01
path: /storage1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-storage2
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteMany"]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: support01
path: /storage2
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-storage3
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteMany"]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: support01
path: /storage3
YAML
kubectl apply -f pvs.yaml
kubectl get pv
Všechny tři jsou Available – existují a nepatří nikomu. PV jsou cluster-scoped:
-n u nich nedává smysl.
storageClassName: "" znamená „žádná třída“. Záleží na tom: PVC, který si také
řekne o žádnou třídu, se na ně naváže; PVC žádající o třídu ne.
Claim
# 4 –
cat > pvc.yaml <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: web-data
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 500Mi
storageClassName: ""
YAML
kubectl apply -f pvc.yaml
kubectl get pvc,pv
Claim si řekl o 500Mi a dostal celý 1Gi volume. Vazba je výhradní a PV se nikdy nedělí – přebytek prostě zůstane nevyužitý. Všimněte si, který PV si vybral; vybíral control plane, ne vy.
Použití
# 5 – pod, který na volume zapisuje
cat > pvpod.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: box
image: busybox
command: ["sh", "-c", "echo \"written by $(hostname) at $(date)\" >> /data/log.txt; sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: web-data
YAML
kubectl apply -f pvpod.yaml
kubectl get pod writer -o wide
kubectl exec writer -- cat /data/log.txt
Pod odkazuje na claim, nikdy na volume. Právě tahle nepřímost dělá manifest přenositelným: stejná specifikace podu funguje tady na NFS i v cloudu na EBS.
# 6 – data přežijí pod
kubectl delete pod writer
kubectl apply -f pvpod.yaml
kubectl exec writer -- cat /data/log.txt # už dva řádky
# 7 – a je opravdu sdílené (ReadWriteMany)
kubectl run reader --image busybox --restart=Never --overrides='{"spec":{"containers":[{"name":"reader","image":"busybox","command":["sleep","3600"],"volumeMounts":[{"name":"d","mountPath":"/data"}]}],"volumes":[{"name":"d","persistentVolumeClaim":{"claimName":"web-data"}}]}}'
kubectl get pod reader -o wide # možná jiný uzel
kubectl exec reader -- cat /data/log.txt # tentýž soubor
Dva pody, možná dva uzly, jeden soubor. To znamená RWX a bloková zařízení to neumí.
Objevovací část
InfodiscoveryÚkol A. Vytvořte PVC žádající o 5Gi. Očekávaný výsledek: zůstane Pending.
Zjistěte z clusteru, ne z téhle stránky, proč přesně.
Úkol B. Vypište na jeden řádek, ke kterému PV je každý PVC navázaný a který claim vlastní každý PV.
Úkol C. Smažte PVC web-data a předpovězte stav PV. Pak vysvětlete, jak
byste ten PV znovu zpřístupnili.
Dynamické provisioning s NFS CSI driverem
Všechno dosud bylo statické: na každý share jste ručně napsali PV. To se nedá udržet – deset týmů chce volumes a máte deset PV a deset tiketů. CSI driver plus StorageClass z toho udělá samoobsluhu: PVC si řekne, driver vytvoří.
K názvosloví. Typ volume nfs:, který jste použili výše, je in-tree NFS
plugin – zakompilovaný přímo do Kubernetes. In-tree storage pluginy jsou zmrazené
a odstraňují se; každý dnešní driver žije mimo strom a mluví CSI. „In-tree
CSI driver“ tedy neexistuje: to, co teď nainstalujete, csi-driver-nfs, je
out-of-tree náhrada in-tree pluginu. Stejný NFS server, stejné exporty – jiná
instalatérská práce, a tahle má budoucnost.
# 8 – instalace driveru (DaemonSet + Deployment s kontrolerem, nic exotického)
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 až bude Running
# 9 – co to přidalo?
kubectl get csidrivers
kubectl get ds,deploy -n kube-system | grep nfs
Všimněte si tvaru: DaemonSet (node plugin, který skutečně připojuje – proto
musí být na každém uzlu, přesně jako v labu 12) a Deployment (kontroler,
který volumes vytváří a maže). Uzly pořád potřebují nfs-common z kroku 1 – CSI
kernelový mount nenahrazuje, jen ho řídí.
StorageClass
# 10 –
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
Čtěte to jako recept, který cluster zvládne bez vás:
| Pole | Význam |
|---|---|
provisioner | který CSI driver obsluhuje claimy této třídy |
parameters | specifické pro driver: NFS server a nadřazený share |
reclaimPolicy | co se stane s PV po smazání jeho PVC – tady Retain |
volumeBindingMode | Immediate vytvoří hned; WaitForFirstConsumer počká na pod |
Reclaim policy se nastavuje na třídě a zdědí ji každý PV, který vytvoří.
S Delete – obvyklou výchozí hodnotou – smazání PVC smaže data. Retain chcete
u všeho, čeho by vám bylo líto.
Nejdřív uvolněte export: /storage3 se stane nadřazeným adresářem, ve kterém
driver vytváří podadresáře, takže ho nesmí držet statický PV.
kubectl delete pv pv-storage3, pokud je ještě Available.
Samoobsluha: PVC bez PV
# 11 – odstraňte statický PV pro /storage3 a prostě si řekněte
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
Žádný PV neexistoval a teď existuje. Jmenuje se pvc-<uuid>, jeho
RECLAIM POLICY je Retain a vytvořil ho driver – ne vy. To je celý rozdíl
mezi první polovinou labu a touhle.
# 12 – kam ta data vlastně uložil?
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'
Driver vytvořil podadresář uvnitř /storage3 pojmenovaný podle volume.
Jeden export, mnoho volumes – takhle jediný NFS share obslouží celý cluster.
# 13 – použijte ho
cat > dynpod.yaml <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: dyn-writer
spec:
containers:
- name: box
image: busybox
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
Specifikace podu je totožná se statickou variantou. Aplikace vůbec neví, jestli volume vznikl ručně, nebo driverem – odkazuje na claim, a přesně o to v té abstrakci jde.
Retain v praxi: data přežijí claim
# 14 – zahoďte pod *i* claim
kubectl delete pod dyn-writer
kubectl delete pvc dyn-data
kubectl get pv
PV je Released, ne pryč. S reclaimPolicy: Delete by driver podadresář na NFS
serveru už smazal; s Retain nechal všechno být a jen odmítá volume vydat
někomu jinému.
# 15 – ověřte, že data na serveru pořád jsou
ssh student@support01 'ls -l /storage3; find /storage3 -name log.txt -exec cat {} \;'
# 16 – předejte tentýž volume novému claimu
kubectl patch pv $PV -p '{"spec":{"claimRef":null}}'
kubectl get pv $PV # zase Available
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 je to, co zabrání provisioneru vytvořit nový volume: claim
pojmenuje PV, který chce, takže jde o statickou vazbu na dynamicky vytvořený
volume. Vyčištění claimRef je ruční krok, který Retain záměrně vynucuje – je
to okamžik, kdy člověk potvrdí, že se stará data smějí použít.
# 17 – nový pod, stará 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 # řádek z kroku 13 tam je
Teď jsou tam dva řádky: jeden zapsaný před smazáním claimu a jeden po něm. Pod je nový, PVC je nové, data přežila obojí.
Staticky, nebo dynamicky?
| Statický PV | StorageClass + CSI | |
|---|---|---|
| Kdo volume vytvoří | vy, na každý zvlášť | driver, na požádání |
| Škáluje na víc týmů | ne | ano |
| Potřebuje driver | ne | ano |
| Vhodné pro | pevnou appliance, existující share | všechno ostatní |
Produkční clustery provozují obojí: výchozí StorageClass pro samoobsluhu a pár ručně psaných PV pro úložiště, které už existuje a musí se připojit přesně tak, jak je.
Úklid CSI části
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 znamená, že je to na vás
ssh student@support01 'sudo rm -rf /storage3/pvc-*'
kubectl get pv,pvc
Retain znamená, že po vás nikdo neuklidí. Uvolněné PV a jejich data tam zůstanou,
dokud je někdo nesmaže – což je ta bezpečnost, o kterou jste si řekli, a účet za
pořádek, který k ní patří.
Úklid celého labu
kubectl delete pod writer reader --ignore-not-found
kubectl delete pvc web-data toobig --ignore-not-found
# vraťte pv-storage3 – lab 19 potřebuje tři statické PV
kubectl apply -f pvs.yaml
for p in pv-storage1 pv-storage2 pv-storage3; do
kubectl patch pv $p -p '{"spec":{"claimRef":null}}' 2>/dev/null
done
kubectl get pv # tři, všechny Available
CSI driver i StorageClass nfs-csi nechte nainstalované – lab 22 je může použít
místo ručně psaných PV, pokud chcete vidět operátor s dynamickým provisioningem.