ADMIN

2020

12

2020-11-29T12:00:00

Applikationsmanagement

PRAXIS

032

Storage

Cluster

Kubernetes

Cloudnativer Storage mit OpenEBS

In den Wolken zuhause

von Oliver Frommel

Veröffentlicht in Ausgabe 12/2020 - PRAXIS

Storage stellt für Container-Cluster eine große Herausforderung dar. Für Kubernetes gibt es einige Ansätze, von denen wir uns das Open-Source-Projekt OpenEBS genauer angeschaut haben. Mit der Software steht eine cloudnative Storage-Umgebung bereit, die Block Devices einzelner Nodes im Kubernetes-Cluster zur Verfügung stellt. Den Betrieb unterstützt ein Kubernetes-Operator, der auch Features wie Snapshots, Backup und Restore unterstützt.

Container zustandslos (stateless) zu betreiben, schien am Anfang der beste Ansatz zu sein. Das vereinfacht die Sache natürlich ungemein. Dadurch lassen sich containerbasierte Anwendungen skalieren, indem auf zusätzlichen Hosts weitere Container starten. Gibt es mit einem Container Probleme, lässt sich dieser einfach beenden und auf einem anderen Node neu starten – soweit die Theorie.
In der Praxis können Anwender mit den zustandslosen Containern leider weniger erreichen, als die meisten sich wünschen. Und so hat beispielsweise die Container-Plattform Kubernetes in der Anfangszeit von Release zu Release entsprechend neue Features hinzugewonnen, etwa die Stateful Sets oder das Storage-Interface für persistente Volumes. Der Hersteller implementierte auch das Container Storage Interface (CSI), das die Cloud Native Computing Foundation (CNCF) zum Standard erhoben hat. Neben den mitgelieferten Möglichkeiten für persistente Volumes gibt es in der Kubernetes-Welt zahlreiche Anbieter von Storage-Produkten, die auf Container-Cluster zugeschnitten sind. Läuft Kubernetes auf einer der großen Cloudplattformen, gibt es typischerweise ein Interface zum entsprechenden Storage-Service, etwa GCEPersistentDisk (Google) oder AWS-ElasticBlockStore (Amazon). Dann besteht die Möglichkeit, klassische Storage-Protokolle wie NFS, iSCSI und FibreChannel zu verwenden oder moderne verteilte Dateisysteme wie Ceph oder GlusterFS.
Cloudnativer Storage
Eine weitere Gruppe von Storage-Lösungen versucht, das Problem bei der Wurzel zu packen, und entwickelt von Grund auf Technologien, die mit den Besonderheiten einer Container-Landschaft umzugehen wissen. Beispiele für diese Cloud-Native-Storage-Ansätze sind Rook, das wir schon in einem früheren Heft [1] vorgestellt haben, Portworx, StorageOS oder OpenEBS (Elastic Block Storage ) [2], das wir uns im Folgenden genauer ansehen.
Container zustandslos (stateless) zu betreiben, schien am Anfang der beste Ansatz zu sein. Das vereinfacht die Sache natürlich ungemein. Dadurch lassen sich containerbasierte Anwendungen skalieren, indem auf zusätzlichen Hosts weitere Container starten. Gibt es mit einem Container Probleme, lässt sich dieser einfach beenden und auf einem anderen Node neu starten – soweit die Theorie.
In der Praxis können Anwender mit den zustandslosen Containern leider weniger erreichen, als die meisten sich wünschen. Und so hat beispielsweise die Container-Plattform Kubernetes in der Anfangszeit von Release zu Release entsprechend neue Features hinzugewonnen, etwa die Stateful Sets oder das Storage-Interface für persistente Volumes. Der Hersteller implementierte auch das Container Storage Interface (CSI), das die Cloud Native Computing Foundation (CNCF) zum Standard erhoben hat. Neben den mitgelieferten Möglichkeiten für persistente Volumes gibt es in der Kubernetes-Welt zahlreiche Anbieter von Storage-Produkten, die auf Container-Cluster zugeschnitten sind. Läuft Kubernetes auf einer der großen Cloudplattformen, gibt es typischerweise ein Interface zum entsprechenden Storage-Service, etwa GCEPersistentDisk (Google) oder AWS-ElasticBlockStore (Amazon). Dann besteht die Möglichkeit, klassische Storage-Protokolle wie NFS, iSCSI und FibreChannel zu verwenden oder moderne verteilte Dateisysteme wie Ceph oder GlusterFS.
Cloudnativer Storage
Eine weitere Gruppe von Storage-Lösungen versucht, das Problem bei der Wurzel zu packen, und entwickelt von Grund auf Technologien, die mit den Besonderheiten einer Container-Landschaft umzugehen wissen. Beispiele für diese Cloud-Native-Storage-Ansätze sind Rook, das wir schon in einem früheren Heft [1] vorgestellt haben, Portworx, StorageOS oder OpenEBS (Elastic Block Storage ) [2], das wir uns im Folgenden genauer ansehen.
OpenEBS ist ein Open-Source-Produkt der Firma MayaData, die für Unternehmenskunden auch entsprechende Angebote mit Support bereithält. Der Firmengründer Evan Powell hat für die Technologie den Begriff "Container Attached Storage" geprägt, wobei der wesentliche Unterschied zu sogenannten Legacy-Systemen darin besteht, dass alle Managementkomponenten des Storage-Layers als Pods im Kubernetes-Cluster selbst laufen. Das ist natürlich kein Alleinstellungsmerkmal von Open­EBS, denn auch andere Cloud-Native-Angebote wie Rook arbeiten so, aber bei klassischen Storage-Anbietern oder den Cloud-Providern läuft das Storage-Management eben außerhalb des Clusters. Ein wesentlicher Unterschied von OpenEBS etwa zu Rook liegt darin, wie das Speichern hinter den Kulissen abläuft. Während Rook im Wesentlichen die Automatisierung eines in Kubernetes laufenden Ceph-Clusters übernimmt, hat OpenEBS einen eigenen Ansatz entwickelt, der verschiedene Komponenten zur Integration in Kubernetes implementiert und zum eigentlichen Storage-Provisioning auf iSCSI setzt. Letzteres ist aber für den Cluster-Admin komplett transparent, sodass es nicht nötig ist, einen iSCSI-Server zu betreiben, wie das etwa bei der klassischen Kubernetes-iSCSI-Integration der Fall wäre. Wie das genau funktioniert, sehen wir gleich, wenn wir OpenEBS auf einem Kubernetes-Cluster installieren.
Grundsätzlich fungieren einzelne Worker-Nodes im Cluster als Storage-Nodes, indem sie ihren lokalen Speicherplatz dem Cluster zur Verfügung stellen. Dies können entsprechend optimierte dedizierte Nodes sein oder auch normale Worker-Nodes, sofern sie über den nötigen Storage verfügen. Dies verbessert nach Meinung der OpenEBS-Entwickler sogar die Zuverlässigkeit des Storage, denn mit mehr Nodes für mehr Workloads steigt auch die Verfügbarkeit von Storage. Gleichzeitig lassen sich damit laut Hersteller auch die Auswirkungen des Ausfalls einzelner Nodes minimieren, denn durch die OpenEBS-interne Replikation stehen die Metadaten automatisch auf jedem Node zur Verfügung.
OpenEBS installieren
Um OpenEBS zu verwenden, benötigen Sie einen Kubernetes-Cluster (Bild 1) mindestens in Version 1.13, besser 1.14, wenn Sie einige neue Features wie Snapshots und das Klonen von Volumes einsetzen wollen. Auf den Nodes, die OpenEBS-Dienste betreiben, muss ein iSCSI-Client (im iSCSI-Jargon: Initiator) installiert sein. Dies setzt voraus, dass Sie soviel Kontrolle über die Nodes haben, dass die Installation von Paketen möglich ist.
Bild 1: Ein Kubernetes-Cluster mit Storage in den Worker-Nodes.
Um OpenEBS zu installieren, müssen Sie sich im Cluster-Admin-Kontext befinden, brauchen also maximale Rechte für den Kubernetes-Cluster, unter anderem weil verschiedene Elemente von OpenEBS als Custom Resource Definitions umgesetzt sind. Um die OpenEBS-Komponenten im Cluster zu installieren, verwenden Sie entweder den Kubernetes-Paketmanager Helm in Version 2 oder 3 oder das YAML-File des OpenEBS-Operators. Statt einer Default-Installation können Sie auch eine angepasste Installation durchführen, indem Sie das YAML-File herunterladen und editieren. Damit lassen sich beispielsweise Node-Selektoren festlegen. Somit bestimmen Sie, auf welchen Kubernetes-Worker-Nodes die OpenEBS-Control-Plane, der Admission-Controller und der Node Disk Manager laufen. Die Standard-YAML-Datei können Sie direkt aus dem Repository anwenden:
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
Danach verrät ein Blick in die Liste der laufenden Pods, ob alles geklappt hat. Hierbei müssen Sie den Namespace "openebs" angeben, in dem sich die OpenEBS-Komponenten befinden:
kubectl get pods -n openebs
 
NAME
maya-apiserver-7b4988fcf6-f4q9q
openebs-admission-server-54dd65b4c9-hkpdh
openebs-localpv-provisioner-68bb775959-g7z8q
openebs-ndm-ggndz
openebs-ndm-hdp5s
openebs-ndm-operator-6b678c6f7f-tp9t6
openebs-ndm-qh4zs
openebs-provisioner-67bfd5bff-p45mz
openebs-snapshot-operator-85d8d495c-g7b77
Ein Aufruf von kubectl get storageclass listet die verfügbaren Storage-Klassen auf, unter denen vier neue zu finden sind: openebs-device, openebs-local, openebs-jiva-default und openebs-snapshot-promoter. Nun können Sie im Prinzip anfangen, Storage von OpenEBS für Pods zu verwenden, auch wenn die bisher eingerichteten Storage-Klassen noch nicht vollständig oder optimal sind.
Listing 1: PVC für Jiva-Storage
kind: PersistentVolumeClaim apiVersion: v1 metadata:     name: demo-vol1-claim spec:     storageClassName: openebs-jiva-default     accessModes:        - ReadWriteOnce     resources:        requests:           storage: 4G
Ein einfaches Beispiel eines Persistent Volume Claims (PVC) ist in Listing 1 zu sehen, das mit kubectl apply -f pvc.yaml ein Jiva-Volume anlegt, das sich anschließend zum Beispiel in der YAML-Deployment-Datei einer Datenbank verwenden lässt.
Jiva ist eine der vier verfügbaren Storage-Engines und neben den beiden Spezialfällen Local Hostpath und Local Device der erste ausgefeiltere Storage-Typ, den OpenEBS implementiert. Dabei steuert jeder beteiligte Node ein Verzeichnis zu dem Jira-Storagepool bei. Ein damit angelegtes Volume wird automatisch zwischen den Nodes repliziert. Ohne weiteres Zutun ist bereits der Default-Storage-Pool für Jiva konfiguriert, der drei Replicas der Daten anlegt, wofür drei Nodes im Cluster benötigt werden.
Storage verwalten
Ein Blick in die Konfiguration der Storage-Klasse mittels kubectl describe sc openebs-jiva-default verweist auf den zugehörigen Storage-Pool "default", über den kubectl describe sp default mehr verrät, etwa den Speicherort "/var/openebs" auf jedem Storage-Node. Wer nun mehr Kontrolle über seinen Storage haben möchte, kann den Nodes jeweils eine weitere Disk spendieren, auf ihnen ein Dateisystem mounten und sie dann als neuen Jiva-Storage-Pool einbinden. Über die zugehörige Storage-Klasse lassen sich dann verschiedene Parameter bestimmen, etwa die Anzahl der Replicas.
Grundsätzlich geschieht das Replizieren der Daten auf der Ebene der Volumes und nicht des Storage-Pools. Das können Sie beobachten, wenn Sie mit dem obigen PVC ein persistentes Volume anfordern. Hier sehen Sie anschließend im Open-EBS-Namespace vier Pods, die neben der ID des PVC den Namensbestandteil "repl-Nummer" enthalten. Jeder Pod läuft auf einem anderen Node und kümmert sich um seinen Teil der Replikation.
Zwei weitere Storage-Optionen, die Open­EBS bietet, sind Local PV Hostpath und Local PV Device. Während die Hostpath-Engine ein Verzeichnis eines Nodes den auf dem gleichen Rechner laufenden Pods zur Verfügung stellen kann, greift Local PV Device hierzu auf ein dediziertes Block Device zu, das in einem Node verfügbar ist. Ein Vorteil der beiden Ansätze gegenüber dem Local Volume Provisioner, den Kubernetes schon von Haus mitbringt, ist etwa die Fähigkeit, Storage dynamisch zu provisionieren, also den Request einer Anwendung zu bedienen statt vorab per Admin ein Volume bereitzustellen. Zudem unterstützen die beiden Storage-Optionen, wie alle anderen OpenEBS-Storage-Engines, Backup und Restore mit der Kubernetes-Anwendung Velero.
Storage-Engine cStor
Die vierte Storage-Engine in OpenEBS ist cStor, das sich anschickt das gute alte Jiva abzulösen, das zwar einfach zu verwenden ist, aber eben auch nicht so viele Optionen bietet wie cStor. Um cStor überhaupt einsetzen zu können, ist es zunächst notwendig, einen cStor-Storage-Pool anzulegen, was OpenEBS per Default nicht macht. Hierzu müssen auf jedem Sto-
rage-Node nicht anderweitig genutzte Block Devices verfügbar sein. Der Node Disk Manager scannt die jeweils verfügbaren Geräte und filtert mit einer (konfigurierbaren) Blacklist diejenigen aus, die für eine Speicherung nicht in Frage kommen, etwa schon gemountete Disks. Potenziell mehrere Disks pro Node bilden dann zusammen einen Storage-Pool, der mit den Storage-Pools der anderen Nodes den cStor-Storage-Pool ergibt.
Eine Liste der im Cluster verfügbaren Block Devices liefert der Aufruf
kubectl get blockdevice -n openebs
Diese tragen Sie in eine YAML-Datei ein und stellen damit einen Storage-Pool zusammen. Hierbei können Sie beispielsweise festlegen, wie ein Node die Daten auf seine lokalen Disks schreibt: striped, mirrored, raidz oder raidz2. Diese Begriffe erinnern nicht von ungefähr an ein RAID, denn dahinter verbirgt sich das ZFS-Dateisystem, das OpenEBS hinter den Kulissen für cStor zur lokalen Datenspeicherung verwendet. Hierbei geht es aber wohlgemerkt eben um den lokalen Datenspeicher – die Replikation der Daten findet wie erwähnt ansonsten auf dem Volume-Level statt. Hier sind maximal fünf Replicas möglich, von denen mindestens drei verfügbar sein müssen, damit das Volume benutzbar ist.
Als Alternative zu dem beschriebenen manuellen Prozess, um den cStor-Storage-Pool zu erzeugen, hat OpenEBS mit Version 2.0 unter dem Begriff cStorPoolCluster (CSPC) neue Kubernetes-Operatoren entwickelt, die das Provisioning und das Management von cStoor-Pools vereinfachen sollen. Dies schließt etwa auch die Erweiterung von Pools durch neue Disks, den Austausch defekter Disks, das Resizing von Volumes, Backup und Re-store sowie automatische Upgrades der Software ein. Mehr Informationen dazu sind auf der Github-Seite [3] zu finden. Ist OpenEBS bereits wie oben beschrieben installiert, genügt es, zusätzlich den Operator zu deployen:
kubectl apply -f https://openebs.github.io/charts/cstor-operator.yaml
Nun legen Sie eine YAML-Datei wie in Listing 2 an und tragen die Block Devices ein, die Sie wie oben beschrieben herausfinden. Hier entsteht ein Storage-Pool, der die Daten zwischen den beiden Platten spiegelt (dataRaidGroupType: "mirror"). Mit den Aufrufen kubectl get cspc -n open-ebs und kubectl get cspi -n openebs sehen Sie den aktuellen Status des Pools. Um nun Volumes aus dem Pool zu beziehen, fehlt nur noch eine Storage-Klasse, wie sie Listing 3 beschreibt. Der Replica Count dieser Storage-Klasse ist 1, denn mehr als ein Pool steht hier ja nicht zur Verfügung. Ist eine Replikation gewünscht, müssten Sie den CStorPoolCluster entsprechend unter dem Key "pools" erweitern. Die Speicherklasse "cstor-simple" lässt sich jetzt in PVCs verwenden, um persistente Volumes aus dem Storage anzufordern.
Listing 2: cStor Pool
apiVersion: cstor.openebs.io/v1 kind: CStorPoolCluster metadata:     name: simplepool     namespace: openebs spec:     pools:        - nodeSelector:              kubernetes.io/hostname: "node2"            dataRaidGroups:            - blockDevices:                   - blockDeviceName: "blockdevice-3f4e3fea1ee6b86ca85d2cde0f132007"                   - blockDeviceName: "blockdevice-db84a74a39c0a1902fced6663652118e"            poolConfig:               dataRaidGroupType: "mirror"
Listing 3: cStor Storage Class
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata:     name: cstor-simple provisioner: cstor.csi.openebs.io allowVolumeExpansion: true parameters:     cas-type: cstor     cstorPoolCluster: simplepool     replicaCount: "1"
Fazit
Voraussetzung für den Einsatz von Open­EBS ist die Kontrolle über die Kubernetes-Worker-Nodes, die Sie mit echten oder virtuellen Disks erweitern müssen. Performance-Wunder dürfen Kubernetes-Anwender aber bei OpenEBS nicht erwarten, denn der Speicher wird hinter den Kulissen über iSCSI über den Cluster verteilt. Wer etwa eine Datenbank mit hohen Performance-Ansprüchen betreiben will, kann auf die Local-Provisioner von OpenEBS zurückgreifen, die mehr Komfort bieten als die Local Provisioner von Kubernetes, aber natürlich letztlich an den jeweiligen Kubernetes-Node gebunden sind.
(jm)
Link-Codes
[2] OpenEBS: https://openebs.io/