ReadWriteMany (RWX) mit dem NFS Ganesha Provisioner

ReadWriteMany (RWX) mit dem NFS Ganesha Provisioner

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 \
--set persistence.storageClass=standard,persistence \
--set 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: 

# nginx-deployment.yaml
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 
# nfs-pvc.yaml
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.