Kolumne: Olis bunte Welt der IT

Serverless noch nicht ganz überzeugend
Keine Kommentare

Ein großes Thema der Softwareentwicklung Anfang 2018 ist zweifellos „Serverless“. Ende Februar war ich bei der BASTA! Spring in Frankfurt, und dort wurde fleißig darüber diskutiert, in und außerhalb von Sessions, was zu zahlreichen Fragen führte. Zunächst einmal: Was bedeutet eigentlich „Serverless“? Keine Server mehr? Nur noch Clients? Natürlich nicht! Zumindest erscheint das im Moment natürlich, da sehr viele moderne Architekturkonzepte Server und Clients benötigen – wer weiß, was die Zukunft bringt?

Serverless bezieht sich von der Wortwahl her wohl auf die Idee, dass der Betrieb benötigter Server aus der Hand des Programmierers genommen wird, aus der Hand des softwareproduzierenden Betriebs insgesamt. In der Vergangenheit haben wir bereits den physikalischen Betrieb spezialisierten Anbietern überlassen, entweder bei einer Hosting-Firma oder beim Cloud-Anbieter, aber wir (also wir Programmierer und unsere Kollegen, die Admins) waren noch immer für die Systemeinrichtung und -pflege zuständig. Mit Serverless fällt letzterer Teil jetzt auch noch weg. Als einzige Verantwortlichkeit für uns bleibt der Code, den wir schreiben, die Funktionalität, die unser Unternehmen an den Markt bringt.

Gern unterscheidet man noch die Begriffe „Serverless“ und „FaaS“. Immerhin verwenden wir schon lange Technologien wie Dropbox, Google Pages und ähnliche, und diese sind auch manchmal in Softwaresysteme eingebunden – also „serverless“. Der moderne Trend, über den soviel geredet wird, dreht sich aber eigentlich mehr um „Faas“, also „Function as a Service“. Eine Funktion (ja, genau wie der Programmierer Funktionen versteht) wird zum Dienst! Darum geht’s. Ohne Server geht das natürlich nicht – tatsächlich haben Amazon und Co. zahlreiche Server extra dafür eingerichtet. Der Punkt ist: das ist nicht unser Problem. Wir brauchen nur den Code für eine Funktion, oder für viele Funktionen, aber eben nichts darüber hinaus.

FaaS ist verlockend. Einen direkteren Weg in die Cloud hat es noch nicht gegeben – einfach eine Funktion schreiben, hochladen, und der eigene Dienst ist online. Abgerechnet wird nach verbrauchter Rechenzeit, in Blöcken von einigen Millisekunden. Skalierung ist automatisch, da gibt’s wenig, was man tun kann, und nichts, das man tun muss.

Funktionen können klein oder groß sein, und es ist wichtig zu verstehen, dass größere Mengen Code zusammen mit der Funktion hochgeladen werden können. Sie können sich eine Funktion ganz einfach vorstellen: Ein paar Parameter werden empfangen, eine Berechnung wird ausgeführt (zum Beispiel), ein paar Werte werden zurückgeliefert. Andererseits hat AWS Lambda die Fähigkeit, eine ganze ASP.NET-Core-Anwendung, MVC oder Web API, laufen zu lassen. Dazu sind natürlich alle Controller, Views und anderer Code erforderlich, den der Entwickler für die Anwendung gebaut hat. Wie kann man denn so was als Funktion bezeichnen? Ganz einfach: der Einstiegspunkt in die programmierte Funktionalität ist wichtig.

Allein geht nichts

Eine Funktion, wenn sie in der Cloud bereitsteht, muss irgendwie aufgerufen werden. Die offensichtlichste Methode besteht darin, dies als Reaktion auf einen Netzwerkzugriff zu tun. Die Cloud-Umgebung erzeugt einen Trigger, der in einem HTTP-Endpunkt besteht. Immer wenn ein passender Request eingeht, wird eine bestimmte Funktion aufgerufen. Zum Beispiel könnte der Request GET http://myserver/mydataservice/47 in einen Aufruf meiner Funktion mydataservice mit dem Parameter 47 umgesetzt werden. Parameter des Requests, wie etwa die HTTP-Methode GET, sind für die Funktion ebenfalls abrufbar.

Das Beispiel der ASP.NET-Core-Anwendung ist somit einfach umsetzbar. Immerhin handelt es sich bei einem Webserver grundsätzlich um eine Funktion, logisch betrachtet: Requests gehen ein, werden verarbeitet und erzeugen Resultate. Die Idee, eine ganze Webanwendung als eine einzige Funktion zu betrachten, macht somit durchaus Sinn! Dabei ist hier mit der ganzen Anwendung natürlich der Teil gemeint, der etwa mittels HTTP über ein bestimmtes URL-Schema verfügbar gemacht wird.

Nun gibt es in einem Anwendungssystem fast immer weitere Bestandteile. Zum Beispiel gibt es vielleicht Datendienste, die getrennt von den HTML-Inhalten Informationen liefern, etwa aus Datenbanken abgefragt. Verantwortungstrennung ist noch immer eine Tugend, also sollte auch angesichts des oben beschriebenen AWS-Features nicht das gesamte Anwendungssystem mit allen zugehörigen Diensten in eine Funktion verpackt werden. Es gibt also mehrere Dienste, möglichst viele recht kleine, wie Microservices es nahelegen. Dadurch verbessern sich die Antwortzeit des Systems sowie das Skalierungsverhalten einzelner Komponenten.

Um diese Dienste alle zur rechten Zeit auf dem effizientesten Weg aufzurufen, bieten Cloud-Umgebungen abgesehen von den beschriebenen HTTP-Endpunkten zusätzliche Typen von Triggern an. So können Funktionen etwa aufgerufen werden, wenn Daten in den Cloud Storage hochgeladen werden, sich etwas an Datenbankinhalten ändert oder ein Frontend-Gerät ein bestimmtes Voice-Kommando eines Endanwenders hört. Trigger und die Infrastruktur, die von der Cloud verfügbar gemacht wird und die Trigger auslöst, werden so zum wichtigen Bestandteil jedes Anwendungskonzepts.

Zusammenhalt ist wichtig

Im Englischen gibt es den Begriff Glue Code. Glue ist Klebstoff, und Glue Code ist der Code, der die eigentlich wichtigen Bestandteile meiner Anwendung zusammenhält. In einer größeren Anwendung habe ich die Möglichkeit, all meinen Glue Code selbst zu schreiben, aber ich kann auch nach Mitteln wie Dependency-Injection-Containern greifen, um in diesem Bereich Arbeit zu sparen – Letztere bieten einen konfigurierbaren Weg, die „richtigen“ Komponenten meiner Anwendung zu identifizieren, zusammenzubauen und aufzurufen. DIContainer bieten einen Ersatz für Glue Code, und in einem FaaS-Konzept übernimmt die Cloud-Infrastruktur diese Rolle.

Dieses neue Verständnis führt für die Entwickler eines FaaS-basierten Anwendungssystems zu weiteren Umstellungen. Wenn etwa eine Funktion getestet werden soll, stellt sich das je nach verwendetem Trigger schwieriger dar als bisher. Ein HTTP-Endpunkt lässt sich zu Testzwecken leicht simulieren oder einfach weglassen. Wenn allerdings eine Funktion von einem Trigger der Cloud-eigenen Message Queue aufgerufen wird, stehen Daten in einem bestimmten Format zur Verfügung, das von der Funktion entsprechend verarbeitet werden muss. Zu Testzwecken muss dieses Format nachgebaut werden – ein zusätzlicher Schritt, der nicht immer einfach ist und meiner Erfahrung nach später zu weiteren Komplikationen führen kann, wenn das Datenformat recht komplex ist und sich basierend auf externen Umständen ändert.

Zum Verständnis: natürlich ist es möglich, eine Funktion nach jeder Änderung des Codes in die Cloud zu laden und dort direkt im Kontext zu testen. Dazu muss allerdings sorgfältig eine Staging-Umgebung vorbereitet werden (eventuell eine pro Entwickler!), und selbst dann ist der erwähnte Fall „Trigger durch Message Queue“ nicht gerade trivial als Test zu automatisieren. Nachdem all diese Probleme gelöst sind, ist noch immer der Roundtrip ärgerlich lang! Moderne Testumgebungen führen meine lokalen Tests automatisch beim Speichern einer Codedatei aus, möglicherweise (wie etwa bei Jest) nur für den geänderten Code. Damit steht innerhalb von Sekundenbruchteilen nach dem Speichern Feedback zu den durchgeführten Änderungen zur Verfügung. Der Vorgang, geänderten Code in die Cloud-Umgebung zu laden und dann online einen Trigger auszulösen, der die Funktion ausführt, ist im besten Fall wesentlich langsamer.

Die Cloud-Anbieter kennen dieses Problem wie auch die ähnlichen Schwierigkeiten, denen man sich beim Debuggen einer FaaS-basierten Anwendung gegenübersehen kann. So gibt es derzeit, je nach Anbieter, ein mehr oder weniger vollständiges Portfolio von Emulatoren, die lokal eine Umgebung ähnlich der in der Cloud nachstellen und somit Tests und Debugging vereinfachen. Allerdings habe ich bereits selbst die Erfahrung gemacht, dass ein Emulator nicht unbedingt in seinem Verhalten präzise mit der „echten“ Umgebung übereinstimmt. Mit anderen Worten: Was im Emulator läuft, funktioniert nicht unbedingt auch in der Cloud.

Entwicklungsumgebungen noch unvollständig

In FaaS-basierten Anwendungen können Sie, wiederum je nach Anbieter, einen Teil der Dienste nutzen, die von der Cloud angeboten werden. Manche Dienste vertragen sich besser mit der Idee FaaS als andere, indem sie on demand genutzt werden können und auch entsprechend abgerechnet werden. Manche Dienste sind bisher noch nicht für diese neue Welt umgestellt worden und erfordern stattdessen das Einrichten einer virtuellen Maschine, die im alten Cloud-Stil dauerhaft läuft und auch so bezahlt sein will. Unter den FaaS-kompatiblen Diensten sind nicht alle mit Emulatoren abgedeckt, und der Aufbau einer lokalen Testumgebung ist umständlich und deckt nicht alle Umstände ab.

In diesem Bereich sind die Cloud-Anbieter gefordert. Ich wünsche mir, dass ich eine Cloud-Runtime für lokale Verwendung verfügbar hätte, die identisch mit der echten ist. Ich möchte ein Kommando wie docker run aws-runtime eingeben und dann gegen diese lokale virtuelle Cloud testen und debuggen – schnell, einfach, spezifisch pro Entwickler, offline, kostenfrei und ohne das Risiko, meine echte Laufzeitumgebung zu beeinträchtigen. Hoffentlich gibt es das bald!

Serverless wird gemeinhin als Weiterentwicklung von Microservices verstanden. Das ist nicht offensichtlich, denn immerhin kann ich auch ein komplexes System aus mehreren Diensten womöglich in einer Funktion implementieren, wie sich aus dem anfangs beschriebenen Beispiel zu ASP.NET Core ergibt. Der Wert des Konzepts ist allerdings größer, wenn Funktionen klein sind! Microservices sollten klein sein, damit sie leicht ersetzbar bleiben – nur so ergibt sich der erhoffte Vorteil, moderne Technologie auch für bestehende Projekte einfacher nutzbar zu machen oder besser vom Arbeitsmarkt profitieren zu können, indem vorhandene Skills nutzbar werden.

Leider ist Serverless allerdings nicht der beste Weg, sich die Vorteile von Microservices zu erkämpfen. Sicherlich kann ein Service kaum noch mehr „micro“ sein als eine einzelne Funktion. Allerdings ist diese Funktion, gerade weil sie so klein ist, auch in größerem Maße als bisher von ihrer Umgebung abhängig. Cloud-Infrastruktur war bisher, etwa in einem auf Docker basierenden Konzept, optional. Ich konnte zum Beispiel für meine Anwendung auf eine Datenbank in der Cloud meiner Wahl setzen, oder auch auf eine MongoDB-Instanz in einem Docker Image oder gar eine Datenbank in einer anderen Cloud. Mit Serverless habe ich diese Freiheit nicht mehr, oder zumindest muss ich damit extrem vorsichtig umgehen: Die von mir selbst betriebene MongoDB erfordert wieder Adminaufwand, skaliert nicht von selbst und wird zeitbasiert abgerechnet. Noch wichtiger: MongoDB, oder die Datenbank in der Cloud des Konkurrenten, ist vermutlich nicht ohne Weiteres mit den Triggern kompatibel, die für meine Funktionen benötigt werden.

Serverless erfordert also wieder eine Festlegung, diesmal auf eine Cloud-Umgebung. Das ist im Sinne von Microservices nicht erstrebenswert, und es wird jedenfalls notwendig, die vorhandenen Dienste in einer Cloud sehr genau zu untersuchen, bevor man sich dafür entscheidet.

Für einfache Zwecke ist es möglich, unabhängig von einer bestimmten Cloud serverless zu sein. Es gibt sogar Tools (etwa eines namens „Serverless“!), die versprechen, Funktionen beliebig in verschiedenen Clouds laufen zu lassen. Offensichtlich läuft ein solcher Ansatz aber auf den kleinsten gemeinsamen Nenner hinaus, denn aus Gründen des Wettbewerbs ist natürlich jedem Cloud-Anbieter daran gelegen, Alleinstellungsmerkmale zu schaffen. Auch liegt der Teufel oft im Detail: Ich habe selbst Erfahrungen damit gesammelt, indem ich ein CQRS/Event-Sourcing-System in AWS Lambda entwickelt habe. Dazu habe ich zum Beispiel Kinesis verwendet, einen AWS-Streaming-Dienst, der sequenzielle Auslieferung von Nachrichten garantieren kann. Als ich in einem weiteren Schritt den Code auf die Google Cloud umstellte, gab es dort kein Kinesis und auch keinen entsprechenden Dienst – und selbst wenn es ihn gegeben hätte, wären sicherlich dessen Trigger und Datenformate ausreichend verschieden gewesen, um Umstellungsaufwand zu erzeugen.

Fazit

Ich finde Serverless sehr interessant, und ich verstehe gut, warum viele Entwickler großes Interesse an der Idee haben. Allerdings gibt es einigen Handlungsbedarf, um die praktische Arbeit an einem umfangreichen FaaS-basierten Anwendungssystem zu vereinfachen und stabiler zu gestalten. Solange die Unterschiede im Funktionsumfang der verschiedenen Cloud-Umgebungen groß bleiben, widerspricht das Konzept den Zielen von Microservices in gewissem Maße. Wenn ich böswillig über die Sache denken wollte, könnte ich mich fragen, ob Serverless nicht hauptsächlich ein Versuch der Cloud-Anbieter ist, ihre eigene Relevanz wiederherzustellen… Vor nicht langer Zeit waren wir an einem Punkt, wo ein Docker-basiertes Anwendungssystem größtmögliche Unabhängigkeit von der Hosting-Umgebung erreicht hatte und die Cloud zur „Commodity“ wurde. (Dafür gibt es kein deutsches Wort – eine Commodity ist ein Produkt, dessen Qualität unabhängig vom Hersteller gleich eingeschätzt wird.) Demgegenüber ist Serverless mit Vorsicht zu genießen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -