mittwald Cloud Plattform Insights (I): Architektur
Neue Anforderungen an das Hosting
Die Anforderungen an das Hosting von Webprojekten haben sich in den zurückliegenden Jahren stetig verändert — nicht nur hinsichtlich der benötigten Ressourcen, sondern auch im Tech-Stack. Beispielsweise verzeichnen wir eine stetig steigende Nachfrage nach zusätzlichen Technologien, die in komplexeren Projekten oft einfach dazugehören (beispielsweise OpenSearch oder Solr für eine Suchfunktion, Keycloak für Authentifizierung, Varnish für Caching, S3-kompatibles Storage, und vieles mehr). Hinzu kommt das Bedürfnis nach mehr Kontrolle über die bereitgestellten Umgebungen (was heutzutage üblicherweise über die Bereitstellung eigener Container-Images erreicht wird).
Diese Trends werden bestärkt durch neue Architekturstile, wie beispielsweise die MACH-Architektur, die sich in unserem Kontext am häufigsten in der Form verschiedener Headless CMS manifestiert (beispielsweise Headless WordPress oder Headless TYPO3), die dann in Microservice-Architekturen integriert werden. In diesen Fällen dient das CMS „nur noch” als API und wird von einem (oder mehreren) Frontends konsumiert (diese dann oft auf Basis von Node.js-Frameworks wie etwa Next.js).
Neben solchen funktionalen Anforderungen steigen die Qualitätsanforderungen, etwa an Verfügbarkeit und Skalierbarkeit. Und aus der nach wie vor angespannten Bedrohungslage lassen sich zudem generell höhere Anforderungen an die Sicherheit der Betriebsumgebung ableiten.
Martin auf den TYPO3 Developer Days 2024: "How to build your own cloud platform"
Der Artikel basiert auf einer Präsentation, die Martin bei den TYPO3 Developer Days 2024 gehalten hat. Hier geht's zu den Slides .
Unsere Lösungsstrategie
All diese neuen Anforderungen motivierten uns, unsere Hosting-Plattform einem Re-Engineering zu unterziehen, und zukünftig auf etablierte Cloud-Technologien als Unterbau sowie mit dem mStudio und dessen API auf eine moderne und zugängliche Nutzeroberfläche mit einer guten Developer Experience zu setzen.
Um die Architektur dieser Plattform in die richtige Richtung zu lenken (Martin Fowler definiert Softwarearchitektur einfach nur als „die Entscheidungen, die im Nachhinein nur noch schwierig zu ändern sind”), definierten wir Qualitätsziele unserer neuen Plattform.
Neben den naheliegenden Dingen wie Sicherheit, Performance und Verfügbarkeit waren uns bei der Entwicklung der Cloud-Plattform folgende Qualitätsziele besonders wichtig:
- Flexibilität: Die Plattform sollte sich schnell auf den Betrieb neuer Produkte anpassen lassen und eine einfache Individualisierbarkeit auf kundenspezifische Bedürfnisse zulassen.
- Ressourceneffizienz: Die Bereitstellung von Projekten sollte die volle Bandbreite von „große Mengen dedizierter Compute-Ressourcen, die exklusiv für ein einzelnes Projekt bereitgestellt werden” bis hin zu „hohe Packdichte für ressourcen- und kosteneffiziente Bereitstellung kleiner Projekte” (umgangssprachlich auch als „Shared Hosting” bezeichnet) unterstützen.
- Wartbarkeit und Benutzbarkeit (für interne Entwickler): Die Cloud-Plattform dient anderen Entwicklungsteams bei mittwald als Grundlage, beispielsweise bei der Entwicklung des mStudios. Daraus abgeleitet ergab sich die Anforderung, die Komplexitäten des Plattformbetriebs hinter einer (für mittwald-interne Entwickler) einfach benutzbaren API zu kapseln, um die Entwicklungsgeschwindigkeit in diesen Teams hochzuhalten.
Aus diesen Qualitätszielen leiteten wir im Anschluss folgende Lösungsstrategien ab:
Container-basierte Bereitstellung mit OCI-Images
Container-Technologien wie Docker (oder containerd, oder CRI-O) haben sich zur Standardtechnologie der Bereitstellung von Software entwickelt. Die Verwendung von Containertechnologien zahlt auf folgende Qualitätsziele ein:
- Flexibilität, da sie ein schnelles Deployment neuer Anwendungen ermöglichen und eine zügige Anpassung der Plattform an neue Anforderungen ermöglichen.
- Security, da sie (im Vergleich zum traditionellen Shared Hosting) einen höheren Grad an Isolation verschiedener Workloads untereinander bei (potenziell) nahezu gleichbleibender Ressourcenauslastung bieten. In Kundenprojekten mit höheren Isolationsanforderungen können als zusätzliche Isolationsschicht weiterhin konventionelle virtuelle Maschinen eingesetzt werden.
- Ressourceneffizienz, da Container sowohl die exklusive Reservierung von Compute-Ressourcen ermöglichen als auch (je nach Anforderung) beliebige Grade an Überbuchung.
Verwendung einer Container Orchestration Engine
Kubernetes hat sich als Standardtechnologie zur Verwaltung von Containern in größeren Clustern etabliert. Die Verwendung von Kubernetes zahlt auf folgende Qualitätsziele ein:
- Skalierbarkeit und Elastizität, da Kubernetes von Haus aus Möglichkeiten zur automatischen Skalierung von Workloads (sowohl horizontal als auch vertikal) anbietet.
- Flexibilität, da rund um Kubernetes herum bereits ein großes Community-Ökosystem aus vordefinierten Packages und Operatoren gibt, auf welchem wir in unserer Plattform-Entwicklung aufbauen können. Auch dies erlaubt die schnelle Erweiterung der Plattform um zusätzliche Funktionen.
- Verfügbarkeit, da Kubernetes umfassende Monitoring- und Self-Healing-Funktionen anbietet.
Implementierung eigener Komponenten über Kubernetes Operators
Operatoren sind der von Kubernetes vorgesehene Mechanismus, die Plattform um eigene Funktionen zu erweitern. Kubernetes ermöglicht hierbei die Definition eigener Ressourcen-Typen, die dann von einem Operator in Kubernetes-Core-Ressourcen „übersetzt” werden. Diese Lösungsstrategie zahlt auf folgende Qualitätsziele ein:
- Benutzbarkeit durch interne Entwickler: Andere mittwald Entwicklungsteams müssen sich nicht mit den (nicht immer einfach zu überblickenden) Kubernetes-Kern-Funktionen auseinandersetzen, sondern können eine deklarative API (dazu später mehr) mit Ressourcen aus der konkreten Fachdomäne nutzen.
Architektur und Organisation
Die so entstandene Kubernetes-basierte Hosting-Plattform gliedert sich in eine größere Architektur ein, mit der wir unseren Kunden eine umfassende Hosting-Lösung bereitstellen.
Makro-Architektur
Das Kunden-Interface der Cloud-Plattform (also das mStudio) ist selbst eine komplexe Anwendung, die ihrerseits wiederum aus zahlreichen Microservices besteht. Über ein paar Details unserer Microservice-Architektur hatten wir zuvor im Blog berichtet, beispielsweise zur Workflow-Orchestration, oder unseren eingesetzten Programmiersprachen. Das mStudio (bzw. einige Microservices davon) agieren hier als Client unserer Kubernetes-Plattform, um Hosting-Dienste bereitstellen zu können.
Auch die Kubernetes-Plattform selbst benötigt natürlich einen Infrastruktur-Unterbau, um Server-, Netzwerk- und Storage-Kapazitäten bereitzustellen. Diese Aufgabe übernimmt in unserer Architektur unsere interne Infrastructure-as-a-Service-Plattform, die wir mit OpenStack und Gardener aufgebaut haben (auch wenn sich das demnächst ändern wird − dazu im zweiten Teil dieser Blogreihe mehr). Die dafür nötigen Hardware-Kapazitäten betreiben wir nach wie vor selbst in unserem eigenen Rechenzentrum am Standort in Espelkamp.
In der Kubernetes-Schicht
Die eigentlichen Hosting-Umgebungen stellen wir über reguläre Kubernetes-Bordmittel bereit. So wird etwa eine App im mStudio mit relativ wenig Umwegen auf ein Container-Image und ein Kubernetes-Deployment übersetzt. Nachfolgendes Bild zeigt beispielsweise, wie ein Space-Server im mStudio mit einem frisch installierten TYPO3 im Hintergrund aussieht, wenn man sich mit kubectl get pods die daraus erstellten Container anschaut.
Eine Besonderheit stellt dabei die Art und Weise dar, mit der wir Container-Images in dieser Plattform bereitstellen. Diese werden nicht per docker build oder einem ähnlichen Befehl gebaut, sondern anhand der Spezifikation, die der Anwender im mStudio vorgenommen hat, dynamisch erstellt. Wie genau das funktioniert, hatte ich zuletzt in meinem T3DD23-Talk erklärt.
Problem bei alldem: Kubernetes ist kompliziert ... und natürlich bleibt es bei einer mStudio-App nicht bei einer einfachen Deployment-Ressource, sondern es kommt eine Vielzahl von weiteren Ressourcen dazu – beispielsweise für die Konfiguration von Volumes oder von Domains und Zertifikaten.
Dem Prinzip des Information Hiding folgend entschlossen wir uns, diese Komplexität vor nachgelagerten Nutzern (sprich, anderen mittwald Entwicklungsteams, die aufbauend auf der Plattform die mStudio-Verwaltungsoberfläche entwickeln) durch entsprechende Abstraktionsschichten zu verbergen. Mithilfe von Kubernetes Custom Resources erweiterten wir die Kubernetes-API um Ressourcen, die sich an der Ubiquitären Sprache (ein Fachbegriff aus dem Domain-Driven Design) der anderen Entwicklungsteams orientierte. Auf diese Weise mussten die übrigen mittwald Entwicklungsteams sich nicht mit den Feinheiten von Kubernetes auseinandersetzen, sondern konnten mit Konzepten weiterarbeiten, mit denen sie aus ihrer eigenen Fachdomäne bereits vertraut waren.
Unser Tool der Wahl war hierbei das Operator SDK; dieses bietet die nötigen Tools und Bibliotheken, um auf einfache Weise neue Kubernetes-Ressourcen zu definieren, und auch mit ein paar Zeilen Go-Code die nötigen Controller zu schreiben, die dann innerhalb des Kubernetes-Clusters laufen und diese neuen Ressourcen verarbeiten.
Um beispielsweise ein TYPO3 innerhalb eines Projekts zu installieren, muss der zuständige mStudio Microservice lediglich noch eine App-Ressource über die Kubernetes-API erstellen (in der dann beispielsweise die gewünschte Version, der Installationspfad und das Zielprojekt angegeben werden). Der dahinterstehende Kubernetes-Operator übersetzt diese Spezifikation dann in ein Container Image, Volumes und die dazugehörigen Kubernetes-Deployments.
Ausblick
All diese Kubernetes-Plattformen laufen natürlich nicht mit Luft und Liebe. Auch Produkte, die nach außen „wolkig” aussehen, laufen im Hintergrund auf Servern und virtuellen Maschinen, kommunizieren über Netzwerke und benötigen Speicherplatz.
Im nächsten Teil dieser Blog-Reihe gehe ich darauf ein, wie der Infrastruktur-Unterbau unserer Hosting-Plattform eigentlich funktioniert (das umfasst beispielsweise, wie wir virtuelle Maschinen und Storage im Netzwerk bereitstellen) und wie wir darauf Kubernetes-Cluster bereitstellen. Gleichermaßen wird es aber auch darum gehen, welche Lektionen wir im Betrieb dieser Infrastruktur gelernt haben, und wie wir unsere Infrastruktur weiterentwickeln werden, um einen effizienten Betrieb der Plattform und gleichermaßen eine hohe Verfügbarkeit und Resilienz sicherstellen können.
Nice to know
Der Artikel basiert auf einer Präsentation, die Martin bei den TYPO3 Developer Days 2024 gehalten hat. Dabei ging es sowohl um die Architektur der Plattform, als auch um unsere Learnings aus der Entwicklung und dem Betrieb der Plattform. Die Slides der Präsentation kannst du weiterhin anschauen.