Die Wahl zwischen Block-, File- und Object Storage

Die Wahl zwischen Block-, File- und Object Storage

Wenn man mit anderen über die Migration ihrer Anwendungen auf Kubernetes spricht, tauchen oft Fragen zur Datenspeicherung auf:

  • Verbleiben die Daten in der Schweiz (Datenlokalität)?

  • Sind die Daten auf dem Speicher verschlüsselt?

  • Gibt es eine Verschlüsselung während des Übertragens?

  • Ist es möglich, «grosse Zahl einfügen» Speicherplatz zur Verfügung zu stellen?

Eine Sache, die oft übersehen wird, ist die Frage, wie man auf Daten in einem Kubernetes-Cluster mit mehreren Knoten zugreifen und sie speichern kann und welche Infrastruktur dafür benötigt wird. Da wir mehrere Optionen haben, möchte ich dieses Thema näher beleuchten und die Unterschiede erläutern.

Warum muss man sich entscheiden?

Häufig werden Anwendungen direkt auf einem einzigen Server (VM oder Bare Metal) ausgeführt, da die Rechenleistung eines Computers für den jeweiligen Anwendungsfall ausreicht. Wenn noch andere Anwendungen laufen, werden diese entweder auf demselben Rechner oder auf einer anderen einzelnen Instanz betrieben. Das Speichern von Daten ist einfach, da die Anwendung nur auf einer lokalen Festplatte lesen und schreiben muss. Diese Architektur entwickelt sich meist aus historischen und wirtschaftlichen Gründen, da kein Load Balancer oder kompliziertes Cluster-Setup erforderlich ist.

Um nun all diese Anwendungen in einem Kubernetes-Cluster zu konsolidieren (um das Deployment zu vereinheitlichen, mehr Fehlertoleranz zu bieten usw.), sollten die meisten Anwendungen eine Sache unterstützen: den verteilten Betrieb (auf verschiedenen Knoten) mit mehreren Instanzen derselben Applikation.

Der Grund dafür ist, dass die Knoten in einem Kubernetes-Cluster sehr dynamisch sind. Ein Knoten kann jederzeit ausfallen (aufgrund eines unerwarteten Ausfalls, einer Wartung, der automatischen Skalierung des Clusters usw.). Wenn wir zum Beispiel Cluster-Upgrades auf unseren NKE-Clustern durchführen, ersetzen wir die Knoten jedes Clusters vollständig, anstatt die Softwarepakete des Betriebssystems auf jeder Instanz zu aktualisieren. Im Falle eines Knotenausfalls kümmert sich Kubernetes um das Verschieben laufender Anwendungen auf andere Server im Cluster. Dies kann jedoch einige Minuten dauern und wenn nur eine Instanz der Anwendung läuft, kann dies zu kurzen Unterbrechungen führen. Diese Unterbrechungen können sich in einem Wartungsfenster summieren, da jeder Knoten ersetzt werden muss. Die parallele Ausführung der Anwendung auf mehr als einem Knoten löst dieses Problem und ist eine der von uns empfohlenen Best Practices.

Wenn jede Anwendungsinstanz Daten nur lokal auf dem Knoten speichert, auf dem sie läuft, haben andere Instanzen keinen Zugriff auf diese Daten, da sie auf anderen Rechnern laufen. Häufig speichern Webanwendungen vom User hochgeladene Daten (PDFs, Bilder usw.). Diese Daten müssen nun für alle Anwendungsinstanzen zugänglich sein. Neben unseren Applikationen, die nun für den Einsatz in einer verteilten Umgebung bereit sein müssen, benötigen wir möglicherweise auch ein Speichersystem, auf das verteilte Clients zugreifen können.

Was steht zur Auswahl?

Basierend auf dem letzten Abschnitt, lässt sich der Zugriff auf das Speichersystem in Kubernetes in zwei Typen unterteilen:

  • read-write-once (RWO)
  • read-write-many (RWX)

Der Unterschied ist einfach zu erklären. Wenn auf einen Speicherort nur ein «Writer» (eine Anwendung, die Daten schreiben oder löschen will) auf einem Knoten und zu einem bestimmten Zeitpunkt zugreifen kann, wird die Zugriffsart RWO genannt. Es kann sein, dass anderen Anwendungen, die auf anderen Knoten laufen, Lesezugriff auf den Speicherplatz ermöglicht werden kann, aber das hängt vom verwendeten System ab.

Wenn mehrere Applikationen (auf verschiedenen Knoten) Änderungen an den Daten vornehmen müssen, benötigen wir ein Speichersystem, das den RWX-Zugriff unterstützt.

Bildquelle: [Canonical](https://canonical.com/blog/what-are-the-different-types-of-storage-block-object-and-file)
Bildquelle: Canonical

Block Storage

Wenn eine Anwendung auf Partitionen oder ganze Festplatten zugreift, beziehen wir uns eigentlich auf Block Storage (da auf Festplattenblöcke direkt zugegriffen wird). Normalerweise legen wir ein Dateisystem an und stellen es den Anwendungen zur Verfügung, die auf diesem einen Knoten laufen (Block Storage ist also ein Vertreter der Kategorie «RWO Zugriff»).

Hast du eine einzelne Anwendung (z. B. einen Postgres-Datenbank-Pod für die Entwicklung), die ihren eigenen Speicherplatz benötigt? Dann solltest du Block Storage wählen.

Hierfür gibt es zwei Varianten:

  • Node Local Block Storage (d.h. eine Platte, die an einen bestimmten Knoten angeschlossen ist)
  • Remote Block Storage (auf den du über iSCSI, Fibre Channel usw. zugreifst)

Remote Block Storage kann im Falle eines Ausfalls oder einer Wartung oft an einen anderen Knoten angebunden werden und bietet somit Fehlertoleranz für Maschinen. Nicht-flüchtiger Node Local Block Storage hingegen ist an einen bestimmten Knoten gebunden, kann aber sinnvoll sein, wenn Bare-Metal-Server Teil des Kubernetes-Clusters sind und deine Anwendung sehr schnellen Zugriff auf den Speicherplatz benötigt. Allerdings sind alle Anwendungen, die persistenten Node-Local-Speicher verwenden, an diesen einen Server gebunden, und im Falle eines Ausfalls kann die Anwendung dann nicht ausgeführt werden. Möglicherweise können in einem solchen Fall spezielle Fehler behebende Algorithmen in der Anwendung selbst eingesetzt werden (die Software selbst kommuniziert mit anderen Instanzen und kann sich bei Ausfällen selbst heilen), aber im Allgemeinen empfehlen wir die Verwendung eines schnellen Remote Block Storage, da dieser in den meisten Fällen mehr Flexibilität bietet (auch in Bezug auf die Speichererweiterung). Zum Beispiel sind all unsere NKE-Cluster auf virtuellen Maschinen aufgebaut und unterstützen standardmässig einen schnellen Remote Block Storage, aber keinen persistenten Node-Local-Speicher.

Es gibt auch flüchtige Node-Local-Speicherlösungen wie Kubernetes emptyDir oder Googles lokale SSD-Funktion für GKE-Cluster, die als Scratch-Speicher oder temporärer Speicher verwendet werden können.

Normalerweise zahlst du für die Menge an Block Storage, die du angefordert hast, und nicht dafür, wie viel du davon bereits verbraucht hast.

File Storage

Im Gegensatz zu Block Storage ermöglicht File Storage (wie der Name schon sagt) die gemeinsame Nutzung von Dateien durch mehrere verteilte Clients, indem ein gemeinsames Dateisystem genutzt wird. Anwendungen, die auf Kubernetes laufen, können auf diese Dateien zugreifen, als wären es lokale Dateien. Da fast jede Programmiersprache den Zugriff auf Dateien ermöglicht, ist die Verwendung von File Storage meist die erste Wahl, wenn ein verteilter Lese- und Schreibzugriff erforderlich ist. Lösungen wie NFS, CIFS, CephFS oder GlusterFS implementieren eine derartige Lösung.

Dateien sind in einem Verzeichnisbaum strukturiert, der recht tief verschachtelt sein kann. Jede Datei hat eine Nutzlast (die eigentlichen Daten der Datei), benötigt aber zusätzlich Metadaten, die gespeichert werden müssen (Zugriffsrechte, Dateityp usw.). Da mehrere verteilte Clients parallel auf das Dateisystem zugreifen können, müssen zusätzlich Sperrmechanismen vorhanden sein, die eine konsistente Sicht für jeden Client gewährleisten. Dies wird von den verschiedenen Implementierungen unterschiedlich gehandhabt (und hat sich beispielsweise auch bei NFS-Implementierungen im Laufe der Zeit geändert). Ohne zu sehr in die technischen Details zu gehen, bieten File-Storage-Systeme meist eine geringere Leistung im Vergleich zu Block Storage, aber sie bieten auch die Möglichkeit des RWX-Zugriffs, was bei der Verwendung von Kubernetes eine Voraussetzung sein kann. Zusätzlich bieten nicht alle Implementierungen von File Storage volle POSIX-Kompatibilität.

Aus der Sicht eines Infrastrukturanbieters ist es nicht so einfach, dynamisch bereitgestellten File Storage in einer Kubernetes-Umgebung bereitzustellen und zu betreiben. Früher haben wir einige NFS-Instanzen im Cluster selbst verwaltet, aber während der Wartungszeiten, in denen alle Knoten ausgetauscht werden, gab es Probleme. Manchmal blieben Clients stecken und Pods konnten nicht gestartet werden. Hinzu kommt, dass NFS und CIFS seit langem bestehende Lösungen sind, die möglicherweise nicht in die dynamischen Umgebungen von heute passen.

NFSv3 beispielsweise authentifiziert Clients auf der Grundlage von IP-Adressen, die in einem Kubernetes-Cluster meist flüchtig sind. NFSv4 kann Clients sicher authentifizieren, aber dafür wird eine Kerberos-Infrastruktur benötigt. CIFS unterstützt einige nette Funktionen, liefert aber auch Windows-spezifische Dinge wie die Druckerfreigabe, die in einer Kubernetes-Umgebung nicht wirklich benötigt werden. Ausserdem gibt es derzeit zwei NFS-bezogene Kubernetes-Sigs-Projekte, aber keines für CIFS. CephFS ist wirklich vielversprechend in Bezug auf Funktionen und Skalierbarkeit, aber es ist auch komplex zu managen (obwohl rook die Situation verbessert hat). Wir haben auch frühe Versionen von GlusterFS verwendet, als wir vor einigen Jahren noch Openshift-Cluster betrieben haben, aber wir hatten damals ziemliche Probleme mit der Konsistenz und Verfügbarkeit.

Die Bereitstellung automatischer Sicherungen und Wiederherstellungsmöglichkeiten für File-Storage-Lösungen in einer Kubernetes-Umgebung kann eine zusätzliche Herausforderung darstellen.

Insgesamt sind File-Storage-Lösungen mit einem höheren Preis verbunden, da ihr automatisierter Betrieb einen gewissen technischen Aufwand erfordert. Wir stellen RWX Speicherplatz in unseren NKE-Clustern bereit, der durch eine NFS-Lösung von unserem Infrastruktur-Anbieter unterstützt wird.

Object Storage

Neben den eben erwähnten File-Storage-Lösungen erfreut sich Object Storage zunehmender Beliebtheit, da er ebenfalls verteilten Lese- und Schreibzugriff ermöglicht. Anstelle von Dateien werden die Daten in Objekten gespeichert, die eine eindeutige ID in einem flachen Namensraum (Bucket) haben. Jedes Objekt ist ausserdem mit Metadaten versehen (was die Suche ermöglicht).

Der Zugriff auf die Objekte erfolgt über HTTP-basierte Protokolle, wobei Amazons S3 das beliebteste ist (da es von ihnen erfunden wurde). Diese Art des Zugriffs unterscheidet sich stark von Lösungen, die auf File Storage basieren. Es gibt kein gemeinsam genutztes Dateisystem mehr, das eingehängt und an den Container übergeben wird. Das zugrundeliegende Betriebssystem oder der Cluster-Orchestrator sind nicht mehr beteiligt, um auf die Daten zuzugreifen. Stattdessen greift die Anwendung selbst über Bibliotheken auf das Object-Storage-System zu. Dies ermöglicht eine grosse Flexibilität, kann aber auch Änderungen am Quellcode der Anwendung bedeuten. Der Grund für die Codeänderungen ist auch, was einige Anwendungen davon abhält, Object Storage zu verwenden. Es wäre einfach zu viel Arbeit, das zu implementieren.

Sobald die Anwendung jedoch Object-Storage-Lösungen nutzen kann, ergeben sich einige grossartige Funktionen, von denen man profitieren kann. Hier einige Beispiele:

  • Pay-what-you-use-Modell: es wird nur der von dir genutzte Speicherplatz berechnet (plus Gebühren für API-Aufrufe und/oder verwendete Bandbreite)
  • Praktisch unbegrenzter Speicherplatz (keine vorherige Bereitstellung von Speicherplatz erforderlich)
  • Verschiedene Richtlinieneinstellungen ermöglichen die Kontrolle der Zugriffsberechtigungen für Buckets und die darin enthaltenen Objekte auf der Basis einzelner Benutzer (die durch «Zugriffsschlüssel» identifiziert werden)
  • Write-once-read-many Buckets (WORM): Daten, die einmal geschrieben wurden, können nicht mehr gelöscht oder überschrieben werden. Richtlinien können dafür sorgen, dass Daten nach einer bestimmten Zeit automatisch gelöscht werden.
  • Datenzugriff von überall: Auf Buckets und Objekte kann normalerweise von überall auf der Welt zugegriffen werden, unabhängig davon, wo deine Anwendung läuft. Eine bereitgestellte File-Storage-Lösung ist möglicherweise nicht von ausserhalb deines Kubernetes-Clusters zugänglich.
  • Die Verwendung von HTTPS bietet Verschlüsselung bei der Übertragung
  • Meist unabhängig vom Anbieter: Du kannst einfach zu einem anderen Anbieter wechseln, sofern das Object-Storage-Protokoll dasselbe ist (und du keine sehr anbieterspezifischen Funktionen nutzt)

Aber es gibt auch einige Nachteile bei Object Storage und dessen Implementierungen. Objekte können nur als Ganzes geschrieben werden. Das bedeutet, dass du ein Objekt nicht öffnen kannst, um es zu ergänzen (wie es bei File Storage möglich ist). Du müsstest das gesamte Objekt herunterladen, deine Daten in der Anwendung anhängen und alle Daten erneut hochladen. Der Zugriff auf Object Storage ist oft langsamer als auf File Storage, so dass Anwendungen, die einen schnellen Zugriff auf Daten benötigen, möglicherweise eine Zwischenspeicherung verwenden oder auf eine File-Storage-Lösung zurückgreifen müssen. Um den Zugriff auf Object-Storage-Systeme zu beschleunigen, sollten die Anfragen nach Möglichkeit parallel erfolgen. Es gibt auch Unterschiede im Funktionsumfang (und der Qualität) von Object-Storage-Lösungen, die den Zugriff über das gleiche Protokoll ermöglichen.

Für unsere Kunden bieten wir eine S3-kompatible Object-Storage-Lösung, gehostet in Schweizer Rechenzentren.

3_S3Object Storage

Was soll ich also verwenden?

Wenn deine Anwendung nur aus einer einzigen Instanz besteht und nur selbst Lese- und Schreibzugriff benötigt, sollte ein schneller Netzwerk-Block-Storage deine Wahl sein. Er ermöglicht im Falle eines Ausfalls ein Failover auf einen anderen Knoten und bietet eine gute Leistung.

Wenn du verteilte Lese- und Schreibzugriffe benötigst, bevorzugen wir die Verwendung von Object Storage gegenüber File Storage (wenn dein Anwendungsfall dies zulässt). Auch wenn deine Anwendung noch keinen Object Storage unterstützt, könnte sich eine Implementierung dieser auf lange Sicht lohnen. Die Verwendung von Objekten ermöglicht einfach mehr Flexibilität und bietet oft auch mehr Funktionen.

Bildquelle Featured Image: Intelligent CIO