Serverless-Technologien finden seit der Einführung von AWS Lambda im Jahr 2014 immer größere Verbreitung. Google, Microsoft und andere Anbieter folgten bald mit ähnlichen Diensten. Die Rede ist von Function-as-a-Service (FaaS). Dabei wird Funktionscode in die Cloud hochgeladen und in der Laufzeitumgebung des Anbieters ausgeführt. Der Cloud-Anbieter stellt die Infrastruktur, übernimmt das Scaling und rechnet die Nutzung nach Pay-per-Use ab. Die Funktionen können dann durch verschiedene Ereignisse innerhalb der Cloud und ohne eigene Server (Serverless) ausgelöst werden.

Die Ökosphäre rund um Serverless – das heißt Entwicklertools und Best Practices – befindet sich noch immer in einer frühen Entwicklungsphase. Für meine Bachelorarbeit habe ich mich speziell mit dem Thema Testing einer Serverless-Funktion beschäftigt.

Was macht Testing im Serverless-Bereich schwierig?

Function-as-a-Service unterscheidet sich von Platform-as-a-Service oder Infrastructure-as-a-Service in dem Sinne, dass wir keinen direkten Einfluss auf die Laufzeitumgebung haben, in der unser Code ausgeführt wird. Wir können also nicht, wie in traditionellen Bereitstellungsmodellen, ein Dockerfile erstellen und davon ausgehen, lokal, im CI-Runner und auf der Produktionsumgebung dieselben Ergebnisse zu erhalten.

Hinzu kommen spezielle Rahmenbedingungen bei der Ausführung von Serverless-Funktionen in der Cloud. Werden Funktionen für eine längere Zeit  – in der Regel etwa 15 Minuten – nicht ausgelöst, muss die Umgebung beim nächsten Funktionsaufruf neu aufgebaut werden. Man spricht von einem Cold Start. Dadurch verzögert sich die Antwortzeit immens und so können beispielsweise Timeouts im Frontend der Anwendung greifen, bevor die Funktion ausgeführt werden kann. Dies sollte in einem Integration- oder End-to-end-Test überprüft werden.

Aber nicht nur Cold Starts sind eine Besonderheit, sondern auch die gesetzten und oft konfigurierbaren Limitierungen bei der Ausführung der Funktion. Dazu gehören beispielsweise:

  • Timeouts
  • Arbeitsspeicher
  • Speicherplatz für das beschreibbare /tmp Storage
  • Größe des Invocation Payloads für Request und Response

Die vollständige Aufzählung und Details zu Limitierungen können in den Dokumentationen der Cloud-Anbieter nachgelesen werden. Adäquate Testing-Lösungen sollten also auch diese Limitierungen berücksichtigen.

Ein Aufruf von Serverless-Funktionen erfolgt über verknüpfte Dienste des Cloud-Anbieters, die zu definierten Ereignissen die Ausführung der Funktion initiieren. Das einfachste Beispiel dafür ist das API Gateway. Hier wird im Prinzip nur ein HTTP-Endpunkt zur Ausführung der Funktion konstruiert. AWS Lambda bspw. unterstützt aber auch Ereignisse aus S3, DynamoDB, Kinesis, SNS oder CloudWatch. Wie sollte man diese Schnittstellen simulieren, um Integration- und End-to-end-Tests durchführen zu können?

Aus all diesen Gründen mag es sinnvoll erscheinen Serverless-Anwendungen direkt in der Cloud zu testen und die Nachteile, die damit verbunden sind (Cloud-Kosten, Abhängigkeit an Internetverbindung), in Kauf zu nehmen. Die Alternative wäre, die Cloud-Umgebung lokal zu emulieren. In den nächsten Abschnitten werde ich beide Ansätze gegeneinander abwägen und aufzeigen, welche Tools und Frameworks für eine lokale Emulation existieren und wie erfolgreich sie dieser Aufgabe nachkommen.

Testen gegen einen Endpunkt in der Cloud

Das Testen gegen einen Endpunkt in der Cloud, das der produktiven Umgebung nachempfunden ist, eignet sich natürlich am besten für eine realitätsnahe Testing-Simulation. Identische Laufzeitumgebung und Limitierungen geben dabei Sicherheit und auch kontextuelle Dienste, wie z.B. ein S3-Bucket, können bei diesen Tests wirklichkeitsgetreu miteinander interagieren.

Dadurch ergeben sich jedoch folgende Nachteile:

  • Testing hat normalerweise den Anspruch, isoliert ausgeführt werden zu können – etwa auf einer CI oder gar lokal. Bei dieser Lösung ist das Testing von einer Internetverbindung bzw. Verbindung zur Cloud abhängig.
  • Das Ausführen von Cloud-Diensten ist mit Kosten verbunden. Im Kontext von Serverless, das nach einem Pay-per-Use-Preismodell abgerechnet wird, entsehen Kosten für jeden einzelnen Aufruf und damit auch zwangsweise jedes Mal, wenn die Testing-Suite ausgeführt wird.
  • Zu weiteren finanziellen Aufwendungen führt die Koordination mehrerer Entwickler, die gleichzeitig an einer Funktion arbeiten. Deployments auf die Testing-Umgebung dürfen nicht den Stand eines anderen Testing-Prozesses überschreiben, bevor dieser abgeschlossen ist. Dieser Fall kann eintreten, wenn mehrere Commits dicht aufeinander folgen bzw. auf mehreren Branches gleichzeitig gearbeitet wird. Hier müssen also spezielle Lösungen gefunden werden, z.B. der Commit-Hash an den Namen der Instanz angehangen werden.

Lokale Emulation der Cloud-Umgebung

Es existieren zahlreiche Projekte von der Community und den Cloud-Anbietern selbst, welche die lokale Emulation bzw. Simulation von Serverless-Funktionen bewerkstelligen sollen. Sie haben das Ziel, lokales Testing und Debugging zu unterstützen. Es ist der Zeit seit der Veröffentlichung der jeweiligen Serverless-Dienste geschuldet, dass die Tools für AWS bereits besser ausgereift sind als für Azure respektive Google Cloud.

Serverless Framework

Beim Serverless Framework handelt es sich um ein sehr populäres Open-Source-Projekt zur Entwicklung von Serverless-Funktionen. Das Besondere an diesem Framework ist, dass es Cloud-agnostisch ist. Es unterstützt also eine Vielzahl von Anbietern und ist gleichzeitig in jeder Programmiersprache verfügbar.

Mit dem Serverless Framework ist es möglich, einen lokalen Webserver zu starten, der als API Gateway für die Funktionen agiert. Die Konfiguration wird der selben Datei entnommen, die auch für die Provisionierung in der Cloud verantwortlich ist. Hier wird ebenfalls der konfigurierte Wert für das Timeout der Funktion entnommen. Diese Limitierung wird also bei der Simulation berücksichtigt.

Serverless Application Model (SAM)

SAM ist ein Framework, das von AWS entwickelt wird und auch zum Entwickeln von AWS Lambda Funktionen genutzt werden kann. Mit dem CLI kann ein lokales API Gateway simuliert werden. Die Funktionen werden als eigene Instanzen von Docker Containern gestartet. Hierbei wird gleichzeitig ein Cold Start simuliert! Die Docker Container basieren auf einem Image der Laufzeitumgebung, welches speziell von AWS bereitgestellt wurde. Timeouts und sogar Speicherlimits werden bei der Ausführung berücksichtigt.

LocalStack

Bei LocalStack handelt es sich um ein zum Teil kommerzielles Projekt, das es sich zur Aufgabe gemacht hat, Dienste aus der AWS Cloud lokal zu emulieren. Dies schließt Lambda und auch alle kontextuellen Dienste, wie S3 und DynamoDB, mit ein.

Auch hier wird ein API Gateway mit Cold Starts containerisierter Funktionsinstanzen emuliert. Timeouts und Speicherlimits werden berücksichtigt.

Azure Functions Core Tools

Die Azure Functions Core Tools werden von Microsoft zum Entwickeln und lokalen Testen von Azure Functions entwickelt. Microsoft stellt eigene Docker Images zur Verfügung, welche die Laufzeitumgebung in ihrer Cloud nachahmen sollen. Allerdings werden hierbei keine Cold Starts emuliert und unter den Limitierungen nur das Timeout berücksichtigt.

Functions Framework

Das Functions Framework wird durch das Google-Cloud-Platform-Team entwickelt. Hierbei handelt es sich aber im Wesentlichen nur um ein CLI, das beim Bootstrapping von Funktionen helfen soll und einen Webserver zum Ausführen der Funktionen zur Verfügung stellt.

Fazit

Aus der Auflistung der verfügbaren Frameworks geht hervor, dass sich die lokale Emulation von Cloud-Umgebungen in ganz unterschiedlichen Phasen der Entwicklung befindet. Während für AWS bereits Frameworks mit einem hohen Anspruch an die Qualität der Simulation existieren (Cold Starts, Timeouts, Memory Limits), existiert für Google Cloud noch nicht einmal ein Docker Image zur Nachahmung der Laufzeitumgebung in der Cloud. End-to-End-Testing von Funktionen, die von anderen Ereignissen als HTTP ausgelöst werden, ist außerhalb von LocalStack und damit AWS schlicht nicht möglich.

Eine vollständige lokale Emulation für das Testing von Serverless-Funktionen existiert nicht. Es ist aber erkennbar, dass sowohl AWS, als auch Azure und Google Cloud, dieses Problem anvisiert haben und an einer Lösung arbeiten bzw. ihre Lösungen weiterhin ausbessern.

Bis dahin bleibt es dem Entwickler überlassen, ob er sich entscheidet, das Testing doch lieber auf einem Endpunkt in der Cloud vorzunehmen oder sich sogar eine hybride Lösung auszudenken. Diese Entscheidung dürfte von der Unterstützung des jeweiligen Cloud-Anbieters und den persönlichen Ansprüchen an die Testing-Qualität abhängen.