Serverless Computing, Teil 2: Pro und contra

Dank Functions as a Service und dem damit verbundenen Serverless-Paradigma ist es möglich, die eigene Fachlichkeit aufgeteilt in einzelne Funktionen in der Cloud zu hinterlegen. Wie lässt sich das beherrschen?

In Pocket speichern vorlesen Druckansicht 9 Kommentare lesen
Serverless Computing, Teil 2: Pro und contra
Lesezeit: 13 Min.
Von
  • Lars Röwekamp
Inhaltsverzeichnis

Im ersten Artikel zu Serverless Computing wurde gezeigt, wie sich eine komplexe Enterprise-Anwendung in die Cloud verlagern lässt: IaaS für die Virtualisierung von Rechnern, PaaS für Cloud-basierte Datenbanken, Dateisysteme oder Anwendungsserver, BaaS und SaaS für fachliche Anwendungsmodule oder ganze Anwendungen. Lediglich die individuelle Anwendungslogik schien bisher eine natürliche Grenze für die Cloud zu bilden – bisher. Dank FaaS und dem damit verbundenen Serverless-Paradigma ist es möglich, die eigene Fachlichkeit aufgeteilt in einzelne Funktionen in der Cloud zu hinterlegen. Angetriggert über synchrone Aufrufe des API Gateway oder Cloud-interne Events ergibt sich eine stark verteilte, Event-getriebene Anwendungsarchitektur. Dieser Artikel zeigt, wie auch diese beherrscht werden kann.

"Kein Server ist einfacher zu verwalten als kein Server", so die treffende Aussage von Werner Vogels, CTO bei Amazon. Aber bedeutet "serverless" wirklich, dass keinerlei administrative Aufgaben mehr auszuführen sind? Auch wenn das ein wünschenswertes Szenario wäre, gibt es auch in der Serverless-Welt noch das eine oder andere zu administrieren.

Je nach Anbieter müssen für die einzelnen Funktionen zum Beispiel der zu allokierende Speicher angegeben, ein Timeout gesetzt oder Zugriffsrechte auf die Funktion beziehungsweise aus der Funktion auf andere Cloud-Ressourcen gesetzt werden. Zusätzlich gilt es, bei Bedarf verschiedene Versionen der Funktionen zu verwalten und ein Staging-Konzept zu konfigurieren. Es war noch nie eine gute Idee, seinen Code ungetestet in Produktion zu schicken. Das gilt auch für die Serverless-Funktionen. Alles in allem lässt sich also nicht von NoOps sprechen – LessOps scheint hier der treffendere Begriff zu sein.

Einzelne Serverless-Funktionen für sich sind in der Regel recht klein und überschaubar. Die eigentliche Komplexität der Anwendung ergibt sich erst durch deren Komposition. Läuft alles glatt, gibt es kein Problem. Aber was ist, wenn ein Fehler auftritt? Zunächst einmal gilt es, wie sonst auch in der Softwareentwicklung, Fehler weitgehend zu vermeiden, also zu verhindern, dass eine Funktion mit Programmierfehlern in Produktion geht.

Da die einzelnen Funktionen per Definition relativ klein sind und in der Regel nur eine fachliche Aufgabe erfüllen, bieten sich Unit-Tests zur Sicherstellung der korrekten Arbeitsweise an. Problematisch daran ist, dass die Funktionen in der Regel stark von anderer Cloud-Infrastruktur abhängen. Im Falle der im Beispiel gezeigten AWS Lambdas werden zum Beispiel ein Cloud-Context-Objekt sowie der Zugriff auf die AWS-Datenbank Dynamo DB und das AWS-Storage-System S3 benötigt. Zusätzlich werden im Beispiel AWS-spezifische Trigger genutzt, was das Schreiben von Unit-Tests vor eine gewisse Herausforderung stellt.

Zum GlĂĽck gibt es eine stetig wachsende Serverless-Community, sodass man nicht allein mit diesen Herausforderungen steht. Im Fall von AWS Lambda existieren einige quelloffene Mock-Frameworks sowie lokal zu nutzende Cloud-Komponenten, zum Beispiel DynamoDB Local, die das lokale Testen der eigenen Lambda-Funktionen erleichtern.

Neben den Unit-Tests kann man Integrationstests implementieren und regelmäßig automatisiert ausführen. Mittels Versionierung, Staging und Aliasing lassen sich neben der produktiven Umgebung entsprechende Integrationstestumgebung in der Cloud aufsetzen. Es ist allerdings zu bedenken, dass durch die Tests Ressourcen in der Cloud genutzt werden, die entsprechend zu bezahlen sind. Regelmäßige Lasttests bieten sich somit nicht unbedingt an.

Was aber, wenn doch einmal ein Fehler im laufenden System auftritt. Wenn zum Beispiel ein fehlerhafter Request vom Client eintrifft, Daten in einer Funktion nicht korrekt verarbeitet werden können oder es zu einem Timeout beim Aufruf einer Funktion kommt? Innerhalb der einzelnen Funktionen lässt sich auf Fehler, wie sonst auch in Java, über normales Exception Handling reagieren. Handelt es sich um eine synchron aufgerufene Funktion, kann man eine entsprechende Fehler-Response setzen, die der Aufrufer entsprechend verarbeiten muss.

Etwas schwieriger dagegen wird es, wenn es sich um einen asynchronen Aufruf handelt. Dann kann innerhalb der Funktion ein Log-Eintrag oder ein Error-Event erzeugt werden, auf das wiederum eine eigene Funktion als eine Art Error- oder Compensation Handler reagiert.

Es gibt aber auch Fehler, auf die sich nicht direkt innerhalb der aufgerufenen Funktion reagieren lässt. Hierzu zählt zum Beispiel ein Timeout oder das Überschreiten eines Rate-Limits. In diesen Fällen greift zunächst eine automatische Retry-Policy. Führt sie nicht zum Erfolg, hat man individuell zu reagieren, indem zum Beispiel eine Dead-Letter-Queue für die Funktion konfiguriert wird, in der alle notwendigen Informationen zur späteren Rekonstruktion des Aufrufs landen. Wie nicht anders zu erwarten, können auch an den Dead Letter Queues wiederum Funktionen auf entsprechende Events – neues Element in der Queue – lauschen und so bei Bedarf notwendige Fehlerbehandlungen vornehmen. Die Abbildung 6 zeigt eine mögliche Fehlerbehandlung mithilfe einer Dead Letter Queue innerhalb des im ersten Artikel kennengelernten myJourney-Image-Upload-Prozess.

Dead Letter Queue

Einer der wesentlichen Vorteile von Serverless-Funktionen liegt darin, dass sich die zugrunde liegende Cloud-Infrastruktur dynamisch dem jeweils aktuellen Ressourcenbedarf anpasst. Bezahlt wird dabei nur für die tatsächlich verbrauchten Ressourcen, also für CPU-Zeit und/oder Speicherverbrauch. "Don't pay idle", so das gemeinsame Credo aller Anbieter. Das Bezahlmodell ist insbesondere für Anwendungen mit stark schwankender Last interessant oder aber für Start-up-Szenarien, bei denen das Wachstum nicht klar vorhersehbar ist. Auch wenn die Cloud-Anbieter eine nahezu unendliche, automatische Skalierung propagieren, hat auch dieses Modell seine Grenzen. Sind sie erreicht, ist in die nächsthöhere Leistungsstufe zu wechseln oder im Zweifelsfall individuell zu verhandeln.

Auf den ersten Blick scheinen die Kosten bei allen Anbietern extrem gering zu sein. Zum Beispiel bietet Amazon einen monatlichen "Free Tier" von einer Millionen Requests und 400.000 GBytes Rechenzeit an (400.000 Sekunden Rechenzeit bei einer Speicherkonfiguration von einem GByte). Abgerechnet wird in Inkrementen von 100 Millisekunden.

Bei der Bewertung der Zahlen ist zu bedenken, dass Last nicht nur im produktiven Umfeld anfällt, sondern auch auf etwaigen anderen Cloud-Stages. Werden zum Beispiel Lasttests in der Cloud durchgeführt, schlagen diese entsprechend zu Buche. Ebenfalls bedacht werden sollte, dass die Anbieter das Geld nicht mit den Serverless-Funktionen verdienen, sondern damit, dass diese eine Reihe weiterer Cloud-Komponenten aus ihrem Portfolio nutzen. Damit die Kosten am Ende nicht zu einer negativen Überraschung werden, bieten alle Anbieter mehr oder minder aussagekräftige Preiskalkulatoren an.

Neben den monatlichen Limits existieren in der Regel auch solche für die Anzahl der parallel zu verarbeitenden Anfragen. Bei Amazon ist sie auf 200 konkurrierende Requests beschränkt, wobei die Abarbeitung einer einzelnen Anfrage nicht länger als 300 Sekunden dauern darf. Auch das klingt zunächst einmal nicht nach viel, kann aber unter Umständen zu einem echten Problem werden. Denn je nach durchschnittlicher Dauer der Anfrage sinkt die Anzahl der möglichen neuen Anfragen pro Sekunde entsprechend. Als Faustregel für die Berechnung der parallel laufenden Anfragen lässt sich folgende Formel heranziehen:

Parallele Anfragen = Anzahl der eingehenden Events (oder Request) pro Sekunde * durchschnittliche Abarbeitungsdauer der Funktion

Erzeugt eine Anwendung zum Beispiel 40 Log-Events pro Sekunde und dauert die Abarbeitung der für das Event registrierten Funktion drei Sekunden, dann liegen im Durchschnitt 120 parallele Abarbeitungen vor. Laut Amazon ist das Limit lediglich eine Sicherheitsmaßnahme und kann auf Anfrage im Bedarfsfall kostenfrei hochgesetzt werden.