Jobsuche Portal

SS22 – Dev4Cloud Projekt – von Robin Härle und Anton Gerdts

  1. Ideenfindung

    Zu Beginn der Ideenfindungsphase für unser Projekt sahen wir uns die verschiedenen Apis auf Bund.dev an, um uns von der Thematik der verfügbaren Daten inspirieren zu lassen. Wir entschieden uns ohne lange abzuwägen dafür ein Jobsuche-Portal mit der Jobsuche API als Datenquelle zu kreieren, da der Inhalt unseres Projekts zweitrangig war und es uns mehr darum ging, technisch interessante Cloud-Dienste auszuprobieren. Als nächstes erstellten wir eine entsprechende Liste von Features die wir in unserer App einbauen wollten und realistisch umgesetzt werden könnten: Neben der klassischen Suchfunktion für Jobs nach Parametern wie Ort, Beschäftigungsart und Titel sollten sich User über eine Single-Sign-On oder Email-Registrierung authentifizieren können. Als authentifizierter User sollte man auf der Detailansicht eines Job-Angebots eine Chat- und Bewertungsfunktion nutzen können, welche über Websockets Echtzeit-Kommunikation mit anderen Usern auf derselben Seite ermöglichen sollten.

Anschließend entschieden wir uns, unser Projekt in der AWS Cloud umzusetzen, da uns hierfür bereits der AWS Cloud Foundations Videokurs zum Lernen zur Verfügung gestellt worden war und AWS im Rahmen des Free Tier sehr umfangreiche Funktionen bietet.

Das Backend bzw. die Cloud-Infrastruktur sollte über Terraform deployed werden; bezüglich des Frontend-Frameworks entschieden wir uns für Vue, da Robin damit schon viel Erfahrung hatte. Außerdem wollten wir das Component basierte UI-Framework Naive UI nutzen, um uns möglichst wenig mit Design und mehr mit der Funktionalität und den Cloud-Diensten beschäftigen zu können.

  1. Umsetzung

Zum Start der eigentlichen Umsetzung der oben beschriebenen Webanwendung hatte Robin bereits vorab Cloud-Funktionen in der IBM Cloud implementiert, welche das Aktualisieren von Tokens zur Authentifizierung für die Jobsuche API übernahmen und somit einfache, direkte Abfragen der Job-Daten aus unserer App heraus ermöglichten.

Die User Authentifizierung realisierten wir über AWS Cognito. Wegen Zeitmangel war leider keine SSO-Authentifizierung mehr umsetzbar. Das Konfigurieren eines Cognito User-Pools funktionierte ohne Probleme, allerdings war die Integration dessen im Frontend schwieriger. Die Dokumentation des AWS SDK für JavaScript, dessen verschiedene Versionen und unterschiedliche Verwendung dieser sowie der Hinweis, dass man für die Authentifizierung über User-Pools (anstatt Identity Pools) eine dedizierte JavaScript Library verwenden soll (amazon-cognito-identity-js) waren unübersichtlich und sehr knapp. Zusätzlich war die API der erwähnten JavaScript Library umständlich designed (z.B. sind User-Daten nach Authentifizierung nicht direkt zugreifbar, sondern müssen jedes mal über eine Methode mit Callback für Success und Failure abgerufen werden). Aus diesem Grund implementierten wir eine Wrapper-Klasse, die für unseren Usecase eine optimierte API bereitstellte.

    Die Live-Chat Funktion realisierten wir über ein Zusammenspiel aus zwei Dynamo-DB Tabellen, drei Lambda Funktionen und einer Websocket API-Gateway Resource. Beim Aufruf einer Job-Detailansicht wird eine Verbindung mit dem Websocket Endpoint aufgebaut. In der “Connections” Dynamo-DB Tabelle wird die Connection-Id der Job-Id der aufgerufenen Detailansicht zugeordnet. Außerdem werden aus der “Comments” Tabelle alle Kommentare des aktuellen Jobs abgerufen und an den Client geschickt. Postet dieser ein neues Kommentar wird dieses in die “Comments” Tabelle gespeichert und an alle Connections, die in der“Connections” Tabelle der gleichen Job-Id zugeordnet sind, gebroadcastet Wird die Job-Detailansicht geschlossen wird auch aus der “Connections” Tabelle der Eintrag mit der Entsprechenden Connection-Id gelöscht.

Hierbei stießen wir auf zwei Probleme: Das erste bezog sich auf Terraform und zeigte, dass die Nutzung von Terraform nicht nur wie anfangs erwartet ein “Segen” wegen einfacherer Nachvollziehbarkeit und Reproduzierbarkeit der Infrastruktur in Code-Form ist, sondern auch Nachteile mit sich bringt: In vielen Fällen werden beim Erstellen von Cloud Ressourcen über die AWS Management Console Parameter automatisch passend konfiguriert, z.B. bei der Integration von Lambda-Funktionen mit einer API-Gateway Route: hier werden unter anderem “Invoke-Permissions” der Lambda-Funktion automatisch gesetzt, d.h. die Lambda-Funktion wird so konfiguriert, dass sie vom API-Gateway getriggert werden darf. Diese und andere Attribute muss man beim Verwenden von Terraform selbst korrekt setzen, was nicht immer einfach ist, da die Dokumentation von Terraform bezüglich solcher “Detail-Attribute” sehr knapp ist.

Das zweite Problem bestand darin, dass sich der gewohnte DevOps-Workflow nicht ohne weiteres mit der Entwicklung von Code für Serverless Cloud-Services wie Lambda-Funktionen vereinen lässt.

Anders als das Frontend einer Webanwendung konnten wir den Code einer Lambda Funktion nicht ohne weiteres in einer lokalen Testumgebung auf unseren Rechnern testen und die Zeit sich in entsprechende Tools wie z.B. das AWS SAM CLI einzuarbeiten fehlte uns. In unserem Fall genügte es den Code zwar in einem Git Repository zu versionieren, allerdings über die AWS Management Console zu testen und zu deployen. In Zukunft wäre es ideal für jegliche Serverless Services Testumgebungen für Entwicklungsrechner und Runner der Git Testing-Pipelines zu erstellen. Damit könnte Anwendungscode den üblichen DevOps Lifecycle durchlaufen und müsste nicht in der Cloud selbst getestet werden, wodurch vermieden werden könnte, dass Fehler bei der Entwicklung (z.B. fehlerhafte Bedienung der AWS Management Console) Cloud Ressourcen zum Absturz o.ä. bringen.

Multiplayer Game with AWS | StadtLandFluss

Dieser Blogbeitrag soll einen Einblick in die Entwicklung unserer Webanwendung mit den oben definierten Funktionen geben sowie unsere Lösungsansätze, Herausforderungen und Probleme aufzeigen. 

Cloud Computing Vorlesung

Ziel der Vorlesung “Software Development for Cloud Computing” ist es, aktuelle Cloud Technologien kennen zu lernen und diese im Rahmen von Übungen und kleinen Projekten anzuwenden. Unser Team hat sich im Rahmen dieser als Prüfungsleistung zu erbringenden Projektarbeit dazu entschieden, das bekannte Spiel „Stadt, Land, Fluss“ als Multiplayer-Online Game umzusetzen. 

Projekt Idee & Inspiration

Zu Beginn der Vorlesung war sich unsere Projektgruppe noch sehr unsicher, was wir als Projekt mit Cloudkomponenten umsetzen wollten, da wir noch keine bis sehr geringe Vorerfahrung in der Cloud-Entwicklung hatten. Erste Brainstormings hatten ergeben, dass wir gerne eine Webanwendung entwerfen wollten. Jedoch war es gar nicht so leicht Zugriff zu interessanten Daten zu bekommen. 

Letztendlich hat sich unsere Gruppe dazu entschieden, sich nicht von Daten abhängig zu machen, sondern etwas Eigenes zu kreieren. 

Die Inspiration für unsere finale Idee (Stadt-Land-Fluss) war das Online-Spiel Skribbl IO, ein kostenloses Multiplayer-Zeichen- und Ratespiel. Dabei wird in jeder Runde ein Spieler ausgewählt, der etwas zeichnet, das die anderen erraten sollen. Skribbl ermöglicht es dem Spieler auch, einen eigenen Raum zu erstellen und Freunde einzuladen, die einen Link zu diesem Raum teilen.

Im Rahmen unseres Projektes hat uns die Idee gefallen etwas zu entwickeln, was man danach mit Freunden zusammen nutzen kann. Den Multiplayer Ansatz fanden wir spannend, da wir so etwas noch nie umgesetzt haben. Da wir alle Stadt-Land-Fluss Fans sind, fiel unsere Wahl auf dieses Spiel. 

Ziel

Primäres Ziel des Projektes war es für uns, erste Erfahrungen in Cloud-Computing zu sammeln und gleichzeitig unsere Fähigkeiten im Software-Engineering auszubauen. 

Konkret war es die Idee ein Stadt-Land-Fluss Spiel mit den folgenden Funktionalitäten zu entwickeln: 

  • Schritt 1: Raumerstellung
    • Spieler kann einen neuen Raum erstellen, oder über eine Raum-Id einem Raum beitreten
  • Schritt 2: Spieldaten bestimmen
    • Der Spieler, welcher einen Raum erstellt, soll die Kategorien selber bestimmen können, sowie die Zeit, welche man für das Ausfüllen einer Spielrunde hat, ebenfalls sollen Mitspieler- und Rundenanzahl bestimmt werden können
  • Schritt 3: Waiting Room 
    • Nach Erstellen oder Beitreten eines Raumes, kommt der Spieler in einem Warteraum, wo er die festgelegten Parameter der Spielrunden sieht und informiert wird, welche Spieler der Runde schon beigetreten sind 
  • Schritt 4: Letter Generator 
    • Der Buchstabe für eine Runde soll zufällig generiert werden, sich aber nicht wiederholen innerhalb eines Spiels
  • Schritt 5: Spielrunde
    • Auf der Seite der Texteingaben, soll ein Spieler die Runde stoppen können, sobald er alles ausgefüllt hat, dies triggert den Stopp bei allen Mitspielern
  • Schritt 6: Kontrollieren der Eingaben 
    • Alle Spieler sehen nach einer Runde ihre eigenen, aber auch alle anderen Eingaben der Mitspieler sowie die Punkte, die dabei erreicht wurden
    • Dabei werden die Punkte nach folgendem Schema berechnet:
      • Hat ein Spieler als Einziger in dieser Kategorie eine Eingabe und ist diese auch gültig (beginnt mit dem generierten Buchstaben), dann erhält er für dieses Feld 20 Punkte
      • Haben andere in diesem Feld auch Eingaben, erhält der Spieler für eine gültige Eingabe 10 Punkte
      • Hat ein anderer Spieler in der gleichen Kategorie die gleiche Angabe, erhält der Spieler 5 Punkte
      • Ist die Eingabe leer oder beginnt sie nicht mit dem generierten Buchstaben, werden keine Punkte vergeben
  • Schritt 7 : Hall of Fame  
    • Darstellung der Spieler-Ränge und ihrer Punkte nach Abschließen aller Runden

Das erste Mockup der zu erstellenden Webanwendung entsprach folgendem Design und war unser Leitfaden für die Entwicklungsphase: 

Skizze der groben Web Anwendung zu Beginn

Einblick in das Spiel – Demo 

Frameworks – Cloud Services – Infrastructure

Frontend

Aufgrund von vorhandenen Vorerfahrungen wurde die zweite Entscheidung getroffen, das Frontend mit Hilfe des Angular Frameworks umzusetzen. Angular ist ein TypeScript-basiertes Front-End-Webapplikationsframework. Das Backend wurde mit Python als Programmiersprache umgesetzt. Zum einen war hier mehr Vorerfahrung vorhanden bei einigen Teammitgliedern und zum anderen haben wir mehr Beispiele zur Anwendung von Websockets und AWS im Zusammenhang mit Angular gefunden, was uns sehr geholfen hat. 

Backend

Wie zu Beginn schon erwähnt, hat uns die parallel zum Projekt laufende Vorlesung gleich zu Beginn den großen Funktionsumfang von AWS aufgezeigt.  Besonders interessant fanden wir die Einsatzmöglichkeiten von Lambda Funktionen. Im Zusammenhang damit hat uns die Funktion gefallen ein API Gateway aufzubauen zu können. Da man bei der Programmiersprache völlig frei wählen kann, haben wir uns für Python entschieden. In der Python Programmierung hatten wir als Team zwar wenig Erfahrung, haben aber in dem Projekt eine Chance gesehen, uns in dieses Thema weiter einarbeiten zu können und unsere Fähigkeiten zu verbessern. 

Architektur

Architektur

Cloud Komponenten 

Vor dem Projektstart hatten wir zu Beginn die Schwierigkeit zu entscheiden, welchen Cloud-Anbieter wir für die Entwicklung nutzen wollen. Voraussetzungen für die Entscheidungen waren, dass es eine ausführliche Dokumentation der Möglichkeiten und Funktionen gibt (aufgrund der mangelnden Vorerfahrung), ebenfalls wollten wir nicht eine Kreditkarte als Zahlungsoption hinterlegen müssen und auch keine bis sehr wenig Kosten verursachen. 

Zu Beginn der Vorlesung hieß es noch, dass wir eventuell ein Konto bei der IBM-Cloud oder über AWS von der Hochschule bekommen würden. Allerdings war dies leider doch nicht der Fall, weswegen wir nach erstem Warten selbst eine Entscheidung treffen mussten. Wir haben uns schlussendlich für AWS (Amazon Web Services) entschieden, da es einer der führenden Anbieter im Cloud Computing ist. Hierbei hat uns gefallen, dass es sehr viele Tutorials und gute Dokumentation zu den einzelnen AWS Services gab. Ein Nachteil war, dass man beim Anlegen eines Kontos eine Kreditkarte hinterlegen musste. Vorteil war andererseits, dass man mit einem Gratis-Kontingent (Free Tier) an Funktionsaufrufen, Rollen, und DB Kapazitäten etc. startet, weswegen im Rahmen des Projektes dahingehend keine Kosten entstehen sollten. Im späteren Verlauf haben wir herausgefunden, dass man allerdings für die Funktionalität von AmazonCloudWatch, welches ein Service zur Einsicht der Logs ist, zahlen muss. Die Kosten waren nicht hoch, weswegen es kein Problem darstellte, allerdings sollte man sich eindeutig über die Kosten, welche bei der Entwicklung entstehen können, im Klaren sein, um nicht böse überrascht zu werden. 

Amazon Free Tier

Übersicht Free Tier : https://aws.amazon.com/de/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=tier%23always-free&awsf.Free%20Tier%20Categories=*all

AWS Services 

Serverless 

Für das Anlegen unseres Projektes und auch der späteren Möglichkeit einer übersichtlichen Vorlage des Codes zur Bewertung, haben wir nach einer Möglichkeit gesucht, alle für AWS benötigten Konfigurationen sowie das Anlegen der verschiedenen Lambda-Funktionen in unserem GitLab Reporsitory hinterlegen zu können und direkt im Code Editor bearbeiten und ändern zu können. 

Nach einiger Recherche sind wir dabei auf das Serverless Framework gestoßen. Vorteile des Serverless Frameworks gegenüber der AWS Grafikoberfläche sind, dass alle man alle Elemente wie Datenbanken, Buckets, API-Routen und Aufrufe sowie Lambda Functions über eine serverless.yaml Datei verwalten kann. Zudem ist es nicht nötig, AWS-Kontoschlüsseln oder anderen Kontoanmeldeinformationen in Skripte oder Umgebungsvariablen zu kopieren oder einzufügen. Über die serverless.yaml können alle Ressourcen und Funktionen übersichtlich und schnell angelegt und bearbeitet werden. 

Functions

In der serverless.yaml angelegte Lambda Funktionen entsprechen dem folgenden Schema : 

Funktion Definition in serverless.yml 

Die in der Datei definierten Funktionen werden jeweils über einen eigenen „handler“ referenziert. Das bedeutet, dass sie über ein „Event“ aufgerufen werden können. Ein Event entspricht dabei beispielsweise einem API-Aufruf. 

broadcast_to_room Lambda-Funktion

Resources 

Neben unseren Funktionen sind in der serverless.yml ebenfalls alle Tabellen sowie Buckets definiert. Dies erleichtert die Einrichtung neuer Ressourcen, welche benötigt werden und gibt eine gute Übersicht. 

iamRoleStatements

Zudem können IAM-Rollen und Berechtigungen, die auf Lambda-Funktionen angewendet werden, konfiguriert werden. 

Allgemeine Konfiguration 

Es können ganz einfach allgemeine Settings angegeben werden, wie zum Beispiel: 

Übersicht der Möglichkeiten: https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/

Dynamo DB 

Um unser Spiel umsetzen zu können, waren wir darauf angewiesen, bestimmte Daten in einer Datenbank zu speichern, dies waren beispielsweise Elemente wir eine Raum-ID, die Spielernamen, die gewählten Spielparameter, sowie die benutzen Buschstaben und eingetragene Ergebnisse der Spieler. Da die Einträge in der Datenbank immer variieren und keinem starren Muster folgen, beispielsweise ist es möglich eine unterschiedliche Zahl an Kategorien pro Spielraum zu haben, haben wir nach einer NoSQL Lösung gesucht. Wir wollten eine Datenbank mit nicht-relationalen Ansatz, um nicht von einem festen Tabellenschemata abhängig zu sein. Unsere Wahl fiel dabei auf Amazon DynamoDB, welches eine schnelle NOSQL-Schlüssel-Wert-Datenbank für beliebig große Datenmengen ist. 

Es gibt eine Tabelle „game_data“, welche alle relevanten Informationen enthält. 

Datenstruktur

Ein Beispiel unserer Datenstruktur ist in der folgenden Abbildung zu erkennen:

Diese JSON-Datei zeigt ein laufendes Spiel mit insgesamt zwei Spielern. Ganz oben ist die Room-Id gespeichert, welche einen einzigartigen Wert aufweist. Diese ist für die Spielrunde besonders wichtig, da so mehrere Räume generiert und gleichzeitig laufen können. Der darunter gespeicherte Array „categories“ beinhaltet alle Kategorien als String-Elemente, die der Spieler, der den Raum erstellt hat, ausgewählt hat. Das Dictionary „game_players“ enthält die Informationen zu allen Spielern. Dazu gehört bspw. der Username, der als Key im Dictionary agiert, die jeweilige Raum-Id, die Punkteanzahl sowie die Eingaben, die der Spieler für eine Runde eingegeben hat. Daneben werden zwei weitere Werte gespeichert, „next_round“ und „status“, welche dazu dienen, zu erkennen, dass die nächste Runde beginnen kann, also alle Spieler bereit sind, oder ob der Spieler noch aktiv ist, also das Spiel nicht bereits verlassen hat.

Des Weiteren werden die Rundenanzahl, die Spiellänge für eine Runde, sowie in einem Array die generierten Buchstaben der Runden gespeichert. Dieser wird pro Runde immer um den nächsten zufällig generierten Buchstaben erweitert.

Datenbank-Interaktionen

Während eines Spiels müssen immer wieder Spielerdaten abgefragt, verändert, hinzugefügt oder gelöscht werden. Dazu gehören bspw. das Anlegen eines neuen Spiels, das Hinzufügen neuer Spieler zu der jeweiligen Raum-Id, das Aktualisieren der erreichten Punkte etc. Diese Interaktionen haben wir in den Lambda-Funktionen, die später noch einmal genauer erklärt werden, implementiert und sie werden aufgerufen, sobald diese benötigt werden. Da diese in Python implementiert wurden, haben wir die AWS SDK für Python, Boto3 verwendet, die eine Integration von Python-Anwendungen und Bibliotheken in AWS-Services, darunter DynamoDB, ermöglicht.

Quelle: https://aws.amazon.com/de/sdk-for-python/

Abfragen von Daten

Auch wenn Boto3 als AWS-SDK für Python nützliche Methoden bezüglich DynamoDB bereitstellt, war es nicht immer einfach, diese zu verwenden. Da unsere Datenstruktur durch die Verschachtelungen recht kompliziert war, war es aufwendig, die benötigten Informationen abzufragen. 

Die obige Abbildung zeigt einen Ausschnitt aus einer Lambda-Funktion und stellt die Abfrage von Spieldaten dar. Zunächst wird die Verbindung zu der gewünschten Datenbank (game_data) hergestellt, indem Endpoint-Url sowie die Region angegeben werden muss. Mit dieser Datenbank-Instanz kann nun mit Methoden wie „.get()“ auf die gewünschte Information in der Datenbank zugegriffen werden. Das Verwenden dieser Funktionen war demnach einfach, jedoch wurden die Daten in der DynamoDB sehr oft in Dictionaries gespeichert, welches an vielen Stellen gar nicht nötig war, was in der Abbildung durch das sich wiederholende „.get(‚L‘)“, „.get(‚M‘)“ etc. zu erkennen ist.

In Wirklichkeit waren die Daten nämlich so aufgebaut:

Man erkennt, dass die Daten durch DynamoDB zusätzlich in Dictionaries und Arrays geschachtelt werden und es war schwierig zu erkennen, um was es sich nun handelt. Das Abfragen wurde dadurch viel komplizierter gestaltet, als es eigentlich ist und war auch sehr fehleranfällig.

Anlegen von Daten

Das Anlegen neuer Daten in die Datenbank erfolgt mithilfe der Methode „put_item“, welcher ein JSON-Objekt mitgegeben werden muss. Die Abbildung zeigt das Anlegen eines neuen Raumes.

Löschen von Daten

Das Löschen von Daten erfolgt ähnlich wie das Updaten von Daten, da der jeweiligen Methode „delete_item“ mehrere Parameter mitgegeben werden können, die den Befehl spezifizieren. In unserem Projekt haben wir komplizierte Aufrufe jedoch nicht gebraucht, weshalb das Löschen im Vergleich zum Updaten von Daten einfach war.

Update von Daten

Das Updaten von Daten erfolgt mithilfe der Methode „update_item“, dessen übergebene Parameter komplexer sind als die anderen und auch abhängig davon sind, welche Typen (Array, Dictionaries, ein einfacher Wert) aktualisiert werden sollen. Um zu bestimmen, welches Objekt in der Datenbank bearbeitet werden soll, kann ein sogenannter „Key“, welcher in der obigen Abbildung die Raum-Id ist, mitgegeben werden. Zusätzlich können die übergebenen Parameter überprüft (ConditionExpression) und schließlich bestimmt werden, wie das Objekt aktualisiert werden soll. In diesem Beispiel wird dem Array, der die generierten Buchstaben eines Spiels speichert, um einen Wert erweitert, weshalb als Wert für den Parameter „UpdateExpression“ ein „list_append“ verwendet wird.

S3

AWS S3 (Simple Storage Service) ist ein Service für das Speichern von Objekten. In unserem Projekt haben wir S3 benutzt, um unsere Anwendung zu hosten. Hierfür haben wir einen S3-Bucket erstellt und dort unter „Static website hosting“ die Einstiegs- und Fehlerseite unserer Anwendung angegeben. Damit Benutzer über die dabei erzeugte URL auf unsere Seite zugreifen können, haben wir diese anschließend noch öffentlich zugreifbar gemacht und eine Policy hinzugefügt, um im öffentlichen Modus den Inhalt des Buckets lesen zu können.

Über die AWS CLI können danach immer die aktuellen Files der gebauten Anwendung in S3 hochgeladen werden, wenn die Anwendung deployt werden soll (siehe auch CI/CD Kapitel):

Ausschnitt aus der CI/CD Pipeline zum Hochladen der Dateien in S3

Lambda

„AWS Lambda ist ein Serverless-, ereignisgesteuerter Computing-Service, mit dem Sie Code für praktisch jede Art von Anwendung oder Backend-Service ausführen können, ohne Server bereitzustellen oder zu verwalten. Sie können Lambda in über 200 AWS-Services und Software-as-a-Service (SaaS)-Anwendungen auslösen und Sie zahlen nur für das, was Sie nutzen.“

https://aws.amazon.com/de/lambda/?c=ser&sec=srv

Amazon API- Gateway 

Für die definierten Lambda Funktionen hat man bei AWS die Möglichkeit eine eigene Web-API mit einem http-Endpunkt zu erstellen. Dafür kann man das Amazon API Gateway verwenden. Die Funktion des API-Gateway ist es Tools für die Erstellung und Dokumentation von Web-APIs, die HTTP-Anforderungen an Lambda-Funktionen weiterleiten, zu erstellen. 

Websocket API Routen 

Api RouteBeschreibung
broadcast_to_roomSendet eine Nachricht an alle Spieler in einem Raum anhand der Raum-Id und den in der Dynamo DB gespeicherten Connection-Ids der Spieler.
check_roundsetzt den Status der nächsten Runde des jeweiligen Benutzers und sendet die Nachricht für die nächste Runde an die Spieler im Raum, wenn alle Benutzer die nächste Runde angeklickt haben.
create_room Erstellt einen neuen Raum in der Datenbank mit den angegebenen Werten oder Standardwerten, wenn keine Werte angegeben werden.
enter_roomFügt einen Benutzer zu einem Raum hinzu, indem sein Username sowie die entsprechende Connection-Id in der Dynamo DB unter dem Schlüssel der angegeben Raum-Id gespeichert werden. 
get_current_playersPrüft, ob der Spieler den Raum betreten darf und sendet alle Spielernamen des Raumes an den neuen Spieler.
get_results_for_roomSendet Raumdaten und Spielerdaten an alle Spieler des Raums mit der angegebenen Raum-Id.
load_user_inputslädt alle Benutzereingaben aus der Datenbank und sendet alle Werte (z.B. [[“Stuttgart”, “Rhein”, “Deutschland”]]) an die Spieler des Raums mit der angegebenen Raum-Id.
navigate_players_to_next_roomNavigiert alle Spieler in einem Raum zum Spielraum und wird aufgerufen, wenn der Hauptakteur die Taste “Spiel starten” drückt.
play_roundFragt Timer, Kategorien und Rundeneinstellungen für den Spielraum über die Room-Id aus der Datenbank ab. 
remove_player_from_roomSetzt den Spielerstatus auf inaktiv in der Dynamo DB und löscht die Raumdaten aus der Datenbank, wenn alle Spieler dieses Raums inaktiv sind.
save_roundSpeichert Benutzereingaben aus Kategoriefeldern in der Datenbank.
start_roundBeginnt die nächste Runde, indem er einen neuen Buchstaben erzeugt und prüft, ob dieser Buchstabe bereits ausgewählt wurde und speichert den erzeugten Buchstaben in der Datenbank.
stop_roundStoppt eine Spielrunde, wenn jemand die Stopptaste gedrückt hat. 

Amazon CloudWatch 

Amazon CloudWatch entspricht einem Überwachungs- und Beobachtungsservice für Entwickler.  Wir wollten über CloudWatch die Verwendung unserer API protokollieren. Dabei gibt es die Möglichkeit der Ausführungsprotokollierung. Hierbei verwaltet das API-Gateway die CloudWatch-Protokolle. Es können verschiedene Protokollgruppen erstellt werden, welche dann die verschiedenen Aufrufe, Abfragen und Antworten an den Protokollstrom melden. Zu den protokollierten Daten gehören beispielsweise Fehler. 

Protokollgruppen

Logs der Methode load_user_inputs

Testing 

Beim Testing haben wir uns hauptsächlich auf das Testen der Lambda-Funktionen mit Unittests fokussiert. Dafür haben wir die Bibliothek Moto benutzt, mit der man AWS Services mocken kann. Dadurch konnten wir in den Tests unsere Datenbank mocken und beispielsweise auch testen, ob beim Aufruf der Lambdas Datenbankeinträge richtig angelegt oder Daten richtig aktualisiert werden. Allgemein muss für das Mocken der Datenbank nur die Annotation @mock_dynamodb2 über der Testklasse eingefügt werden und anschließend kann in der setUp-Methode die Datenbank definiert werden, die für die Tests benutzt werden soll. Dadurch können auch Testdaten in die Datenbank eingefügt werden, um bestimmte Testfälle zu testen.

Neben Moto haben wir die Bibliothek unittest.mock benutzt, mit der zum Beispiel das Senden einer Nachricht über die Websocket-Verbindung gemockt werden kann, oder auch der Aufruf einer Lambda-Funktion. Zudem kann man mit Methoden wie assert_called() oder assert_called_with(…) überprüfen, ob und mit welchen Argumenten die gemockte Methode aufgerufen wurde. Allgemein war dies bei unserem Projekt sehr hilfreich, da wir in fast jeder Lambda-Funktion Nachrichten über die Websocket-Verbindung schicken und somit auch testen konnten, ob die richtigen Nachrichten geschickt werden.

Für manuelle Tests oder zum kurzen Testen von bestimmten Eingabewerten war auch die Seite https://www.piesocket.com/websocket-tester sehr hilfreich, da man dort die verschiedenen Lambda-Funktionen über eine Websocket-Verbindung aufrufen kann.

CI/CD Pipeline 

CI/CD Pipeline in GitLab auf dem master-Branch

Um unsere Tests automatisiert ablaufen zu lassen und auch andere Schritte wie das Deployen der Lambda-Funktionen nicht immer manuell ausführen zu müssen, haben wir in GitLab eine CI/CD Pipeline erstellt. Da wir alle vor diesem Projekt noch nie eine CI/CD Pipeline angelegt hatten, konnten wir somit durch das Projekt auch Erfahrungen in diesem Bereich sammeln. Allgemein ist unsere Pipeline unterteilt in verschiedene Stages: In der Testing-Stage werden die Unittests für unsere Lambda-Funktionen ausgeführt. In der Build-Stage werden die aktuellen Versionen der Lambda-Funktionen über das serverless-Framework deployt und anschließend wird unsere Angular-Anwendung gebaut. Am Ende wird unsere Anwendung in der Deploy-Stage deployt, sodass sie danach über eine öffentliche Url verfügbar ist. Je nachdem, auf welchen Branch ein Entwickler in unserem Repository pusht, werden unterschiedliche Jobs ausgeführt. So werden zum Beispiel die Unittests auf jedem Branch ausgeführt, das Deployen der Lambda-Funktionen jedoch nur beim Pushen auf den develop- oder master-Branch und das Deployen der Anwendung nur beim Pushen auf den master-Branch.

Insgesamt gibt es in GitLab unter Settings -> CI/CD die Möglichkeit, Variablen anzulegen, die in der CI/CD Pipeline benutzt werden. Wir haben daher in IAM (Identity and Access Management) bei AWS einen Benutzer für die Pipeline angelegt, der nur die Rechte hat, die in der Pipeline benötigt werden. Die Keys dieses Benutzers haben wir anschließend als Variablen in GitLab angelegt, sodass zum Beispiel das Deployen der Lambda-Funktionen in der Pipeline mit diesem Benutzer durchgeführt werden kann.

Schwierigkeiten 

Während unserer Arbeit am Projekt sind uns einige Schwierigkeiten begegnet, die im Folgenden näher beschrieben werden.

Datenstruktur

Wie bereits beschrieben hatte sich das Abfragen von Daten in der DynamoDB als sehr aufwendig und komplex dargestellt, auch wenn der Methodenaufruf an sich leicht zu verstehen und einfach ist. Durch die weiteren Verschachtelungen, die AWS zusätzlich zu unserer bereits komplexen Datenstruktur hinzugefügt hat, wurde das Abfragen von Daten schwieriger dargestellt, als es eigentlich ist und auch der Code, den wir dafür geschrieben haben, wurde durch dieses sehr unleserlich. Dadurch kam es in unserer Gruppe oft zu Fehlern bei den Abfragen, da der Fehler nicht direkt erkannt wurde und die endgültige Datenstruktur in der Datenbank für unsere Gruppe unklar wurde. 

Wir haben diesbezüglich, auch nachdem wir die Struktur festgelegt hatten, mehrmals gelesen, dass DynamoDB für komplexe Datenstrukturen und Interaktionen (auch das Updaten von Daten) schlichtweg nicht geeignet ist. Dennoch haben wir es dabei belassen, da wir bereits im Projekt weit fortgeschritten waren und keine Zeit mehr hatten, eine Alternative zu finden.

Deployen von Lambdas

Damit die Lambdas, die wir in der serverless.yaml definiert und in den jeweiligen Handler-Dateien implementiert hatten, in unserer Anwendung aufgerufen werden konnten, war es nötig, den Befehl „serverless deploy“ aufzurufen, der alle definierten Lambdas in den Handler-Dateien deployed. Das Problem war, dass dadurch bestehende Funktionen, an denen andere Gruppenmitglieder arbeiteten, anschließend überschrieben wurden, was das Arbeiten sehr behinderte, wenn man an dem Projekt gleichzeitig arbeitete.

Um diese Situation weitestgehend zu verhindern, haben wir beschlossen, die Methoden auf dem Development Branch allesamt mit dem Befehl „serverless deploy“ zu deployen. Anschließend wird auf unterschiedlichen Branches gearbeitet und anschließend nur die Funktion, an der man gerade arbeitet, mit dem Befehl „serverless deploy function –function [Name der Funktion]“ deployed. Dieses hat nur teilweise funktioniert, da das Deployen einer einzigen Funktion nur möglich war, wenn diese bereits existiert, also durch „Serverless deploy“ deployed wurde.

Fazit

Alles in allem haben wir durch das Projekt einen Einblick in die Möglichkeiten von Cloud Computing bekommen und konnten verschiedene Dinge in diesem Bereich ausprobieren. Wir konnten vor allem Erfahrungen mit AWS Lambda, API Gateway und dem serverless Framework sammeln, da dies der Schwerpunkt unseres Projektes war. Zudem haben wir einige grundlegende Dinge gelernt, zum Beispiel dass es sinnvoll ist, schon früh im Projekt eine CI/CD Pipeline aufzubauen oder auch CloudWatch zu aktivieren, um Fehler in den Lambda Funktionen schneller zu erkennen und beheben zu können.

Allgemein haben wir durch das Projekt auch gelernt, dass es sehr wichtig ist, sich am Anfang Gedanken zu Themen wie Security oder auch dem generellen Aufbau des Projektes zu machen. Aus Zeitgründen verfolgt man sonst oft die am schnellsten funktionierende Lösung und kann dann später nur mit viel Aufwand grundlegende Dinge ändern. Für das nächste Projekt würden wir daher mehr Zeit für die Einarbeitung und Recherche einplanen, um dies zu vermeiden. Bei unserem Projekt wäre es vor allem sinnvoll gewesen, schon von Beginn an die verschiedenen Umgebungen einzurichten, sodass alle Entwickler lokal unabhängig voneinander entwickeln und testen können und man auf der Entwicklungsumgebung Änderungen durchführen kann, ohne die Produktivumgebung zu beeinflussen.

TLA+ and PlusCal Appetizer

“Temporal logic of actions (TLA) is a logic developed by Leslie Lamport, which combines temporal logic with a logic of actions. It is used to describe behaviours of concurrent systems.” [1]

TLA+ is a formal specification language built on top of the TLA logic. It is used to design and verify the correct behavior of programs and is especially useful in distributed and concurrent systems. PlusCal is a formal specification language as well that gets transcompiled to TLA+. It is used to specify algorithms.

Continue reading

An overview of Large Scale Deep Learning

article by Annika Strauß (as426) and Maximilian Kaiser (mk374)

1. Introduction

One of the main reasons why machine learning did not take off in the 1990s was that the lack of computational power and  the size of data sets, available at that time

Since then, a lot has changed and machine learning methods have found their way into the field of ultra large systems (ULS) like f.e. Google, where they have been used very successfully for quite some time.

Two main areas of application can be distinguished:

  • Learn better ML models faster with very large data sets and very high computing power by parallelizing and distributing different components of the ML computation.
  • Deep Learning methods are developed, trained and applied to control, understand, improve and optimize specific areas within a ULS, e.g. replace multiple overcomplicated subcomponents with a single, machine learned model that still does the same job
Continue reading

Games aus der Cloud, wo sind wir und wohin geht die Reise?

Cloud Gaming – flüssiges Zocken auch mit schlechter Grafikkarte? (esports.com)

Was genau ist Cloud Gaming?

Cloud Gaming lässt sich mit Remote Desktops, Cloud Computing und Video on Demand Diensten vergleichen. Im Grunde beinhaltet Cloud Gaming das Streamen von Videospielen aus der Cloud zum Endkunden. Dabei erfasst und überträgt der Client seine Nutzereingaben (bspw. Maus, Tastatur, Controller) an den Server. Während dieser wiederum die Gesamtspielweltberechnung sowie Nutzereingaben-Auswertung bewältigt. Der Client stellt lediglich die Kapazität bereit, die Frames in der gewünschten Qualität gestreamt zu bekommen. Wesentliche Vorteile ergeben sich für leistungsschwache Geräte, während der offenkundige Nachteil in der Notwendigkeit einer gut ausgebauten Dateninfrastruktur liegt.

Neben Game Streaming gehört zum Cloud Gaming auch das Hosten von Serverinstanzen für Onlinespiele, sowie das Bereitstellen von Plattformdiensten (bspw. Bestenlisten, Chatsysteme, Authentifizierung etc.). Wesentlicher Bestandteil ist des Weiteren das Angebot von leistungsfähigen Downloadservern.

Plattformdienste und etablierte Nutzung der Cloud

Plattform- oder Onlinedienste bieten Schnittstellen, welche Spielemetadaten zur Verfügung stellen. Diese Dienste Umfassen in der Regel Funktionalitäten wie Bestenlisten, Chat- und Gruppensysteme sowie Onlinelobbys, aber auch Metadienste wie Authentifizierung, Analyse, Zuordnung von Spieleridentitäten und Matchmaking. Hierbei können Dienste sowohl öffentlich im Internet als auch intern für andere Dienste zur Verfügung stehen. Während diese Systeme zu Beginn ihres Aufkommens noch als einzelne Monolithen bereitgestellt wurden, bieten heutige Cloudbetreiber solche Dienste als Microservices an. Dies sorgt dafür, dass sich die hohe Skalierbarkeit der Cloud auf die Plattformdienste übertragen lässt. Diese Skalierbarkeit ist vor allem deshalb wichtig geworden, da der Spielemarkt in den letzten Jahren stark gewachsen ist und auch die Spiele selbst immer ressourcenintensiver geworden sind. Als gutes Beispiel kann man hierfür eine der größten Spieleplattformen hernehmen: Steam. Steam integriert viele der angebotenen Spiele in die eigenen Plattformdienste. Dies umfasst Beispielsweise eine Freundesliste, Chatsysteme, Matchmaking und Verbindungssysteme. Zusätzlich wird auch die dahinter liegende Infrastruktur für die Vermarktung der Spiele von Steam gestemmt. Dies umfasst einen Webshop und Downloadserver.

Der große Aufwand und die Nachfrage nach diesen Diensten zeigen sich anhand des weltweiten Datenverkehrs von Steam. So kommt zum Zeitpunkt dieses Blogeintrags allein der deutsche Datenverkehr auf über 35 Petabyte innerhalb der letzten 7 Tage. Und dies wiederum entspricht lediglich ca. 4,3% des weltweiten Datenvolumens.

Aufnahme der Steam Statistiken am 16.03.2022 um 14.48 Uhr – Steam: Game and Player Statistics (steampowered.com)

Diese Zahlen steigen vor allem dann rasant an, wenn es zu speziellen Aktionen kommt, wie etwa dem Release eines neuen, stark erwarteten Spieles oder Sale-Aktionen vergleichbar mit einem Black-Friday für Games. Hierbei kommt es dazu, dass teilweise Millionen von Spielern gleichzeitig das neue Produkt erwerben und herunterladen wollen.

Diese starke Belastung spüren aber nicht nur Shop- und Downloadserver, sondern natürlich auch die klassischen dedizierten Spieleserver selbst. Gerade beim Release eines neuen Massive Multiplayer Onlinespiels (MMO) oder einer neuen großen Inhaltserweiterung versuchen sich gleichzeitig mehrere tausende Spieler auf den Spieleservern einzuloggen, während es zum Normalbetrieb meist nur ein Bruchteil der User ist. Auch hier hilft die hohe Skalierbarkeit der Cloud. Da solche Ereignisse normalerweise zu dem Betreiber bewussten und geplanten Zeiten auftreten können allerdings im Vorfeld schon Ressourcen reserviert und bereitgestellt werden.

Technische Herausforderungen des Game Streaming

Während Plattform- oder Onlinedienste auf die heute weit verbreiteten und gut etablierten Microservice Strukturen und Architektur zurückgreifen, eröffnen sich mit Games as a Service oder Game Streaming ganz neue Herausforderungen. Die Simulation des eigentlichen Spiels kann noch problemlos in einer emulierten Umgebung ablaufen und seine Inputs von außen beziehen, sowie das berechnete Resultat nach außen über einen Video stream abgeben. Das wahre Problem liegt allerdings in der Latenz. Bei Games handelt es sich im Gegensatz zu den meisten anderen Medien um ein interaktives Medium. Das heißt auf eine Aktion des Nutzers muss idealerweise eine unmittelbare Reaktion des Mediums erfolgen. Bei Game Streaming sind die Ansprüche daran besonders hoch, wenn es mit anderen interaktiven Medien wie einem Livestream mit Live Chat verglichen wird. Hier sind Verzögerungen von bis zu 1 Sekunde noch akzeptabel. Bei Spielen hingegen wird eine Latenz von wenigen Millisekunden erwartet. In dieser Zeit muss also die Eingabe beim Client registriert, an den Server geschickt, dort verarbeitet und ein neues Bild an den Client zurückgeschickt, decodiert und angezeigt werden.

Eine abstrahierte, allgemeine Darstellung der Abläufe eine Game Streaming Dienstes – CloudRetroPicture.png (787×492) (webrtchacks.com)

Was kann Cloud Gaming?

Viele Vorteile aus Cloud Computing und Video on Demand Diensten können sich direkt auf Game Streaming übertragen lassen.

  • Für das Spielen von Games aus der Cloud wird keine teure, eigene Hardware benötigt. Der Streamingdienst Betreiber stellt die nötige Hardware zur Verfügung, um das gewünschte Spiel auf einer maximalen Qualitätsstufe darstellen zu können.
  • Um die Wartung, Instandhaltung und Modernisierung der Hardwaresysteme kümmert sich der Streaming Anbieter. Für den Endkunden fallen dadurch keine hohen Einzelkosten an.
  • Spiele stehen jederzeit und überall auf vielen verschiedenen Endgeräten zur Verfügung. Auch leistungsschwache Geräte wie Smartphones, Smart TVs oder einfache Laptops können somit zum Spielen Hardwareintensiver Titel verwendet werden
  • Das Manipulieren und Betrügen in Online- und Singleplayerspielen wird durch das Streaming erheblich erschwert. Dies resultiert direkt aus der Begrenzung der Interaktionspunkte mit dem Spiel. Lediglich die Bildausgabe und die Nutzereingabe finden auf dem lokalen Gerät statt. Jegliche weitere Informationsverarbeitung, wie beispielsweise die Position eines Mitspielers bleiben dem Nutzer verborgen.

Neben den Vorteilen gibt es jedoch auch einige Nachteile:

  • Für Gamestreaming ist zwingend eine durchgehende, leistungsstarke Internetverbindung von Nöten. Denn im Gegensatz beispielsweise zum Video on demand kann bei Game Streaming kein Buffering verwendet werden. Dies ist bei Spielen allerdings nicht möglich, da der weitere Verlauf des Spieles direkt von den Eingaben des Spielers abhängig ist. Ein Verbindungsverlust führt somit zwangsläufig zu einer abrupten Unterbrechung der Spielsession.
  • Schwankungen in der Bandbreite führen zu einer Drosselung der Bildqualität und mindern damit das Spielerlebnis.
  • Das Übertragen bereits in Besitz befindlicher Spiele werden von vielen Anbietern nicht unterstützt. Dies kann dazu führen, dass Spiele entweder nicht zur Verfügung stehen oder nochmals auf der Streaming Plattform gekauft werden müssen.
  • Das Modifizieren der eigenen Spieldaten wird unterbunden, da nicht lokal auf die Spielinhalte zugegriffen werden kann. Dies verhindert, dass Spieler Modifikationen erstellen und ihr Spielerlebnis bei Bedarf individuell anpassen können.
  • Der Anbieter entscheidet welche Publisher und Franchise in seinem Portfolie aufgenommen werden. Dies erschwert vor allem kleine Studios oder Indie Entwickler sich auf dem Markt zu etablieren.
  • Spiele stehen zusätzlich nur solange zur Verfügung so lange sie sich im Angebot des Streamingdienst befinden oder dieser die Lizenzen hierfür besitzt.

Fazit

Cloud Gaming umfasst mehr als nur Game Streaming. Es ist bereits fester Bestandteil der heutigen Infrastruktur, da ein Großteil der Spiele auf Cloud-Dienste zurückgreift oder durch Plattformen wie bspw. Steam in diese integriert wird. Zwar steht Game Streaming an sich gerade erst in den Startlöchern, doch es würde mich nicht verwundern, wenn immer mehr Nutzer umsteigen oder zumindest teilweise auf dessen Vorteile zugreifen würden. Meiner Meinung nach wird es in absehbarer Zeit kein kompletter Ersatz für alle Spieler werden. Allerdings bin ich der Überzeugung, dass Game Streaming zum Netflix der Spieler wird, da es aufgrund der vorhandenen Technologien, Infrastruktur und Kunden über ein hohes Potential verfügt. Die Entwicklung der Streaming Ttechnologien steht allerdings erst an ihrem Anfang. Eine Weiterverfolgung wird sich in jedem Fall lohnen.

Quellen

Überblick über die Cloud-Gaming-Infrastruktur  |  Cloud Architecture Center  |  Google Cloud
Microservices – Wikipedia
Unreal Pixel Streaming in Azure – Azure Gaming | Microsoft Docs
Azure for Gaming – Azure Gaming | Microsoft Docs
Cloud-Gaming: Die besten Anbieter im Vergleich | heise Download
Cloud-Gaming im Vergleich: Die besten Spiele-Streamingdienste | NETZWELT
Cloud Gaming – flüssiges Zocken auch mit schlechter Grafikkarte? (esports.com)
Cloud gaming – Wikipedia
Xbox Cloud Gaming – Wikipedia
What is cloud gaming and how does it work? – Dot Esports
How Cloud Gaming and Streaming Games on Stadia and xCloud Works (makeuseof.com)
Open Source Cloud Gaming with WebRTC – webrtcHacks
Youtube: Google WebRTC project update & Stadia review

Logging im großen Maßstab mit Grafana Loki

Heutzutage erzeugen die meisten Systeme und Anwendungen Logging-Daten die für Sicherheits- und Überwachungszwecke nützlich sind, z. B. für die Fehlersuche bei Programmierfehlern, die Überprüfung des Systemstatus und die Erkennung von Konfigurationsproblemen oder sogar Angriffen. Treten Ereignisse innerhalb einer Anwendung auf, werden diese von integrierten Protokollierungsfunktionen erfasst und mit zusätzlichen Metadaten aufgezeichnet. Mit dem Wachstum von Microservice-Anwendungen ist die Protokollierung für die Überwachung und Fehlerbehebung dieser Systeme wichtiger als je zuvor. 

Besitzt man beispielsweise nur einen kleinen Webserver, ist es evtl. noch möglich die erzeugten Logs täglich zu kontrollieren. Nur wie sieht es aus wenn mitten in der Nacht etwas passiert? Oder beim Betrieb eines großen verteilten Systems? Wie können diese Daten zentral gesammelt und sinnvoll genutzt werden? Hier kommen Log-Aggregation-Systeme ins Spiel, welche bei den Anforderungen an die Beobachtbarkeit und den Betrieb jedes großen, verteilten Systems unterstützen.

In diesem Blogpost möchte ich auf den relativ neuen und immer populärer werdenden Logging-Stack Grafana Loki eingehen. Wie ist es aufgebaut? Wo sind die Unterschiede zu beispielsweise ELK (Elasticsearch, Logstash und Kibana). Was macht es so besonders und warum ist es für große Firmen wie Red Hat interessant, welche in ihrer neuesten Version OpenShift 4.10 Grafana Loki Support anbieten werden?

Continue reading

Um die Technik kümmern sich die Anderen…

Das Phänomen von ‘sozio-technischen Systemen’ und ‘Ökosystemen’

Mammut aus Gras von Christopher Alvarenga – [4]

Wenn wir uns als technisch begeisterte Computer Science Studierenden ein zu Deutsch “Ultra Großes, Skaliertes System” vorstellen… Was stellen wir uns dann wirklich vor? Naja wir denken wahrscheinlich erstmal an viele Zeilen Code, Unmengen von Server-Hardware und ellenlange Kabel. Das allein macht ein Ultra Large Scaled System (ULS) aber noch lange nicht aus.

Continue reading

Discord Monitoring System with Amplify and EC2

Abstract

Discord was once just a tool for gamers to communicate and socialize with each other, but since the pandemic started, discord gained a lot of popularity and is now used by so many other people, me included, who don’t necessarily have any interest in video gaming. So after exploring the various channels on discord, I found that most of them have some kind of rules that users have to adhere to. But with channels that have more than 100 members, moderating becomes really tedious. The idea behind this project, which is a part of the lecture Software Development for Cloud Computing, is to automate this process by creating a discord monitoring system.

Features

With this system, an admin can add words that they want to be prohibited in the chat. These words are then used to delete messages that don’t adhere to the rules or to flag these messages for an admin to review when the system is not 100% certain that the message contains harmful content.

Architecture

Frontend

The client side is fairly simple, it is built with React.js and only has two sections. The first is flagged messages, where a moderator is able to review messages the system has flagged.

The second is where a moderator can add or remove censored words as shown below.

Backend

The server side of this monitoring system is built using express.js. The library discord.js was used to communicate with the discord api, and for the database I used dynamoDB.

The main part of the app is the following code:

discord.client.on("ready", () => {
  console.log(`Logged in as ${discord.client.user?.tag}!`);
  discord.client.on("message", (msg) => {
    const status = monitorSys.getScannedMessageStatus(msg.content);
    if (status === "FLAG") {
      flaggedMessagesController.addFlaggedMessage(msg);
    } else if (status === "HARMFUL") {
      messagesController.deleteHarmfulMessage(msg.id);
    } else {
      console.log("SAFE");
    }
  });
});
start();
discord.client.login(process.env.DISCORD_KEY);

Here the bot goes online and starts scanning every message that goes into the chat. With a helper method the system then either flags a message or deletes it right away if it contains harmful content.

EC2

The backend is deployed on an ec2 instance that is based on an Amazon Linux image. When creating this instance we add a script, as shown below, to install the CodeDeploy Agent that we are going to use later for the CI/CD.

#!/bin/bash
sudo yum -y update
sudo yum -y install ruby
sudo yum -y install wget
cd /home/ec2-user
wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install
sudo chmod +x ./install
sudo ./install auto

Then we need to add these rules as shown below. The one with port 80 is necessary to be able to download data from the internet and the one with port 3000 is for the client to have access to our server.

CI/CD

Frontend

CI/CD On the frontend was done using GitHub actions and amplify. With GitHub I created an action that runs whenever a branch gets pushed. This pipeline runs our tests first and checks if all succeed. If one or more tests fail, this branch can’t be merged to main. This restrictions can be activated directly from the settings of our repository on GitHub. To create this action, create a file ./.github/workflows/tests.yml with the following:

name: Node.js CI
on:
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm install
    - run: npm test

After a branch gets merged with main, Amplify builds the new version and deploys the app when the build is ready. Amplify provides a really straightforward way to deploy a React app. We just have to create a new app on Amplify and connects it with a specific branch on GitHub.

Amplify then detects the Javascript library that’s being used e.g. React, Vue or Angular and adjust the settings accordingly. Environments variables can be added directly on Amplify.

Backend

Here I used CodeDeploy to deploy the newest version of our backend whenever it’s pushed to the main branch. To achieve this, we need to first create a pipeline on CodeDeploy and connect it to our repository on GitHub, which is similar to what we did on Amplify. After that we need to add some scripts to our project for CodeDeploy. These files should be stored inside of ./scripts.

The first script file we need is called before_install.sh

#!/bin/bash

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
. ~/.nvm/nvm.sh
nvm install node

DIR="/home/ec2-user/discord-monitoring-server"
if [ -d "$DIR" ]; then
  echo "${DIR} exists"
else
  echo "Creating ${DIR} directory"
  mkdir ${DIR}
fi

With that we first download node and npm and then we create a working directory if it does not already exist.

Then we create a file called application_start.sh

#!/bin/bash
#give permission for everything in the discord-monitoring-server directory
sudo chmod -R 777 /home/ec2-user/discord-monitoring-server
#navigate into our working directory where we have all our github files
cd /home/ec2-user/discord-monitoring-server
#add npm and node to path
export NVM_DIR="$HOME/.nvm" 
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # loads nvm 
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # loads nvm bash_completion (node is in path now)
#install node modules
npm install
#start our node app in the background
npm start > app.out.log 2> app.err.log < /dev/null &

And lastly, we have application_stop.sh:

#!/bin/bash
#Stopping existing node servers
echo "Stopping any existing node servers"
pkill node

After creating these scripts we also need to add the appspec.yml file, which specifies the target folder in our ec2 instance and 3 hooks, each with its respective script file we created earlier.

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/discord-monitoring-server
hooks:
  ApplicationStop:
    - location: scripts/application_stop.sh
      timeout: 300
      runas: ec2-user
  BeforeInstall:
    - location: scripts/before_install.sh
      timeout: 300
      runas: ec2-user
  ApplicationStart:
    - location: scripts/application_start.sh
      timeout: 300
      runas: ec2-user

Conclusion

During the Software Development for Cloud Computing lecture, we learned a lot of theory about cloud computing and the different services Amazon and IBM provides. We also got to see some examples on the different subjects that were discussed during the semester. But with this project I was only able to implement and work on some of these topics. The first and main one was deploying the app on Amplify and EC2 and the second, which I personally found very interesting, is continuous integration and continuous delivery. This lecture gave me a really good overview on what can be achieved with cloud computing and I am hoping to learn way more about the topic in the near future.

“Himbeer Tarte und harte Fakten”: Im Interview mit Ansible, k3s, Infrastructure as Code und Raspberry Pi

Why so serious? – Ein Artikel von Sarah Schwab und Aliena Leonhard im Rahmen der Vorlesung Systems Engineering and Management.

Die Idee, ein fiktives Interview zu erstellen, entstammt daraus komplexe Sachverhalte unterhaltsam und verständlich zu machen.

Raspberry Fruit

Wir sind heute zu Gast in der Tech-Sendung “Himbeer Tarte und harte Fakten”.  Heute geht es unter Anderem um die Themen “Raspberry Pi”, “Infrastructure as Code” und “Ansible”. Vier Experten haben wir zu einer Diskussionsrunde eingeladen. Herzlich Willkommen, Frau Ann Sibel, Herr Kah Dreis, Herr Archie Tex-Ture und Frau Infra Struc-Ture. 

Continue reading

Software Dependencies – Die Gefahr aus der Tiefe

“Unbekannte infiltrieren Paketmanager npm und verseuchen Tools mit Schadcode” heißt es am 08.11.2021 auf heise online. Die Nutzeraccounts der Maintainer von coa und rc wurden gehackt und neue Versionen dieser Pakete hochgeladen (inklusive Malware). Zwar denken sich bestimmt viele bei coa und rc: “Aha toll”, aber spätestens bei React oder Angular sollte man hellhörig werden. Schließlich werden diese JavaScript-Frameworks Millionenfach genutzt. Und wer jetzt richtig geraten hat, ahnt schon: Beide dieser Frameworks nutzen diese Bibliotheken auf die ein oder andere Weise. Dieser Blockartikel beschreibt die Risiken von Software-Dependencies in Produktivsystemen und Wege, damit umzugehen.

Karikatur eines Softwareprojekts (Quelle: GitHub)
Continue reading