Effiziente Entwicklungsumgebungen: Warum wir Kubernetes durch Virtual Machines ersetzt haben
Für die Entwicklung unserer TYPO3- und Shopware-Projekte setzen wir eine Abnahmeumgebung ein: Dabei erstellen wir aus dem aktuellen Programmcode der Entwicklung eine Kopie der Website, inklusive des Live-Datenbestands. So können wir und unsere Kunden neue Features direkt testen, ohne durch veraltete oder Testdaten abgelenkt zu werden.
Dieser Prozess ist in unserer GitLab-Instanz vollständig automatisiert: Beim ersten Commit in einem Feature-Branch wird die Umgebung erstellt, und mit jedem weiteren Commit wird der Code aktualisiert. Sobald der Branch freigegeben und gemerged ist, wird die Umgebung automatisch wieder abgebaut.
Bis vor einigen Monaten liefen diese Umgebungen auf einem externen Kubernetes-Cluster. Hierfür haben wir für jede Instanz Docker-Images erstellt und mittels passender Helm-Charts bereitgestellt. Allerdings gab es gleich mehrere Probleme. Zum einen ist es mit dem Einkauf eines Clusters alleine nicht getan. Um sich nicht zu weit vom bestehenden Produktivsetup zu entfernen, muss Storage so bereitgestellt werden, dass er sich für die Applikation wie gewohnt verhält. Das ist allerdings dann nicht wirklich “Kubernetes-nativ”. Hierdurch entstand für den Betrieb einer solchen Umgebung direkt zusätzlicher Aufwand, der mit dem eigentlichen Thema “Betrieb von Testsystemen” nichts zu tun hat.
Zum anderen ist das Debugging von Problemen – und darum geht es bei Testsystemen ja vordergründig – in einer solchen Umgebung deutlich aufwändiger. Cloud-native Applikationen sind im Regelfall vollständig zustandslos. Sie können in Read-Only-Umgebungen laufen, weil sie davon ausgehen, dass z.B. Datenbanken außerhalb liegen und Logfiles von einem externen Loganalysesystem entgegengenommen werden sollen. Für verteilte Systeme ist das auch absolut sinnvoll und ermöglicht es überhaupt erst, im großen Stil horizontal zu skalieren. Ein Kubernetes-Pod, der eine Applikation ausführt, kann jederzeit durch einen zweiten ersetzt und dann weggeräumt werden. Wann genau das passiert, ist ohne Tiefenwissen auch nicht unbedingt erkenn- oder steuerbar. Das macht es schwierig, Fehler zu analysieren. Außerdem ist es auch in Entwicklungsphasen damit nur schwer möglich, etwa CLI-Commands einfach auszuführen oder in Logdateien hineinzuschauen.
Natürlich könnte man sich per kubectl zum nächsten Pod verbinden und das dort tun. Es entsteht aber eine hohe Hürde für das Entwicklungsteam, die stark auf die Developer Experience (DX) drückt.
Kompliziert ist nicht immer besser
Ein anderes Problem ergab sich aus der Tatsache, dass per Kubernetes betriebene Instanzen architektonisch weit entfernt von unseren Produktivsystemen sind. Das führte zu subtil verändertem Verhalten der Applikationen, sobald sie auf dem Cluster liefen, die in der Praxis ebenfalls schwer zu debuggen waren.
Aus diesem Grund sind wir ans Reißbrett zurückgegangen und haben uns das eigentlich zu lösende Problem erneut angeschaut. Hierbei ging es vordergründig darum, Testinstanzen dynamisch bereitzustellen und Compute- sowie Storageressourcen möglichst nur in Anspruch nehmen zu müssen, solange diese Instanzen benötigt werden.
Nun bieten aber praktisch alle Infrastrukturprovider APIs an, um Ressourcen programmatisch anzulegen und wieder abzubauen. Eine gemeinsame Abstraktion, die bei Bedarf auch den einfachen Austausch eines Providers durch einen anderen zulässt, ist das Terraform-Projekt. Terraform ermöglicht es uns, skriptgesteuert z.B. VMs bei verschiedenen Providern mit definierten Eigenschaften zu erzeugen. Der Provider kümmert sich um das initiale Aufsetzen der VM und installiert gewöhnlich ein Image eines vorher ausgewählten Betriebssystems, z.B. einer Linux-Distribution.
Was dann noch fehlte, war das automatisierte weitere Aufsetzen und Konfigurieren der nötigen Software, um unsere Testinstanzen laufen zu lassen, sprich ein ganz gewöhnlicher PHP-basierter Webserver-Stack. Das ist allerdings etwas, das wir tagtäglich tun und das wir mittels Ansible in Zusammenarbeit mit Terraform im Kontext der Testinstanzen sehr gut automatisieren konnten.
Im Ergebnis haben wir jetzt wieder Umgebungen, die sehr viel ähnlicher dem sind, was sich in der Produktion abspielt. Die Systeme erlauben, genau wie die Produktivsysteme, einen direkten Zugriff per SSH, um etwa Fehler analysieren zu können, einen Blick in bestimmte Logs zu werfen oder um zu prüfen, ob bestimmte Prozesse laufen. Und obwohl es sich bei den so erzeugten Testinstanzen um durchaus produktionsreife Systeme handelt, bleiben wir in der Lage, diese Instanzen bei Bedarf anzulegen oder wieder abzubauen. Unser Ziel haben wir jedenfalls voll erreicht: Die Umgebungen laufen nun stabiler und schneller.
Dieser Wechsel hat uns wieder einmal gezeigt, dass nicht alles, was technisch möglich ist, auch immer sinnvoll sein muss. Kubernetes ist rückblickend für das Szenario „1:1 Kopie einer Webapplikation“ nicht unbedingt die beste Wahl. Wir überprüfen regelmäßig die von uns genutzten Techniken und passen sie bei Bedarf an. So entwickeln wir uns technisch und konzeptionell weiter – zum Vorteil unserer Kunden.
Das heißt jedoch nicht, dass Kubernetes für uns keinen Nutzen hat. Wir setzen es an anderen Stellen weiterhin erfolgreich ein, beispielsweise für den Betrieb unserer GitLab-Runner.
Einsparungen von 65 %
Gestern erhielten wir die zweite Monatsabrechnung für unsere neue Infrastruktur. Im Vergleich zu den Monaten mit Kubernetes sparen wir nun 65 % der Kosten. Zugegeben, das war nicht unser ursprüngliches Ziel, aber wir nehmen diesen Vorteil natürlich gerne mit. :-)
Wir freuen uns, wenn Ihr diesen Beitrag teilt.
Kommentare
Keine Kommentare gefunden.