Blog

Bring dein Startup mit Serverless auf Erfolgskurs

MAY 10, 2020

Bei der Wahl eines Tech Stacks suchen Startups nach kosteneffizienten und skalierbaren Lösungen. Serverless kann eine gute Option sein – aber wie gelingt der Einstieg?

Severi Haverila

Severi works as a DevOps consultant by day, and runs a startup company at night. His main interests are working with state-of-the-art technologies and solving problems for the customers in a pragmatic way.

Was fehlt Start-ups in der frühen Phase? Genau, Server! Sorry, ich meinte Geld. Deshalb sollten sie Server vergessen und ihr Geschäft mit Serverless starten. Und mit Servern meine ich nicht nur eure veralteten Server-Racks oder EC2-Instanzen, auf denen euer LAMP-Stack läuft, sondern auch eure schicken GKE Kubernetes-Cluster. Zahlt ruhig die zusätzlichen 50 Dollar für einen minimalen Kubernetes-Cluster, der nur euer Frontend und ein paar NodeJS-Microservices für eher seltenen Traffic bereitstellt, wenn ihr unbedingt wollt. Aber warum, wenn ihr all das zu einem Bruchteil der Kosten umsetzen und gleichzeitig den Wartungsaufwand reduzieren könnt?

In diesem Blogbeitrag zeige ich euch, wie ihr all das in 10 Minuten umsetzt. Genauer gesagt konzentrieren wir uns darauf, euch und die übrigen Entwickler an eurem Produkt arbeiten zu lassen, das die Welt verändert. Am Ende dieses Beitrags habt ihr ein Repository mit einer auf AWS laufenden serverlosen API und einem React-Frontend, das diese API nutzt.

Das Serverless Framework

Wir verwenden das Serverless Framework, um unsere serverlose API auf AWS zu erstellen. Das Serverless Framework ist nicht auf AWS beschränkt: Es unterstützt alle großen Cloud-Anbieter sowie anbieterunabhängige Technologien wie Kubeless.

Bei der Arbeit mit AWS nutzt das Serverless Framework im Hintergrund CloudFormation, um unseren serverlosen Stack einzurichten. Mit dem Serverless Framework lassen sich einfach benutzerdefinierte CloudFormation-Ressourcen zu unserem serverlosen Deployment hinzufügen. Beispielsweise könnten wir ein CloudFormation-Template erstellen, das eine DynamoDB-Tabelle für unsere Lambdas anlegt, und sie zusammen mit unseren Lambdas durch Ausführen eines einzigen Befehls „serverless deploy“ bereitstellen.

Genug geredet – lasst uns die Lambdas starten!

Los geht’s

Das Projekt ist so konfiguriert, dass die Zusammenarbeit mit anderen einfach ist. In der Praxis bedeutet das, dass ihr persönliche Kopien der serverlosen API erstellen, neue Features dafür entwickeln und das Frontend automatisch so konfigurieren könnt, dass es eure eigene serverlose REST-API nutzt.

Klont dieses Repository: https://github.com/severi/serverless-react-getting-started

und checkt den Tag step1 aus

Im Repository findet ihr ein grundlegendes Boilerplate-Projekt von create-react-app für das Frontend (Ordner frontend) sowie ein einfaches Beispiel des Serverless Framework für das Backend (Ordner api).

Der Frontend-Teil wurde durch Ausführen von (weitere Informationen) erstellt:

Das Frontend ausführen

Lasst uns zunächst prüfen, ob das Frontend korrekt läuft. Führt dazu die folgenden Befehle aus:

Und schon kann es losgehen! Jetzt, da unser Frontend erfolgreich läuft, könnt ihr es beenden – es sei denn, ihr möchtet die großartige Landingpage von create-react-app noch etwas länger bewundern. Wir konzentrieren uns nun auf den serverlosen Teil und kommen später zum Frontend zurück.

Unsere serverlose API starten

Als Nächstes brauchen wir die richtigen Tools! Installiert die CLI des Serverless Framework.

Wir müssen unsere AWS-Zugangsdaten konfigurieren und ein AWS-Konto erstellen, falls ihr noch keines habt. Glücklicherweise bietet serverless.com ausführliche Anleitungen dazu, daher müssen wir sie hier nicht kopieren und einfügen. Folgt einfach diesem Link und kommt zurück, wenn ihr bereit seid.

Die Funktion bereitstellen

Führt im Ordner api sls deploy aus, um unsere Funktion auf AWS zu starten. Eure Ausgabe sollte etwa so aussehen:

Jetzt läuft unsere Funktion auf AWS und wir können versuchen, sie auszulösen. Dafür verwenden wir curl. Die URL der Funktion findet ihr in der Ausgabe des Befehls sls deploy. Schaut im Abschnitt endpoints: nach. Wie wir sehen, läuft sie tatsächlich:

Was passiert hier?

Die API-Struktur ist einfach:

Die interessanten Teile sind die Dateien serverless.yml und functions/myFunction.js. In den folgenden Abschnitten beschreiben wir beide im Detail.

serverless.yml

Unsere serverless.yml besteht aus drei Abschnitten:

  1. service – Definiert den Namen unseres serverless-Stacks

  2. provider – AWS-bezogene Konfigurationen

  3. functions – unsere serverless-Funktionen, die wir deployen möchten

Wir deployen eine Funktion namens myFunction. Im Abschnitt handler wird definiert, wo sich der Code für die jeweilige Funktion befindet. Das entspricht „Funktion main in der Datei functions/myFunction.js“.

functions/myFunction.js

Die Funktion, die wir deployen, ist einfach. Sie gibt lediglich ein { hello: "world" }-JSON-Objekt zurück.

Im Unterabschnitt events legen wir außerdem fest, dass myFunction auf HTTP-GET-Anfragen an den folgenden Pfad /myfunction reagieren soll.

Zur Erinnerung: So testen wir unsere Funktion. Achtet darauf, wie die URL endet:

Im provider-Abschnitt definieren wir außerdem die Standard-Stage als „dev“.

Diese kann überschrieben werden, indem dem Befehl sls deploy das Flag --stage stagename hinzugefügt wird.

Stages sind hilfreich, um getrennte Entwicklungs- oder Testumgebungen zu erstellen. Hier findet ihr die vollständige Referenz für serverless.yml.

AWS

Was passiert also auf AWS-Seite? Das Serverless Framework verwendet im Hintergrund CloudFormation, um unseren serverless-Stack zu deployen. Zur Überprüfung könnt ihr den CloudFormation-Bereich in der AWS-Weboberfläche öffnen: https://eu-north-1.console.aws.amazon.com/cloudformation/home

Seht ihr, wie serverless anhand der von uns angegebenen Service- und Stage-Namen einen CloudFormation-Stack erstellt?

Wenn ihr zum Tab Resources unseres frisch erstellten CloudFormation-Stacks navigiert, seht ihr, welche AWS-Ressourcen während des Deployments erstellt wurden.

serverless-api-dev screen shot

Zu diesen Ressourcen gehören:

  • ServerlessDeploymentBucket

  • API Gateway

  • IAM Role

  • MyFunctionLambdaFunction

  • Der S3-Bucket, in den unser Lambda-Code kopiert wird

  • Darauf bezieht sich der Teil https://XXXXXXXX.execute-api.eu-north-1.amazonaws.com/ unserer Lambda-URL.

  • Ermöglicht unserer Lambda-Funktion, ihre Logs in CloudWatch zu schreiben

  • Die Lambda-Funktion selbst

Einheitlicher Code-Stil

Ein Vorteil von Node.js ist, dass wir dieselbe Programmiersprache und dieselben Bibliotheken sowohl für das Frontend als auch für das Backend unserer Anwendung nutzen können. Unser Frontend verwendet bereits die neuesten ES6-JavaScript-Features. Um dieselben Features auch in den Backend-Services zu nutzen und unseren Code-Stil zwischen Frontend- und Backend-Funktionen einheitlich zu gestalten, müssen wir unser erstes Plugin zu serverless.yml hinzufügen: serverless-bundle. Weitere Informationen zu den Änderungen, die ES6 an der JavaScript-Syntax einführt, findet ihr hier.

Wie die Entwickler von serverless-bundle in ihrem eigenen Serverless-Leitfaden empfehlen, aktivieren wir auch das individuelle Packaging für unsere Funktionen.

Durch die Aktivierung des individuellen Packagings zusammen mit dem serverless-bundle-Plugin können wir für jede unserer Funktionen ein eigenes optimiertes Paket erstellen – aktuell gibt es zwar nur eine, aber in einer realen Anwendung wären es viele.

Jetzt können wir myFunction.js auf die ES6-Syntax umstellen. Da unsere Beispielfunktion recht einfach ist, beschränkt sich der Unterschied zwischen der ES5- und ES6-Implementierung auf die Art, wie die Funktion deklariert wird. In einer größeren Codebasis hätte dies natürlich deutlich mehr Auswirkungen.

Lasst uns unsere Funktion erneut deployen:

serverless-bundle aktiviert unter anderem auch eslint für unser Serverless-Projekt. Ihr könnt dies testen, indem ihr das abschließende Semikolon aus unserer Funktion entfernt und dann versucht, sie erneut zu deployen. Ihr solltet etwa Folgendes sehen:

Wie lassen sich diese Probleme am besten beheben? Natürlich könnten wir sie manuell korrigieren, aber auf Dauer kann das mühsam werden. Wenn möglich, möchten wir diese Probleme automatisch beheben.

Leider habe ich keinen Weg gefunden, um:

  1. die Linting-Fehler in Visual Studio Code anzuzeigen

  2. eslint mit dem Flag --fix auszuführen und dabei direkt die eslint-Konfigurationen zu verwenden, die serverless-bundle bereitstellt.

Als Workaround habe ich Folgendes gemacht:

und die folgenden Skripte zur package.json der API hinzugefügt

Dadurch werden die eslint-Konfigurationen mit derselben Konfiguration überschrieben, die das Plugin verwendet. Da sich die Datei .eslintrc.json im Stammverzeichnis des API-Ordners befindet, funktioniert das Linting sowohl über die CLI als auch in VSCode.

Wir können nun automatisch behebbare Linting-Probleme korrigieren, indem wir Folgendes ausführen:

In der Praxis wäre es sinnvoll, euer Linting zu standardisieren, sodass sowohl euer Frontend- als auch euer Serverless-Code mit denselben Einstellungen gelintet werden. So erreicht ihr einen einheitlichen Code-Stil für alle eure JavaScript-Dateien.

Eine vollständige Lösung für diesen Schritt findet ihr im Tag step2.

Aufruf der Serverless-Funktion über unsere Website

Lasst uns die Datei App.js unseres Frontends bearbeiten und Code hinzufügen, der unsere Lambda aufruft.

Das allein reicht nicht aus, denn wir erhalten einen CORS-Fehler – klickt auf den Button, öffnet die Konsole eures Browsers und ihr seht die Fehlermeldung. CORS steht für „Cross-Origin Resource Sharing“ und ermöglicht Anfragen an andere Domains als die, auf der eure Website läuft. Mehr über CORS erfahrt ihr hier.

Wenn wir uns die Referenz für serverless.yml unter https://serverless.com/framework/docs/providers/aws/guide/serverless.yml/ ansehen, sehen wir, dass wir die folgende Option zu unserer Funktion hinzufügen können:

Wie die Kommentare zeigen, müssen wir auch unsere Antwort anpassen. Durch das Hinzufügen dieses Headers können wir während der Entwicklung Cross-Origin-Anfragen aus unserer großartigen Webanwendung senden, also von http://localhost:3000.

Deployt die Funktion erneut. Nun sollte ein Klick auf den Button die Antwort korrekt in der Browser-Konsole ausgeben.

Weniger Hardcoding, mehr Zusammenarbeit

Die API-URL direkt in unseren Frontend-Code zu schreiben, ist nicht optimal. Im Produktions-Build eurer Anwendung möchtet ihr die Produktionsversion eurer API ansprechen. Während der Entwicklung sollten ihr und eure Kollegen jeweils eure eigenen Versionen verwenden.

Um dies zu ermöglichen, lagern wir die fest codierte URL aus dem Code in eine Umgebungsvariable aus, indem wir die fetch-Zeile in unserer App.js wie folgt ändern:

Wie einige von euch vielleicht schon bemerkt haben, verwenden wir die von create-react-app bereitgestellte Funktion für benutzerdefinierte Umgebungsvariablen, um unseren Service-Endpunkt zur Build-Zeit als Umgebungsvariable einzubetten (weitere Informationen).

Als Nächstes definieren wir die Variable REACT_APP_ServiceEndpoint. Dazu verwenden wir die Datei .env.development.local (weitere Informationen zu verschiedenen .env-Dateien und ihren Prioritäten findet ihr hier).

Wir können die Datei manuell erstellen und die folgende Zeile hinzufügen:

Das funktioniert bei nur einem Entwickler und einer Umgebungsvariable gut. Doch was ist, wenn wir später weitere Informationen zu unserem AWS-Stack im Frontend benötigen? Vielleicht möchtet ihr Nutzern erlauben, Dateien direkt aus ihrem Browser in S3 hochzuladen, ohne Lambdas aufzurufen. Und wie teilen wir diese manuellen Schritte anderen Entwicklern mit, damit sie ihre eigenen Entwicklungsumgebungen einrichten können?

Glücklicherweise lässt sich das Erstellen der env-Datei automatisieren.

Für die Automatisierung verwenden wir einen eigenen Fork eines Plugins namens „Serverless Stack Output Plugin“. Laut README des Plugins macht es Folgendes:

Ausgaben aus eurem AWS CloudFormation Stack in JSON-/YAML-/TOML-Dateien speichern oder die Ausgaben zur weiteren Verarbeitung an eine JavaScript-Funktion übergeben.

Mit unserem Fork des Plugins können wir die Umgebungsdateien für create-react-app während des Deployment-Schritts unseres serverlosen Stacks automatisch generieren.

Installieren wir unsere angepasste Version des serverless-stack-Plugins mit folgendem Befehl:

und passen unsere serverless.yml wie folgt an:

Wenn wir die API erneut mit `sls deploy` deployen, seht ihr, dass auch eine Datei namens .stack.dev.react_app erstellt wird. Kopieren wir sie in das Frontend-Verzeichnis und benennen sie um:

Und schon sind wir fertig!

Startet das Frontend neu und prüft, ob es das Hello-World-Objekt weiterhin in der Konsole ausgibt. Mit diesem Ansatz kann ein einzelner Entwickler einfach eine neue serverlose Umgebung erstellen, indem er beim Deployment eine eigene Stage definiert (z. B. `sls deploy --stage myenv`), die generierte Datei zum Definieren der Umgebungsvariablen für das Frontend verwendet und sie mit dem neuen serverlosen Backend verbindet. Dies beschränkt sich nicht nur auf serverlose Funktionen und ihre API-Gateway-URL, sondern gilt auch für andere AWS-Ressourcen, die mit dem serverless-framework erstellt werden.

Für den Production-Build können die Umgebungsvariablen in .env.production abgelegt und in der Versionsverwaltung gespeichert werden.

Eine vollständige Lösung für diese Stage findet ihr im Tag step3:

Um den serverlosen Stack aus AWS zu löschen, führt Folgendes aus:

Wenn ihr besonders sorgfältig sein möchtet, prüft die CloudFormation-Seite in der AWS-Konsole und stellt sicher, dass alle vom serverless framework erstellten Stacks gelöscht wurden.

Fazit

Der Einstieg in Serverless muss nicht schwierig sein. Es ist noch ein neues Feld, deshalb entwickeln sich die Technologie und die Best Practices darum kontinuierlich weiter. Ich hoffe, dieses Tutorial hat eure Reise mit Serverless etwas verständlicher gemacht und ihr könnt nun neue Serverless-Projekte starten und gemeinsam mit euren Kollegen entwickeln.

Technische Schulden gehören zur Realität jedes Unternehmens. Sie sind zwar kein Weltuntergang, doch die Entscheidung, sie einzugehen, sollte immer bewusst getroffen werden. Eure Infrastruktur als Code zu verwalten und die Bereitstellung zu automatisieren, sollte schon früh weit oben auf eurer To-do-Liste stehen. Zum Glück unterstützt uns das serverless framework mit CloudFormation dabei.

Serverless ist ein großartiges Werkzeug in der Toolchain eines Entwicklers, aber nicht immer die kosteneffizienteste Lösung (weitere Informationen findet ihr in diesem Blog und diesem Blogbeitrag über Serverless). Für die richtigen Einsatzzwecke ermöglicht es uns jedoch, kosteneffiziente und automatisch skalierbare Services zu erstellen. Und wie ihr in diesem Blogbeitrag gesehen habt, ist der Einstieg ziemlich unkompliziert.

Wenn ihr mehr über das Thema erfahren möchtet, findet ihr hier ein großartiges Tutorial für den Einstieg. Vielleicht behandeln wir in zukünftigen Blogbeiträgen auch weitere Aspekte der Serverless-Entwicklung. Bis dahin ist jetzt die Zeit, kein Geld mehr für unzureichend genutzte Server zu verschwenden und eure Serverless-Reise zu beginnen!

  • Cloud

Subscribe to our newsletter