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ě.

kubectl create -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: toobig
spec:
  accessModes: ["ReadWriteMany"]
  resources: { requests: { storage: 5Gi } }
  storageClassName: ""
YAML
kubectl get pvc toobig
kubectl describe pvc toobig | tail -5

no persistent volumes available for this claim and no storage class is set. Se StorageClass by cluster volume vytvořil; tady nemá jak.

Úkol B. Vypište na jeden řádek, ke kterému PV je každý PVC navázaný a který claim vlastní každý PV.

kubectl get pvc -o custom-columns=PVC:.metadata.name,VOLUME:.spec.volumeName,SIZE:.status.capacity.storage
kubectl get pv -o custom-columns=PV:.metadata.name,STATUS:.status.phase,CLAIM:.spec.claimRef.name

Úkol C. Smažte PVC web-data a předpovězte stav PV. Pak vysvětlete, jak byste ten PV znovu zpřístupnili.

kubectl delete pod writer reader
kubectl delete pvc web-data
kubectl get pv

PV přejde do Released, ne Available: reclaim policy je Retain, takže data zůstanou a volume nedostane nikdo jiný. Pro opětovné použití zrušíte starou vazbu:

kubectl patch pv pv-storage1 -p '{"spec":{"claimRef":null}}'
kubectl get pv

S persistentVolumeReclaimPolicy: Delete by PV zmizel i s daty.

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ří.

Poznámka

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:

PoleVýznam
provisionerkterý CSI driver obsluhuje claimy této třídy
parametersspecifické pro driver: NFS server a nadřazený share
reclaimPolicyco se stane s PV po smazání jeho PVC – tady Retain
volumeBindingModeImmediate 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.

Tip

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ý PVStorageClass + CSI
Kdo volume vytvořívy, na každý zvlášťdriver, na požádání
Škáluje na víc týmůneano
Potřebuje driverneano
Vhodné propevnou appliance, existující sharevš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
Varování

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.