mittwald Cloud Plattform Insights (II): Learnings & Re-Engineering
Die Tiefen des IaaS
Der Unterbau unserer Kubernetes-basierten Hosting-Plattform besteht aus einem Infrastucture-as-a-Service-Angebot. Ziel solcher Angebote ist es, Infrastrukturkomponenten (also Server, Netzwerke oder Storage) als virtuelle Komponenten auf Anfrage und automatisiert bereitstellen zu können.
Für unsere Plattform entschieden wir uns für OpenStack. Dabei handelt es sich um ein weit verbreitetes Open Source Projekt, mit dem man Cloud-Infrastrukturen auf eigener Hardware aufbaut. OpenStack lässt sich gut automatisieren, da alle Funktionen (beispielsweise für die Bereitstellung virtueller Maschinen, Netzwerke oder Storage-Kapazitäten) per API angesteuert und somit in Automatisierungsprozesse eingebunden werden können. Für die Bereitstellung von Speicherplatz in so einer Infrastruktur wird OpenStack häufig mit Ceph integriert (dazu gleich mehr).
Um die Bereitstellung von Kubernetes-Clustern noch weiter zu automatisieren (Kubernetes ist die Grundlage für unseren darauf aufbauenden Hosting-Stack), integrierten wir zudem Gardener; eine Plattform zur (weitgehend) automatisierten Bereitstellung und Verwaltung von Kubernetes-Clustern auf Cloud-Plattformen wie beispielsweise OpenStack.
Komplexi-was?
Nach den ersten Jahren im Betrieb wurden langsam die Einschränkungen der von uns gewählten Technologien deutlich. Gardener beispielsweise gibt sehr klare Wege vor, auf denen es Kubernetes-Cluster bereitstellt. Werden für bestimmte Plattform-Features von diesem Weg abweichende Konfigurationen nötig, macht das häufig einen hohen Anpassungsaufwand und aufwändige Workarounds nötig. Beispielsweise benötigten wir zur Anbindung der Storage-Cluster an unsere virtuellen Maschinen jeweils ein zweites Netzwerk-Interface. Dafür mussten wir zunächst die Bereitstellung von virtuellen Maschinen erweitern. Weiterhin enthielten unsere Kubernetes-Cluster eine große Anzahl individuell konfigurierter Nodes (teilweise einzelne Nodes pro Kunde oder pro Projekt). Das ist ein Use Case, der in Gardener gar nicht vorgesehen ist, und uns schnell an systemseitige Limitierungen gebracht hat. Aufwändiges drumherum arbeiten war die Folge.
Auch der gewählte IaaS-Unterbau brachte Herausforderungen mit sich. Insbesondere das Software Defined Networking von OpenStack ist komplex und erfordert ein hohes Maß an Spezialwissen. In Fehlerfällen (beispielsweise ausgelöst durch DDoS-Angriffe wie am 20. September oder am 27. September) führt die komplexe Verkettung aus Einzelkomponenten oft zu einem diffusen Fehlerbild, das nur schwierig zu debuggen ist. Im Störungsfall erhöht sich die Wiederherstellungszeit damit oft unnötig.
Der Storage-Trade-Off
Die Wahl einer Storage-Technologie ist eine der wichtigsten Architekturentscheidungen beim Aufbau einer Cloud-Plattform. Da wir den Anspruch erheben, unsere Plattform wenn möglich mit Open Source Technologien zu entwickeln, landeten wir schnell bei Ceph, dem Platzhirsch in diesem Bereich. Doch auch mit Ceph als Rahmenbedingung bleibt die Auswahl der konkreten Storage-Mechanismen ein Labyrinth aus Trade-Offs.
Lokal vs. Networked?
Der erste Trade-Off ist bereits die Entscheidung für (oder gegen) ein netzwerkbasiertes Storage-System. Diese bieten zwar nicht die Performance einer lokalen SSD, dafür aber höhere Verfügbarkeit und bessere Failover-Mechanismen, wodurch Daten unabhängig von Hardware-Ausfällen sind. Zudem ermöglicht dies das schnelle Neustarten von Workloads auf anderen Servern.
Block Device vs. File System?
Ein weiterer Trade-Off ist die Wahl zwischen Networked Block Devices und Networked File Systems. Block Devices sind performanter, vor allem bei vielen zufälligen Schreibzugriffen (was sich insbesondere bei Datenbanksystemen bemerkbar macht). Außerdem können Block Devices mit einem Dateisystem formatiert werden, das auf den jeweiligen Anwendungsfall zugeschnitten ist. So konnten wir mit einem Wechsel von EXT4 auf XFS beispielsweise spürbare Performance-Steigerungen bei Datenbank-Workloads erreichen.
Block Devices können nicht von mehreren Servern gleichzeitig genutzt werden. Bei Datenbanken ist das kein Problem (die sind sowieso nicht darauf ausgelegt, mit mehreren Server-Instanzen auf denselben Daten zu arbeiten), schränkt aber Web-Workloads ein, wenn sie horizontal skaliert betrieben werden sollen. Netzwerk-Dateisysteme wie CephFS oder NFS ermöglichen den gleichzeitigen Zugriff mehrerer Server, sind jedoch genau wegen dieser Möglichkeit langsamer.
Die schlechtere Performance lässt sich teils mit applikationsseitigem Caching ausgleichen; im einfachsten Fall schon allein durch den PHP-OPcache, teilweise mit Caching-Kapazitäten im Ceph-Cluster selbst. Dennoch merkten wir bald, dass es Anwendungsfälle gab, in denen die Performance von CephFS nicht ausreichend war — beispielsweise, wenn Anwender dateiintensive Operationen durchführten (wie etwa große Git-Repositories zu klonen), oder Webanwendungen/CMS mit dateibasierter Datenhaltung (wie etwa Kirby CMS) einzusetzen. Daher begonnen wir, auch diese Workloads auf Block Devices umzustellen.
Replication vs. Erasure Coding
Ceph unterstützt zudem verschiedene Datensicherheitsmechanismen. Im „Replication"-Modus werden Datenblöcke mehrfach dupliziert, was zu hohem Speicherplatz-Overhead führt (3 GB Speicherplatz für 1 GB Nutzdaten). „Erasure Coding" ist speicherplatzsparender (1,5 GB Speicherplatz für 1 GB Nutzdaten) aber rechenintensiver und somit langsamer.
Wir begannen mit Erasure Coded Storage und bemerkten dann, dass bei einigen Anwendungen (vor allem bei Datenbankservern) eine deutlich bessere Performance durch den Wechsel auf Replication erzielt werden konnte.
Learnings
Klingt alles kompliziert − ist es auch. Gelernt haben wir vor allem, dass man im Bereich Storage mit einem einzelnen Storage-Mechanismus nicht glücklich wird. Einerseits haben verschiedene Arten von Workloads verschiedene Anforderungen (Datenbanken vs. verschiedene Arten von Webanwendungen), andererseits haben auch verschiedene Kundenprojekte verschiedene Anforderungen (manche Projekte benötigen Hochverfügbarkeit, in anderen ist Performance wichtiger, und für wieder andere soll es möglichst günstig sein). Manche Technologien lassen sich zudem nur sinnvoll einsetzen, wenn die Architektur der Applikation darauf optimiert ist.
Für die zukünftige Weiterentwicklung unserer Plattform leiten wir daraus die Anforderung ab, dem Anwender selbst die Auswahl zu überlassen, welcher Storage-Mechanismus für welches Projekt verwendet werden soll (und somit auch, auf welchen Trade-Off er sich am liebsten einlassen möchte). Denn letztlich ergeben sich diese Abwägungen aus dem jeweiligen konkreten Projekt, sodass es als Infrastrukturbetreiber schwierig ist, hier eine generische Lösung anzubieten.
Re-Engineering − so geht’s weiter
Wenn wir schon von „zukünftiger Weiterentwicklung” sprechen: Im Rahmen der Entwicklung und des Realbetriebs der Cloud-Plattform konnten wir viele Learnings erzielen, die wir in die Weiterentwicklung der Plattform einfließen lassen können.
So ziehen wir beispielsweise Konsequenzen aus der Komplexität unserer Infrastructure-as-a-Service-Plattform OpenStack. Der Betrieb der Plattform ist aufgrund ihrer Komplexität sehr aufwändig und erfordert viel Spezialwissen. Da beispielsweise im Networking-Bereich sehr viele Komponenten aufeinander abgestimmt sein müssen, ist das Debugging im Fehlerfall zeitraubend, was zu unnötig hohen Wiederherstellungszeiten führt.
Außerdem stellten wir fest, dass wir zahlreiche Funktionen von OpenStack entweder gar nicht benötigten (so beispielsweise die Multi-Mandantenfähigkeit) oder uns nicht flexibel genug waren, und wir sie bereits durch Alternativen ersetzt hatten (beispielsweise ersetzten wir die OpenStack-Loadbalancer durch MetalLB).
3 Optimierungen des
Re-Engineering
- Geringere Abhängigkeit von zentralen Storage-Komponenten bringt bessere Stabilität.
- Höhere Performance durch Aufbau auf Bare Metal und lokalem Speicherplatz.
- Schlankere, weniger komplexe Infrastruktur für schnelleres Debugging.
Wir werden zukünftig statt auf OpenStack und Ceph stärker auf Kubernetes-native Lösungen setzen. Ein paar Beispiele:
- Statt auf komplexe Software Defined Networking Lösungen wie Open vSwitch setzen wir zukünftig auf die Kubernetes-Bordmittel: Beispielsweise unterstützt Calico (eines der Standard-Netzwerk-Plugins für Kubernetes) Routing mit BGP. Mit entsprechender Netzwerk-Hardware können die Routen innerhalb eines Kubernetes-Clusters somit auch außerhalb des Clusters bekanntgegeben und Traffic aus dem Internet direkt bis zum Container gerouted werden. Dies vereinfacht das Setup signifikant und eliminiert mögliche Fehlerquellen im Networking-Stack.
- Durch den Fokus auf Bare Metal Deployment haben wir zukünftig die Möglichkeit, wieder hoch performanten lokalen Storage anzubieten (mit den oben genannten Trade-Offs). Das OpenEBS-Projekt stellt beispielsweise einige schöne Integrationen dafür bereit. Aber auch das Bereitstellen von Netzwerk-Speicher per Ceph ist dank Projekten wie Rook weiterhin möglich. Die so gewonnene Flexibilität wird es uns zukünftig ermöglichen, die jeweils am besten zu den Anforderungen des Kundenprojekts passende Storage-Technik anbieten zu können.
- Statt auf von OpenStack bereitgestellte virtuelle Maschinen setzen wir vermehrt auf Nested Containerization. Das bedeutet, dass eine Kubernetes-Node nicht zwingend eine echte virtuelle Maschine sein muss. Sie kann ihrerseits ebenfalls wieder ein Container sein, der aus einem anderen Kubernetes-Cluster heraus verwaltet wird. Das ermöglicht uns eine schnellere (und vereinfachte) Bereitstellung neuer Kubernetes-Nodes. An Stellen, an denen wir weiterhin virtuelle Maschinen benötigen, können wir Kubernetes-native Technologien wie etwa KubeVirt einsetzen.
All diese Änderungen ermöglichen eine ähnlich gute Automatisierbarkeit, wie OpenStack sie anbietet — jedoch ohne die zusätzliche Komplexität und mit einem geringeren operativen Overhead.
Dieses Re-Engineering läuft mittwald intern bereits seit einigen Monaten. Die darauf aufbauende Plattform befindet sich derzeit in der finalen Testphase und steht unmittelbar vor dem Rollout. Wir werden diese im Zuge für neue Hosting-Workloads im mStudio freigeben und bestehende mStudio Hosting Umgebungen zeitnah (und dabei soweit möglich unterbrechungsfrei und ohne notwendige Nutzerinteraktion) auf die neue Plattform umstellen.
Über Details unserer neuen Hosting Plattform werden wir auch zukünftig an geeigneten Stellen, etwa als Konferenzbeitrag und hier im Blog berichten.