Einführung
Du hast die Anforderung, dass Deine Anwendung für eine Lastverteilung über mehrere Nodes skalieren muss, aber Zugriff auf ein gemeines PVC benötigt? Zu diesem Zweck benötigst Du ein PVC welches RWX-fähig ist. Im Rahmen unserer Managed Kubernetes Cluster ist es möglich, einen CSI Cinder Blockstorage zu erstellen. Dieser ist jedoch wegen technischer Limitierungen nur ReadWriteOnce (RWO) fähig. Wie wir mittels eines Workarounds dennoch ein RWX-fähiges PVC auf die Beine stellen, zeigen wir Dir hier an einen Beispiel aus der Praxis! Was PVCs sind, wird im Tutorial Persistente Volumes in Kubernetes erstellen erklärt, auf welchem das vorliegende Tutorial aufbaut. Nehmen wir beispielsweise einen Webserver, welcher auf seinen PVC HTML-Seiten, Bilder, JS etc. gespeichert hat. Diese Dateien sollen einmalig hochgeladen und allen Instanzen des Webservers gleichzeitig zur Verfügung gestellt werden. Um die Last besser zu verteilen, wird das Deployment entsprechend skaliert. Somit laufen mehrere Pods des gleichen Types auf unterschiedlichen Servern. Damit alle Pods – über Hostgrenzen hinweg – Zugriff auf die Dateien haben, erstellen wir eine RWX Storageclass mit den nfs-ganesha-server-and-external-provisioner. Als Grundlage hierfür dient ein NWS Managed Kubernetes Cluster. Nach Abschluss des Workarounds haben wir die Möglichkeit, PVCs zu erstellen, welche durch mehrere Pods gleichzeitig gelesen und beschrieben werden können.
Ein Hinweis vorweg: das beschriebene Setup ist nicht HA fähig!
Voraussetzungen
Das Tutorial setzt Folgendes voraus:
- NWS Managed Kubernetes Cluster
- kubectl
- helm
Weitere Informationen zu diesen Themen findest Du bei uns in den NWS Docs
Einrichtung
Mit folgendem Befehl fügen wir das Helm Repository hinzu, welches uns den nfs-ganesha-server-and-external-provisioner bereitstellt, nachfolgend NFS Provisioner genannt.
$ helm repo add nfs-ganesha-server-and-external-provisioner https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner/
Danach können wir sofort mit der Installation loslegen. Zu beachten sind die Einstellungen, die dem –set Parameter folgen. Diese bewirken:
-
persistence.enabled=true Dieser Parameter stellt sicher, dass unsere Daten auf einem persistenten Volume gespeichert werden und auch nach Neustart des Pods noch vorhanden sind.
-
persistence.storageClass=standard Hier wird angegeben, dass für die persistenten Daten die Storageclass "standard" genutzt werden soll.
-
persistence.size=200Gi Dieser Parameter gibt an, wie groß das PVC sein soll, welches der NFS Provisioner für die Dateien holt.
Zu beachten ist, dass die PVC Größe, welche mit persistence.size angegeben wird, für alle NFS PVCs geteilt wird, welche über den NFS Provisioner bezogen werden. Es gibt noch viele weitere Konfigurationsoptionen mit denen der NFS Provisioner an die eigenen Bedürfnisse angepasst werden kann. Diese findest du hier
$ helm install nfs-server nfs-ganesha-server-and-external-provisioner/nfs-server-provisioner --set persistence.enabled=true,persistence.storageClass=standard,persistence.size=200Gi
Wenn der NFS Provisioner erfolgreich installiert werden konnte, erscheint eine Ausgabe wie folgt:
NAME: nfs-server LAST DEPLOYED: Mon May 22 14:41:58 2023 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The NFS Provisioner service has now been installed. A storage class named 'nfs' has now been created and is available to provision dynamic volumes. You can use this storageclass by creating a `PersistentVolumeClaim` with the correct storageClassName attribute. For example: --- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-dynamic-volume-claim spec: storageClassName: "nfs" accessModes: - ReadWriteOnce resources: requests: storage: 100Mi
Nun sehen wir uns den eben erstellten NFS Provisioner Pod an und prüfen ob dieser läuft:
kubectl get pods NAME READY STATUS RESTARTS AGE nfs-server-nfs-server-provisioner-0 1/1 Running 0 36m
Und natürlich auch die dazugehörige Storageclass für die automatische Bereitstellung von NFS RWX PVCs. Diesen können wir nun verwenden, um dynamisch PVCs vom Typ RWX zu erstellen und zu nutzen.
$ kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE encrypted cinder.csi.openstack.org Delete Immediate true 3h22m encrypted-high-iops cinder.csi.openstack.org Delete Immediate true 3h22m high-iops cinder.csi.openstack.org Delete Immediate true 3h22m nfs cluster.local/nfs-server-nfs-server-provisioner Delete Immediate true 39m nws-storage cinder.csi.openstack.org Delete Immediate true 3h22m standard (default) cinder.csi.openstack.org Delete Immediate true 3h22m
Webserver Beispiel
Wir haben jetzt ein PVC mit der Eigenschaft RWX welches wir dynamisch provisionieren können. Nun sehen wir uns konkret an, wie dieser in ein Deployment eingebunden wird. Wir legen zwei Dateien an; eine für unser Deployment des Webservers und eine für das RWX PVC:
apiVersion: apps/v1 kind: Deployment metadata: labels: app: webserver name: webserver spec: replicas: 1 selector: matchLabels: app: webserver template: metadata: creationTimestamp: null labels: app: webserver spec: containers: - image: nginx:latest name: nginx volumeMounts: - mountPath: /files name: files volumes: - name: files persistentVolumeClaim: claimName: nfs-pvc
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi storageClassName: nfs
Unser Deployment gibt an, dass wir vom NGiNX Container genau eine Replika laufen lassen wollen. Dieses bindet das dynamisch erstellte NFS PVC unter /files ein. Jetzt müssen wir die Definitionen noch mittels kubectl an Kubernetes verfüttern:
$ kubectl apply -f nfs-pvc.yaml nginx-deployment.yaml persistentvolumeclaim/nfs-pvc created deployment.apps/webserver created
Wie wir sehen, läuft ein Webserver Pod:
$ kubectl get pods NAME READY STATUS RESTARTS AGE nfs-server-nfs-server-provisioner-0 1/1 Running 0 54m webserver-5486dd9cf5-hfhnd 1/1 Running 0 114s
Nachdem dieser aber bei weitem nicht für unsere Last ausreicht, erweitern wir das Deployment auf 4 Replikationen:
$ kubectl scale deployment webserver --replicas=4 deployment.apps/webserver scaled
Es empfiehlt sich ein kleiner Check und siehe da, alle 4 Pods laufen fröhlich vor sich hin:
$ kubectl get pods NAME READY STATUS RESTARTS AGE nfs-server-nfs-server-provisioner-0 1/1 Running 0 55m webserver-5486dd9cf5-hfhnd 1/1 Running 0 3m9s webserver-5486dd9cf5-nh9fl 1/1 Running 0 18s webserver-5486dd9cf5-ss27f 1/1 Running 0 18s webserver-5486dd9cf5-xl2lj 1/1 Running 0 18s
Jetzt prüfen wir noch, ob das NFS auch zwischen den Pods richtig funktioniert. Ein erster Befehl zeigt uns, dass /files leer ist. Mithilfe eines zweiten Befehls erstellen wir die Datei nfs-is-rwx. An der dritten Ausgabe können wir erkennen, dass wir erfolgreich waren. Wir haben in einem Pod erstellt und die Datei war sofort in einem anderen Pod vorhanden.
$ kubectl exec webserver-5486dd9cf5-hfhnd -- ls /files $ kubectl exec webserver-5486dd9cf5-nh9fl -- touch /files/nfs-is-rwx $ kubectl exec webserver-5486dd9cf5-xl2lj -- ls /files nfs-is-rwx
Zusammenfassung
Du hast nun einen NFS Server eingerichtet, welcher im Hintergrund ein CSI Cinder Blockstorage nutzt. Der NFS Server nutzt das Block Storage im Hintergrund um via NFS ein RWX PVC für Deine Pods bereitzustellen. Dadurch haben wir die technische Limitierung eines CSI Cinder Blockdevices umgangen und Du kannst Deinen NWS Managed Kubernetes Cluster fortan für mehr Anwendungsfälle nutzen.