SaaS ohne Kubernetes: Schwimmen gegen den Strom
Immer noch ist Kubernetes irgendwie "cool" – obwohl sich im Gespräch mit Kolleg:innen die Stimmen mehren, die den hohen Aufwand beklagen, der in Betrieb und Wartung involviert ist. Hier ist noch eine solche Stimme: Anhand konkreter Beispiele technischer Schwierigkeiten will ich erzählen, warum wir uns nach dreieinhalb Jahren entschieden haben, Kubernetes wieder zu verlassen.
Ich bin Geschäftsführerin eines 2019 gegründeten Software-Startups. Unser Produkt ist eine python-basierte general-purpose Automatisierungs- und Integrationsplattform. Es wurde ursprünglich als reine SaaS-Lösung konzipiert. Unsere Vorstellung war, dass wir hunderte Workspaces für unsere Kunden in unserer Cloud hosten und als Managed Service zur Verfügung stellen.
Von Beginn an legten wir das Produkt deshalb auf Skalierbarkeit aus: mit horizontal skalierbaren Microservices in Docker-Containern. Zur Automatisierung der bedarfsgesteuerten Skalierung entschieden wir uns – natürlich – für Kubernetes (K8s). Schließlich ist K8s damals wie heute die marktführende und bekannteste Orchestrierungsplattform für Containerverwaltung und -bereitstellung und wird in unserem Umfeld auch von vielen anderen SaaS-Startups genutzt.
Doch: Auch auf einer breiten Welle surft es sich manchmal nicht so gut.
Drei Jahre später zogen wir deshalb einen Schlussstrich: Kubernetes flog raus.
Im ersten Teil dieses Erfahrungsberichtes erfahren Sie alle Details über die technischen Schwierigkeiten und die monetären Kosten, die Kubernetes mit sich brachte: Von der Umstellung von Docker auf containerd über AppArmor-Kompatitiblitätsproblemen bis zu nginx-ingress-admission-webhook-Ärger. Selbst als Managed Services wurde die Nutzung ressourcenintensiv und aufwändig, die vollständige Automatisierung des Clusters als "cattle" zudem sehr komplex.
In Teil zwei komme ich auf den Mehrwert zu sprechen. Den Mehrwert, den wir erwartet haben und den, den wir tatsächlich realisieren konnten. Spoiler: Ernüchternd wenig.
Zum Schluss das Fazit: Was bleibt? Was bringt Kubernetes? Was nicht? Und wie geht die Reise weiter?
Technische Schwierigkeiten, monetäre Kosten – oder: wie man trotz Ausdauer langsam untergeht
Zu Beginn war die Nutzung des Managed Service für K8s bei Google gratis. Natürlich war für die genutzten Compute-Ressourcen zu zahlen, es fielen aber keine eigenen Managed-Service-Gebühren an. Mit der Zeit führte Google dann eigene K8s-Managed-Service-Gebühren ein und erhöhte diese schrittweise. Sie machen aktuell 7-8 Prozent unserer gesamten Kosten bei Google Cloud aus.
Deutlich schwerwiegender als die monetären Kosten waren bei uns jedoch die technischen Schwierigkeiten, die wir mit K8s erlebten.
Problem 1: Umstieg auf Google Cloud
Beim Umstieg dachten wir darüber nach, ob wir selbst hosten oder einen Managed Service nehmen sollten.
Aus zwei Gründen war diese Entscheidung schnell gefällt:
- Im Gründungsteam waren wir nur zwei Personen. Wir hatten damals schon oft gehört, dass es sehr aufwändig wäre, K8s selbst zu betreiben.
- Betrieb und Operations sind nicht unser Kerngeschäft. Als kleines Team lagern wir so viel wie möglich aus, was nicht im Kern unseres Unternehmens oder Produktes steht.
Wir beschlossen also, ein Managed Service zu nutzen.
2019 gab es erst sehr wenige Angebote für Managed K8s. Wir hatten bis zu dem Zeitpunkt Amazon Web Services (AWS) als Cloud-Provider genutzt. Amazon hatte zwar bereits das Amazon EKS Service im Portfolio, das war aber aus unserer Sicht noch unreif und sehr aufwändig in der Bedienung. Unsere Recherche ergab, dass Google Cloud ein deutlich reiferes und komfortableres Managed K8s Service anbot. Unverzagt im Optimismus und Enthusiasmus unseres damals noch jungen Unternehmertums beschlossen wir, von AWS nach Google Cloud umzuziehen. Da unser Produkt vollständig containerisiert war, erwarteten wir wenige Probleme. Und tatsächlich war der Umzug schnell erledigt. Fast vollständig...
Wir haben heute noch "Reste" auf AWS: Unser DNS läuft weiterhin dort. Von den monetären Kosten überschaubar ist es dennoch unbequem, hier einen weiteren Anbieter mit eigenem Log-in nur für so einen kleinen Service zu nutzen. Da der Umstieg aber aufwändig wäre, werden wir AWS wohl noch einige Zeit weiter mit "herumschleppen".
Als junges Unternehmen haben wir damit bereits kurz nach der Gründung gelernt, dass es zwar einfach ist, neue Software zu nutzen – sie wieder zu verlassen allerdings deutlich schwieriger ist.
K8s lief aber wie erwartet und die Skalierung funktionierte wunderbar. Der nächste Schritt der Saga ergab sich ein gutes Jahr später. Im Sommer 2020 informierte uns Google darüber, dass sie Docker nicht mehr langfristig in ihren K8s Nodes unterstützen würden.
Problem 2: Docker Runtime Deprecation
Mit K8s 2.19 empfahl Google noch sanft den Umstieg auf containerd. Mit K8s 1.20 wurde es ernst: Google kündigte die Deprecation der Docker Runtime an. Docker ist eine populäre und leichtgewichtige Containerisierungstechnologie. Aufgrund der großen Verbreitung und Beliebtheit von Docker entschied sich K8s zu Beginn, voll auf Docker zu setzen – es zeigten sich jedoch bereits früh in der Entwicklungsgeschichte der beiden Technologien erste Risse und es kam schließlich dazu, dass K8s Docker seine Unterstützung entzog [1].
Inzwischen in Version 1.24 hat es K8s immer noch nicht geschafft, die Unterstützung für Docker ganz einzustellen. Viele und große Unternehmen haben auf Docker in Kombination mit K8s gesetzt und der Druck auf K8s ist enorm, die Kompatibilität aufrechtzuerhalten. Wir halten aber durchaus große Stücke auf die Technologie-Empfehlungen von Google. Schon nach der ersten Ankündigung beschlossen wir, uns über das Thema schlau zu machen. Google empfahl den Umstieg auf containerd – was nichts anderes ist, als der Docker daemon unter einem anderen Namen, der von Docker an die Cloud Native Computing Foundation (CNCF) gespendet wurde. Dort wird containerd zur optimierten Kompatibilität mit K8s weiterentwickelt.
Ein Umstieg macht aus technischer Sicht also durchaus Sinn. Noch dazu, da wir keine docker-spezifischen Funktionalitäten verwendeten, die containerd nicht unterstützt. Also stiegen wir um. Mit Umwegen...
Problem 3: COS und AppArmor vertragen sich nicht
Google Cloud bietet für die K8s Cluster Nodes verschiedene Betriebssysteme an: Es kann das google-eigene Container-Optimised OS (COS), Ubuntu oder Windows gewählt werden. Google empfiehlt COS, welches sehr schlank ist und, wie der Name sagt, für containerisierte Applikationen optimiert wurde. COS läuft z. B. auch auf Google Chrome Books.
Bisher hatten wir unsere Nodes mit Ubuntu betrieben. Unsere Überlegung: Für den Umstieg auf containerd mussten wir ohnehin einen brandneuen K8s Cluster erstellen. Das wäre eine gute Gelegenheit, gleich andere Best-Practice-Empfehlungen umzusetzen und beschlossen, zusätzlich von Ubuntu auf COS umzusteigen. Da wir unsere Anwendungen ja in Containern betreiben, die unabhängig vom darunterliegenden Betriebssystem funktionieren sollten, erwarteten wir keine Probleme, sondern erhofften uns Vereinfachung und Verschlankung. Sie ahnen es schon: so einfach war es leider nicht. Es ergaben sich unerwartete und massive Schwierigkeiten mit AppArmor.
Ohne einen grünen Zweig zu finden, beschlossen wir, das Problem ungelöst zu lassen.
AppArmor regelt als Mandatory Access System Zugriffsrechte von Anwendungen. Ziel ist, das Betriebssystem und Applikationen vor internen und externen Angriffen zu schützen. Wir nutzen AppArmor intensiv zur Umsetzung sehr strenger Ausführungseinschränkungen in den Sandbox-Umgebungen unserer Plattform, in denen Code von Anwendern ausgeführt wird.
Der Umstieg von Docker auf containerd brauchte zwar etwas Zeit, verlief aber reibungslos. Nur: Als wir dann im neuen Cluster starteten, schlugen unsere Tests sofort an. AppArmor wurde zwar geladen, schien aber nicht zu funktionieren. Schlimmer noch: AppArmor lieferte keinerlei Fehlermeldungen. Der AppArmor-Prozess lief, die Policies waren hinterlegt, wurden aber nicht enforced – silently! Hätten wir keine entsprechenden Tests, wäre uns das erst viel später aufgefallen. Wir waren schon recht tief in die Eigenheiten des COS eingestiegen und investierten einiges an Zeit und Ärger in die Fehlersuche. Leider war die Suche erfolglos: AppArmor und COS scheint keine häufige Kombination zu sein. Ohne einen grünen Zweig zu finden, beschlossen wir deshalb, das Problem ungelöst zu lassen. Stattdessen stiegen wir wieder auf Ubuntu um. Also: neuer Cluster, neues Glück. Mit Ubuntu und containerd lief bald alles wieder reibungslos.
Es wurmt uns bis heute, dass wir es nicht schafften, herauszufinden, was die Ursache des Fehlverhaltens war. Es fühlte sich wie Scheitern an. Gleichzeitig war der Ärger groß, dass wir sehr viel Zeit und Nerven investiert hatten, nur dafür, dass nachher alles genauso lief wie vorher. Der Umstieg von Docker auf containerd wurde uns von Google aufgezwungen, hatte für uns aber keinerlei positive Effekte.
Problem 4: Network Policies
Network Policies regeln den Zugriff von Applikationen auf Netzwerke. Leider bieten die normalen K8s Network Policies nicht alle Möglichkeiten, die wir brauchen: Reihung/Priorisierung von Policies ist nicht möglich, es können ausschließlich "allow-" und keine "deny"-Regeln gesetzt werden. Das kann zu sehr langen Konfigurationslisten führen. Zusätzlich können keine Regeln für log actions gesetzt werden.
Nach Recherche verschiedener Lösungen stießen wir schließlich auf ein Open-Source-Tool, das die Konfiguration von komplexeren Network Policies für K8s Cluster ermöglicht: Calico [2]. Schnell mussten wir feststellen, dass Calico zwar vieles kann, die Dokumentation aber mehr als dürftig ist. Wir verbrachten viel Zeit damit, Funktionalität zu reverse-engineeren – also durch Versuch und Irrtum die spezielleren und nicht dokumentierten Regeln der Konfiguration zu lernen.
Ein weiterer sehr unbequemer Aspekt von Calico ist das Fehlen einer REST-API. Calico kann nur über ein Command Line Interface (CLI) konfiguriert werden. Um die Konfiguration zu automatisieren, müssen wir also eine eigene VM hochfahren, auf der wir das Calico-CLI hochspinnen, config-Files hinkopieren, über das CLI an Calico "füttern", welches dann die Konfiguration auf den Kubernetes-Cluster anwendet. Nicht gerade einfach.
Fazit: Obwohl K8s inzwischen eine sehr verbreitete Technologie ist, gibt es für spezielle Konstellationen erst wenige und unreife Tools. Das ist im Endeffekt schlicht eine Folge der Jugend der Technologie. Mit Sicherheit entwickeln sich die Standard-K8s-Network-Policies weiter, sodass diese früher oder später alle Optionen bieten, die wir brauchen. Oder Calico wird entsprechend weiterentwickelt, um diese Lücke zu füllen und wird früher oder später wohl auch eine REST-API und bessere Dokumentation anbieten. Aktuell muss man weiterhin damit rechnen, dass manche Aspekte der K8s-Konfiguration sehr aufwändig sind.
Problem 5: Lokales Testen
Eine direkte Konsequenz der Entscheidung für einen Managed Service ist, dass wir diesen natürlich nur in der Cloud zur Verfügung haben. Die lokalen Instanzen am eigenen Rechner, die unsere Entwickler:innen für ihre tägliche Arbeit nutzen, laufen ohne K8s. Da sie nicht skaliert werden müssen, hat das funktional keine Auswirkungen. Für das Testen jedoch sehr wohl, da unser Produkt ja mit K8s kompatibel bleiben muss.
Gerade das Testen von Network Policies und die Entwicklung von Funktionalität, die mit Netzwerkzugriff in Zusammenhang steht, ist damit deutlich komplizierter, da nur die remote CI/CD-Pipeline in der Cloud für das Testen genutzt werden kann. Jeder, der sich schon einmal über die langen Ladezeiten der K8s-Logs in der Google-Cloud-Management-Konsole geärgert hat, weiß, wie mühsam es ist, damit zu arbeiten. Ganz besonders, da die Calico-Dokumentation so dürftig ist, braucht es hier oft mehrere Versuch-und-Irrtum-Anläufe bis eine Änderung der Konfiguration den gewünschten Effekt hat und an die neue Funktionalität angepasst ist. Da unsere Entwickler:innen K8s-spezifische Aspekte wie z. B. die Network Policies lokal nicht testen können, ist dieser Schritt sowohl langwierig als auch unbeliebt.
Immer wieder haben wir überlegt, z. B. über einen lokalen Simulator wie micro-K8s auch lokales Testen von K8s-Aspekten zu ermöglichen. Aufgrund des hohen Aufwands haben wir uns aber immer wieder dagegen entschieden. Stattdessen ist der Aspekt "kann nicht lokal getestet werden" ein weiterer Punkt auf der länger werdenden Liste, die K8s bei unseren Entwickler:innen unbeliebt macht.
Problem 6: Validierung von Nginx-Ingress-Routen
Die Konfiguration von Ingress-Routen in Nginx ist bekannter Weise brüchig: Bereits eine inkorrekte Route kann dazu führen, dass die gesamte Konfiguration nicht mehr funktioniert und das gesamte Routing fehlschlägt. Um dem vorzubeugen, bietet Nginx Validierungs-Webhooks an: Endpunkte, an die neue Routen geschickt werden, welche diese validieren und zurückgeben, ob sie korrekt sind und funktionieren würden oder nicht. Ein sehr praktisches und sinnvolles Feature.
Leider haben uns auch hier K8s bzw. die Calico Network-Policies einen Strich durch die Rechnung gemacht. Auch nach vielen Versuchen – die wie oben beschrieben aufwändig auszuprobieren waren – haben wir es schlicht nicht geschafft, den Zugriff auf Nginx Validierungs-Webhooks zu erlauben. Schlussendlich haben wir die Webhooks deaktiviert.
Da wir neue Ingress-Routen ausschließlich automatisiert und nach fixem Schema anlegen, ist die Validierung der Routen kein essentieller Schritt für uns. Wir arbeiten seit mehreren Jahren ohne die Validierung und haben keine Probleme mit der Nginx-Konfiguration. Schön ist diese Lösung dennoch nicht. Vor allem, da wir uns sehr scheuen, irgendwelche Änderungen an diesem Punkt vorzunehmen – seien es Updates von Nginx oder Änderungen in der automatisierten Anlage von Ingress-Routen.
Problem 7: Clusterautomatisierung ist komplex
Eigentlich wollen wir das Deployment unserer K8s-Cluster vollständig automatisieren, zum Beispiel für eine wöchentliche Rotation. Viel Arbeit ist in diesen Prozess gesteckt worden, ganz am Ziel sind wir aber immer noch nicht. Aus technischer Perspektive ist das größte Hindernis die Komplexität der Konfiguration der vielen Einzelteile, die für das Deployment eines gesamten Clusters inklusive darin laufender Komponenten und Systeme notwendig sind. Von händisch gepflegten Config-Files haben wir uns zwar schon lange verabschiedet, ein dediziertes Configuration-Management (CM) oder Master-Data-Management (MDM) ist aber auch noch nicht im Einsatz. Aktuell pflegen wir unsere Konfigurationen in einer PostgreSQL-Datenbank.
Das ist zwar für den Großteil unserer Prozesse mehr als ausreichend, skaliert aber mit steigender Komplexität nur schlecht, da für neue Konfigurations-Konstellationen neue Tabellen angelegt und bei Bedarf wieder geändert werden müssen. Hier stellte sich die Kosten-Nutzen Frage: Wie viel Zeit wären wir bereit, in die Automatisierung des Cluster Deployments zu investieren? Was erhofften wir uns dadurch?
Die Kosten konnten mir meine Techniker gut beziffern.
Der Nutzen? Der sollte darin bestehen, dass bei regelmäßiger Rotation des Clusters Probleme – z. B. mit neuen Versionen von einzelnen Komponenten – früher erkannt werden können. Damit sollte die häufigere Behebung von einzelnen Problemchen weniger ins Gewicht fallen als das einmalige Lösen vieler verschiedener Probleme, die sich über längeren Zeitraum ansammeln, wenn ein Cluster nur selten rotiert wird.
Wie z. B. die – im Nachhinein betrachtet unkluge – Entscheidung, gleichzeitig den Umstieg von Ubuntu auf COS und von Docker auf containerd zu wagen. Hätten wir unseren Cluster häufiger neu aufgesetzt, hätten wir den Umstieg auf COS wohl schon sehr viel früher in Angriff genommen und die Probleme mit AppArmor zu dem Zeitpunkt bereits festgestellt. Anstatt erst später, als erst einmal nicht sofort klar war, welche der Änderungen zu den AppArmor-Problemen geführt hatten und diese Probleme das Erreichen des eigentlichen Ziels – Umstieg auf containerd – deutlich verzögerten.
Der Nutzen ließ sich also damit zusammenfassen, dass wir uns dann noch häufiger damit auseinandersetzen müssten, Probleme im K8s-Cluster- Deployment zu reparieren. Auch wenn konzeptionell klar ist, dass es sinnvoll wäre, war zu dem Zeitpunkt der Frust bereits so groß, dass wir uns dennoch dagegen entschieden, in die saubere Automatisierung des Cluster-Deployments noch weiter zu investieren. Stattdessen stießen wir den Gedanken an, was uns K8s wirklich bringt – und ob wir ohne es nicht alles in allem besser da stünden.
Der Mehrwert von Kubernetes
Was haben wir denn nun davon, K8s zu nutzen? An erster Stelle steht jedenfalls, dass die automatische Skalierung von Ressourcen über K8s wunderbar funktioniert. Das Tool tut, was es tun soll. Da das der Grund war, warum wir es überhaupt einsetzen, sollte dies den größten Mehrwert darstellen.
Hier kommt unsere spezielle Situation ins Spiel, die mit K8s nichts zu tun hat: Leider brauchen wir die Skalierbarkeit aktuell kaum. Als wir uns für die Nutzung von K8s entschieden, boten wir unser Produkt ausschließlich als SaaS-Lösung an. Aber: Schon unser erster Kunde fragte nach Möglichkeiten einer On-premise-Installation. Also entwickelten wir bald auch ein On-premise-Angebot. Hier war von Beginn an klar die Anforderung, dass ein On-premise-Betrieb ohne K8s auskommen können muss. Betrieb von K8s als Voraussetzung wäre schlicht wegen des involvierten Aufwandes für viele unserer Kunden ein No-Go.
Ein ernüchterndes Ergebnis, das zu der Entscheidung führte, uns von K8s zu trennen.
Aktuell nutzen rund die Hälfte unserer Kunden das Produkt ausschließlich on-premise. Ein weiteres Viertel nutzt eine Kombination aus on-premise und SaaS-Workspaces und nur ein Viertel unserer Kunden rein als SaaS. Dazu kommt, dass sich das SaaS-Angebot als ein Einstiegsmodell herausgestellt hat: Da es einen schnellen Einstieg erlaubt, beginnen manche Kunden mit einer SaaS-Instanz, die sie in weiterer Folge um eine On-premise-Instanz erweitern oder ganz auf on-premise umsteigen. Damit ist die Auslastung unserer SaaS-Infrastruktur deutlich geringer und weniger variabel, als wir angenommen hatten. Damit bringt uns die automatische Skalierbarkeit von K8s nur wenig.
Ein positiver Nebeneffekt ist das Wissen, das wir im Team rund um K8s aufgebaut haben. Betrachtet man das komplette Spektrum der Stakeholder, hat es uns auch hinsichtlich Bewerbern und Arbeitsmarkt etwas gebracht. Kubernetes in Stellenausschreibungen zu erwähnen klingt attraktiv und innovativ.
Insgesamt aber ein ernüchterndes Ergebnis, das zu der Entscheidung führte, uns von K8s zu trennen.
Kubernetes – Auf der Welle surfen oder nicht?
Was bleibt? Kubernetes ist eine tolle Sache. Es ist ein großartiges Open-Source-Projekt mit genialen Features. Viele Unternehmen profitieren von Kubernetes. Besonders für SaaS-Angebote mit hoher Grundauslastung und starker Variation der Last ist K8s ein absolutes Muss. Auch wir schließen eine zukünftige Nutzung nicht aus.
Wichtig ist aber auch eine ehrliche Analyse, ob Kubernetes überhaupt Sinn macht. Für uns fällt die Kosten-Nutzen-Rechnung aktuell negativ aus. Daher schwimmen wir gegen den Strom der wachsenden K8s-Nutzung unter SaaS-Anbietern und investieren stattdessen, um wieder von K8s weg zu kommen.