Nachhaltige Softwarebereitstellung in Kubernetes

(Bild: iX)
Serverless Deployments mit Knative ermöglichen einen bedarfsgerechten, nachhaltigen Softwarebetrieb. Eine Migration ist herausfordernd, aber gut machbar.
Nachhaltigkeit in der Softwareentwicklung wird immer wichtiger â insbesondere im Kontext der Anwendungsbereitstellung in der Cloud. Steigende Energiepreise motivieren immer mehr Unternehmen, ĂŒber nachhaltigere Softwarearchitektur nachzudenken und ihre auf Computing zurĂŒckzufĂŒhrenden CO2-Emissionen detaillierter zu ĂŒberwachen.
Im Cloud-Native-Umfeld gibt es immer mehr Werkzeuge und Methoden, um beispielsweise die Nachhaltigkeit einer Software durch nachfrageorientierte, bedarfsgerechte Skalierung zu verbessern. Der Artikel zeigt anhand eines konkreten Fallbeispiels, wie Entwicklerinnen und Entwickler mithilfe der Open-Source-Software Knative [1] eine containerisierte Anwendung in Kubernetes in einen dynamisch skalierbaren Serverless-Dienst ĂŒberfĂŒhren können, der sich in Zeiten fehlender Nachfrage sogar komplett abschaltet.
Bedarfsgerecht skalieren fĂŒr mehr Nachhaltigkeit
Knative basiert auf Kubernetes, das sich als RĂŒckgrat moderner containerisierter Anwendungslandschaften etabliert hat. Kubernetes bietet viele Möglichkeiten zum Automatisieren der Bereitstellung, Skalierung und Verwaltung von Containeranwendungen. Trotz seiner FlexibilitĂ€t fehlt Kubernetes jedoch die FĂ€higkeit, Serverless-Anwendungen bereitzustellen. Unter anderem erachtet die Green Software Foundation das zugrunde liegende Bereitstellungsmuster als nachhaltig, bei dem Anwendungen sich basierend auf der aktuellen Nachfrage skalieren und gegebenenfalls auch komplett abschalten lassen.
Soll eine einfache Beispielanwendung aus einem standardmĂ€Ăigen Kubernetes-Deployment in einen Knative-Service migriert werden, gilt es, die folgenden QualitĂ€tskriterien zu berĂŒcksichtigen:
- Die Migration sollte möglichst wenige Anpassungen am Quellcode erfordern. Nur dadurch lĂ€sst sich sicherstellen, dass sich gröĂere Anwendungen einfach migrieren lassen, ohne zu hohe Kosten fĂŒr einen Umbau der Anwendungslandschaft zu verursachen.
- Die in der existierenden Anwendungsumgebung etablierte Praxis fĂŒr Logging, Monitoring und Tracing sollte weiterhin nutzbar sein. Um eine einfache Migration zu ermöglichen, ist es daher notwendig, die KompatibilitĂ€t von Knative mit den vorhandenen Tools sicherzustellen. Das im Folgenden skizzierte Beispiel baut daher auf der weit verbreiteten Monitoringsoftware Prometheus auf.
- Existierende Endpunkte der Beispielanwendung sollen mit Knative nach der Migration unter denselben Routen verfĂŒgbar sein. In gröĂeren Anwendungslandschaften sind Routen hĂ€ufig statische Adressen der Services. Wenn nach einer Migration ein Service plötzlich unter einer anderen URL verfĂŒgbar ist, kann dies Probleme verursachen, insbesondere wenn die Kommunikation nicht ĂŒber ein API-Gateway oder eine Service-Registry entkoppelt ist.
- Das Lastmuster der Beispielanwendung muss lĂ€ngere ZeitrĂ€ume ausweisen, in denen der Service nicht aufgerufen wird, es also keine Zugriffe durch Nutzerinnen und Nutzer gibt. Nur fĂŒhrt das Herunterskalieren der Anwendung auf null Replikas zu einem nennenswerten Ressourcengewinn.
Die zu migrierende Beispielapplikation besteht aus einem einfachen, zustandslosen Kubernetes-Deployment, das eine REST API ĂŒber einen Ingress bereitstellt. Der Prometheus-Operator soll die Metriken zur Ăberwachung der Applikation ĂŒber die Kubernetes Custom Resource ServiceMonitor erfassen â wie Abbildung 1 verdeutlicht.

Knative: Serverless-Dienste in Kubernetes
Das Open-Source-Projekt Knative zielt darauf ab, Serverless Workloads in Kubernetes auszufĂŒhren und zu verwalten. Dazu stellt es Funktionen fĂŒr die automatische Skalierung bereit â bis hin zu der FĂ€higkeit, Anwendungen auf null Pods herunterzuskalieren, um den Leerlauf von Pods zu vermeiden. Auch wenn die Ersparnisse pro Anwendung klein sein mögen, summiert sich der Effekt ĂŒber Tausende von Pods und eine Vielzahl von Anwendungen gerade in groĂen Clustern. Dabei ist zu beachten, dass Knative nur mit zustandslosen Anwendungen umgehen kann, die ausschlieĂlich ĂŒber HTTP kommunizieren.

Wie Observability, Platform Engineering und andere neue AnsĂ€tze Entwicklerinnen und Entwicklern ĂŒber den gesamten Software Development Lifecycle hinweg zu produktiverem Arbeiten verhelfen, zeigen die Artikel des neuen Sonderheftes iX Developer "Cloud Native" [2].
Um eine Serverless-Laufzeitumgebung innerhalb eines Kubernetes-Clusters zu schaffen, mĂŒssen mehrere Komponenten von Knative Serving zusammenarbeiten. Dazu zĂ€hlen insbesondere Service, Route und Revision:
- Service: Ein Knative-Service definiert eine Serverless-Anwendung und ist daher nicht mit einem Service in Kubernetes zu verwechseln. Beim Anlegen eines Knative-Services wird automatisch eine Route und eine Konfiguration erstellt.
- Route: Sie bestimmt, wie Anfragen an die verschiedenen Revisionen einer Anwendung geleitet werden. Es lĂ€sst sich beispielsweise festlegen, dass 90 Prozent der Anfragen an die aktuelle Version der Anwendung und die restlichen 10 Prozent an eine neue Version flieĂen sollen.
- Revision: Bei jeder Ănderung am Knative-Service erstellt Knative eine neue Revision. Jede Revision reprĂ€sentiert einen Snapshot des Codes und der zugehörigen Konfiguration.
Migration und Herausforderungen
Die Serverless-Migration erscheint auf den ersten Blick simpel, gilt es doch lediglich, das existierende Deployment in einen Knative-Service zu ĂŒberfĂŒhren, den der Knative Serving Operator dann zur Laufzeit in ein Kubernetes-Deployment umwandelt. Hinzu kommen schlieĂlich noch verschiedene Kubernetes-Dienste sowie eine â automatisch erstellte â Ingress-Ressource. Der bereitgestellte Service skaliert nun basierend auf den tatsĂ€chlichen Anfragen und schaltet sich vollstĂ€ndig ab, falls Anfragen ausbleiben â in der Standardeinstellung bereits nach 30 Sekunden. Damit wĂ€re die Migration abgeschlossen, sofern man die oben genannten QualitĂ€tskriterien unberĂŒcksichtigt lieĂe. Will man sie hingegen erfĂŒllen, ergeben sich noch einige Herausforderungen.
- Abruf der Applikationsmetriken durch Prometheus: In der Standardkonfiguration sammelt der Knative-Service keine Metriken von Prometheus. Soll er die Daten abrufen, ist dafĂŒr ein Start der Applikation erforderlich, da Prometheus die Metriken stets ĂŒber einen Pull-basierten Ansatz abruft. Prometheus ist standardmĂ€Ăig darauf ausgelegt, in regelmĂ€Ăigen AbstĂ€nden die Metriken der Ziel-Applikationen ĂŒber einen eigens dafĂŒr vorgesehenen Endpunkt abzurufen, zu speichern und fĂŒr weitere Auswertungen bereitzustellen. FĂŒr dauerhaft aktive Anwendungen ist das sinnvoll, aber es wĂŒrde die angestrebte Nachhaltigkeit bei Serverless-Applikationen konterkarieren.
- VerfĂŒgbarkeit des Service unter einem dedizierten Endpunkt: Der Endpunkt, unter dem der Knative-Service bereitstehen soll, lĂ€sst sich in der Regel nicht frei festlegen. In der Standardkonfiguration wird der Service immer nach dem folgenden Schema auĂerhalb des Clusters verfĂŒgbar gemacht:
<Service Name>.<ServiceNamespace>.<Cluster Url>
Dazu generiert Knative aus der Knative-Service-Definition automatisch Ingress- und Kubernetes-Service-Ressourcen. Daher lĂ€sst sich das Schema zwar anpassen, der tatsĂ€chliche Endpunkt eines einzelnen Service aber nicht frei bestimmen. - Dynamisches Hochskalieren der Anwendung: Auch das dynamische Hochskalieren einer Serverless-Anwendung aus dem ausgeschalteten Zustand heraus kann Probleme verursachen. Muss eine Anwendung erst noch starten, bevor sie auf eine Anfrage reagieren kann, dauert das Beantworten mindestens so lange wie der Applikationsstart. Dieser Kaltstart dauert je nach verwendeter Programmiersprache und Framework von wenigen Millisekunden bis zu mehreren zehn Sekunden. Die Beispielanwendung ist mit Spring Boot und Java 20 entwickelt. Sie benötigt ohne Optimierungen zwischen einer und zehn Sekunden, bis sie Anfragen beantworten kann â je nachdem, wie viel CPU-Leistung ihr zur VerfĂŒgung steht. FĂŒr die meisten AnwendungsfĂ€lle ist eine Antwortzeit von zehn Sekunden bereits inakzeptabel, etwa wenn Nutzerinnen oder Nutzer eine Anwendung ĂŒber einen Webbrowser aufrufen.
Herausforderungen meistern
FĂŒr die im Zusammenhang mit den definierten QualitĂ€tskriterien auftretenden Probleme gibt es LösungsansĂ€tze. Das Abrufen der Anwendungsmetriken lĂ€sst sich sogar auf zwei unterschiedlichen Wegen umsetzen.
Bei einem Pull-basierten Ansatz muss sichergestellt sein, dass ein Abrufen der Metriken nicht zu unnötigen Applikationsstarts fĂŒhrt, wenn die Applikation gerade ruht. Andererseits darf das Abrufen der Metriken nicht dauerhaft verhindern, dass Knative die Anwendung herunterskaliert. Beides lĂ€sst sich durch eine geeignete Porttrennung erreichen. Beim Start eines Pods öffnet Knative mehrere Ports in einem separaten Container, dem Queue-Proxy. Knative nutzt diese Ports, um Anfragen an die eigentliche Applikation zu leiten. Hierbei bleibt der durch den Applikationscode geöffnete Port bestehen. Ist eine Anfrage direkt an den Applikations-Port gerichtet, ignoriert Knative diese fĂŒr die Skalierung.
Beim Monitoring von Knative-Applikationen können Metriken verloren gehen, die zwischen dem letzten Abruf durch Prometheus und dem Skalieren der Applikation auf null geschrieben wurden. AbhĂ€ngig vom Anwendungsfall kann dieser Verlust verkraftbar sein, da sich das Abrufintervall in Prometheus konfigurieren lĂ€sst. WĂ€hlt man etwa ein Abrufintervall von 15 Sekunden, verliert man also maximal die Metriken in diesem Zeitfenster, sollte Knative die Anwendung kurz vor Ablauf der 15 Sekunden herunterskalieren. Muss jedoch sichergestellt sein, dass sĂ€mtliche Metriken an Prometheus gesendet werden, stöĂt der Pull-basierte Ansatz an seine Grenzen. Soll Prometheus beispielsweise auch das Auftreten schwerer Fehler ĂŒberwachen, und ein Fehlerevent tritt zwischen dem letzten Abruf der Metriken und dem Herunterfahren der Applikation auf, bleibt es im Monitoring unsichtbar. Gegebenenfalls notwendige MaĂnahmen zur Fehlerbehebung lassen sich nicht einleiten.
Alternativ lassen sich moderne Monitoring-Lösungen â darunter auch Prometheus â zum Abrufen von Metriken auch in einem Push-basierten Modus betreiben. Im Falle von Prometheus bietet sich dazu das Push-Gateway an. Es stellt einen Endpunkt zum Abliefern der Metriken bereit. In der Zielumgebung lĂ€uft mit dem Push-Gateway dann zwar dauerhaft eine weitere Komponente, da es aber die Metriken sĂ€mtlicher Serverless-Anwendungen entgegennehmen kann, spielt der zusĂ€tzliche Ressourcenverbrauch keine nennenswerte Rolle. Der Push-basierte Ansatz lĂ€sst sich in der Regel jedoch nicht ohne aufwendige Anpassung des Quellcodes umsetzen â und wird daher hier nicht weiter betrachtet.
Listing: DomainMapping
apiVersion: serving.knative.dev/v1alpha1
kind: DomainMapping
metadata:
name: my-route.example.org
spec:
ref:
name: Beispiel-App
kind: Service
apiVersion: serving.knative.dev/v1
Um einen Knative-Service zusĂ€tzlich zum standardmĂ€Ăig generierten Endpunkt auch ĂŒber einen weiteren bereitzustellen, lĂ€sst sich per DomainMapping ein benutzerdefinierter Endpunkt einrichten. DomainMapping ist eine Kubernetes-Ressource (Custom Resource) und steht in Knative bereits seit der Beta zur VerfĂŒgung. Ein Beispiel zeigt das Listing oben. Soll der Knative-Service nur unter einem benutzerdefinierten Endpunkt verfĂŒgbar sein, lĂ€sst sich das Erstellen des automatisch generierten Endpunktes deaktivieren. Dazu genĂŒgt es, dem Knative-Service eine Annotation hinzuzufĂŒgen, beispielsweise mit folgendem Konsolenbefehl:
kubectl label kservice ${KSVC_NAME} networking.knative.dev/visibility=cluster-local
Die Kaltstartproblematik lĂ€sst sich in der Regel nicht ohne Anpassungen des Quellcodes beheben. Der erforderliche Aufwand hĂ€ngt stark von der Programmiersprache und dem gewĂ€hlten Framework ab. Bei modernen Programmiersprachen und Frameworks ist die Wahrscheinlichkeit hoch, dass die Kaltstartzeit der Anwendung sich in einem akzeptablen Rahmen hĂ€lt. Das Java-Framework Quarkus [3] beispielsweise ist auf schnelle Startzeiten hin optimiert. Die diesem Artikel zugrunde liegende Beispielanwendung ist in Java 20 mit dem weit verbreiteten JVM-Framework Spring Boot implementiert. Diese Kombination fĂŒhrt in der Praxis regelmĂ€Ăig zu lĂ€ngeren Startzeiten.
Dieses Problem gehen Entwicklerinnen und Entwickler auf verschiedene Art und Weise an. Zum einen lassen sich die CPU-Limits und gegebenenfalls die Requests so anpassen, dass der Anwendung genĂŒgend Rechenleistung zur VerfĂŒgung steht, um einen möglichst raschen Applikationsstart zu erreichen. Da die Spring-Boot-Anwendung im spĂ€teren Regelbetrieb allerdings mit deutlich weniger CPU-Leistung auskommt, fĂŒhrt diese Vorgehensweise in typischen Kubernetes-Deployments zu einer ineffizienten Ressourcenauslastung. Als effizientere Alternative bietet sich daher an, die seit Spring 6.0 weiter optimierten AoT-Processing-Funktionen [4] (Ahead of Time) zu nutzen und die Anwendung ĂŒber ein natives Graal-VM-Image bereitzustellen. FĂŒr die Beispielanwendung lĂ€sst sich dieser Weg einfach umsetzen, bei gröĂeren Anwendungen ist jedoch mit höherem Anpassungsaufwand zu rechnen.

Vor- und Nachteile einer Serverless-Migration
Die aufgefĂŒhrten Herausforderungen bei der Migration der Beispielanwendung lieĂen sich in den meisten FĂ€llen einfach und ohne groĂen Aufwand lösen. FĂŒr das Monitoring und das Bereitstellen des Service unter einem bestimmten Endpunkt lassen sich Vorgehensweisen finden, die den gestellten QualitĂ€tsanforderungen genĂŒgen. Die Kaltstartproblematik hingegen ist abhĂ€ngig von der gewĂ€hlten Programmiersprache und erfordert einen höheren Aufwand.
Ist das Einsparen von Ressourcen das Hauptziel der Migration, ist zu beachten, dass Knative selbst auch CPU- und Arbeitsspeicher-Ressourcen bindet. Laut Dokumentation setzt eine produktive Installation sechs CPU-Kerne und sechs GByte Arbeitsspeicher voraus. Um einen positiven Einfluss auf die Nachhaltigkeit des Gesamtsystems zu erzielen, braucht es also eine kritische Masse an Anwendungen.
DarĂŒber hinaus ist Knative nur fĂŒr Anwendungen mit volatiler Anfragelast sinnvoll. Dazu bieten sich die im Artikel konkret behandelten Serverless-Anwendungen an, die sich komplett abschalten lassen. Aber auch Anwendungen, die im allgemeinen Betrieb dynamisch und weitgehend unvorhersehbar skalieren mĂŒssen, profitieren von der Möglichkeit, Deployments anhand der Rate der HTTP-Requests zu skalieren, statt anhand der CPU-Verbrauchs-Metriken, wie es mit horizontalen Pod-Autoscalern möglich ist. Weist eine Anwendung jedoch ein stabiles, nur seltenen Schwankungen unterworfenes Anfragelastmuster auf, lĂ€sst sich der Ressourcenverbrauch durch eine Migration mit Knative nur unwesentlich senken. In solchen FĂ€llen trĂ€gt Knative eher dazu bei, die KomplexitĂ€t des Systems unnötig zu erhöhen.
Neben den bisher genannten EinschrĂ€nkungen ist zu beachten, dass Knative ausschlieĂlich fĂŒr zustandslose Anwendungen geeignet ist. Es ist nicht möglich, ein persistentes Volume in einem Knative-Service zu mounten. AuĂerdem darf die Anwendung ausschlieĂlich ĂŒber HTTP auf einem bestimmten Port aufgerufen werden. Anwendungen, die Datenverkehr auf mehreren Ports empfangen oder ĂŒber andere Protokolle kommunizieren, lassen sich nicht mit Knative nutzen.
Fazit: Mehr Nachhaltigkeit mit Knative ist möglich
Aus Sicht von Entwicklerinnen und Entwicklern eignet sich Knative gut fĂŒr das Tuning von Anwendungen in Richtung Nachhaltigkeit. Es existiert eine umfassende und leicht verstĂ€ndliche Dokumentation sowie ein offizielles CLI-Werkzeug, mit dem sich Knative-Ressourcen verwalten lassen, was zu einer positiven Developer Experience beitrĂ€gt. Wer die Kubernetes-Distribution OpenShift einsetzt, profitiert zudem von einer UI-Integration, die die Entwicklungsarbeit mit Knative vereinfacht.
Knative empfiehlt sich als geeignetes Werkzeug, um Anwendungen unter bestimmten Voraussetzungen nachhaltiger zu entwickeln und zu betreiben. IT-Architektinnen und -Architekten, die sich mit dem Thema der Nachhaltigkeit beschĂ€ftigen, sollten den Einsatz von Knative in ihren IT-Umgebungen evaluieren. Wer bereits Kubernetes als Grundlage der eigenen internen Entwicklungsplattform im Unternehmen nutzt, dĂŒrfte Knative als passende ErgĂ€nzung zum Bereitstellen von Serverless-Anwendungen schĂ€tzen lernen.
Marius Stein
ist IT-Berater bei viadee mit ĂŒber 10 Jahren Erfahrung in der Entwicklung Cloud-nativer-Lösungen. Er treibt den Kompetenzbereich Cloud-Architekturen technisch und strategisch mit voran.
(map [5])
URL dieses Artikels:
https://www.heise.de/-9533674
Links in diesem Artikel:
[1] https://knative.dev/docs/
[2] https://www.heise.de/news/iX-Developer-Cloud-Native-ist-da-9357842.html
[3] https://www.heise.de/hintergrund/Quarkus-Der-Blick-ueber-den-Tellerrand-4532556.html?seite=2
[4] https://www.heise.de/hintergrund/Spring-Framework-6-verarbeitet-Native-Images-und-baut-auf-Jakarta-EE-9-oder-10-7342050.html
[5] mailto:map@ix.de
Copyright © 2023 Heise Medien