Wir bringen eine Java-Anwendung in die Cloud - Teil 4

Migration nach AWS: Going Serverless

Christian Müller

©Shutterstock / CoreDESIGN

In diesem Artikel möchten wir prüfen, ob unser DevOps-Team nicht noch weitere Verantwortlichkeiten in Bezug auf den Betrieb und die Wartung der Anwendung abgeben kann. Unser Team soll möglichst viel Zeit mit der Entwicklung neuer Funktionalitäten verbringen, anstatt Infrastruktur zu managen, zu skalieren und zu patchen.

Im dritten Teil dieser Serie haben wir unsere monolithische Anwendung durch die Zerlegung in verteilte Microservices wartbarer gemacht. Das ermöglicht es uns, diese unabhängig voneinander zu skalieren. Der Einsatz von verwalteten Services wie dem Application Load Balancer (ALB) [1], dem Amazon Relational Database Service (RDS) [2], Amazon ElastiCache [3] und Containern, die in AWS Fargate [4] laufen, hat uns bereits erhebliche Vorteile in Bezug auf den Betrieb, die Wartung und die Skalierbarkeit der Anwendung gebracht. Wir haben ebenfalls aufgezeigt, wie durch die Verwendung von asynchroner Kommunikation zwischen den einzelnen Microservices die Ausfallsicherheit erhöht werden kann.

Artikelserie

Teil 1: Warum wechseln?

Teil 2: Verwendung von Managed Services

Teil 3: Verwendung von Containern

Teil 4: Going Serverless

Teil 5: Optimierung der Architektur

Teil 6: Sicherheit und Verschlüsselung

Wir möchten uns einen weiteren Aspekt von serverlosen Architekturen zunutze machen. Anstatt für provisionierte Ressourcen zu zahlen, die wir gegebenenfalls nicht vollständig nutzen, wünschen wir uns eine nutzungsbasierte Abrechnung. Diese macht es einfacher, die Kosten auf einen Geschäftsprozess umzulegen und die Rentabilität eines Service zu ermitteln. Wird die Infrastruktur nicht genutzt, möchten wir möglichst nichts dafür bezahlen.

Führen wir uns zur Erinnerung unsere aktuelle Architektur noch einmal vor Augen, bevor wir sie weiter optimieren (Abb. 1).

Abb. 1: Die aktuelle Architektur unserer containerisierten Anwendung

Abb. 1: Die aktuelle Architektur unserer containerisierten Anwendung

API-Management und Deployments – warum das Rad neu erfinden?

Starten wir an der Schnittstelle zu unseren Kunden – unserem API. In unserer aktuellen Implementierung nutzen wir Spring Boot, um unser API zu implementieren, und veröffentlichen Letzteres hinter einem Application Load Balancer. Jede Zeile Code, die wir schreiben, ist auch gleichzeitig eine Verpflichtung und bringt Verantwortlichkeiten mit sich. Damit stehen wir in der Verantwortung, dieses API zu managen, zu patchen, zu versionieren, hochverfügbar zu gestalten, zu skalieren und mittels des Load Balancer unter einem fixen URL bereitzustellen. Um diese Verantwortlichkeiten für unser Team weiter zu reduzieren, nutzen wir zukünftig das Amazon API Gateway [5], um unser API zu veröffentlichen. Das Amazon API Gateway ist ein vollständig verwalteter Service, der uns erlaubt, unser API deklarativ zu beschreiben und unsere Endpunkte mit diversen Compute Resources zu verbinden. Das können traditionelle Anwendungen im Rechenzentrum sein, virtuelle Maschinen in der Cloud, Container oder auch nur einzelne Funktionen, die nur die auszuführende Businesslogik beinhalten (Abb. 2). Für die deklarative Beschreibung des API können wir unter anderem die OpenAPI-Spezifikation (vormals Swagger) nutzen.

Abb. 2: Unterschiedliche Integrationsoptionen des Amazon API Gateway

Abb. 2: Unterschiedliche Integrationsoptionen des Amazon API Gateway

Bei unserem Ansatz haben wir bisher Aspekte wie feingranulares Zugriffsmanagement, Drosselung von Anfragen basierend auf Nutzungsplänen, Monetarisierung des Service, Caching im API-Layer oder das Tracing von Anfragen außer Acht gelassen. In einer realen Anwendung sind diese jedoch durchaus relevant: Amazon API Gateway macht es uns sehr einfach, diese Aspekte – wenn notwendig – zum gegebenen Zeitpunkt per Konfiguration hinzuzunehmen. Für unsere Anwendung entscheiden wir uns, AWS X-Ray [6] zu nutzen, um Tracinginformationen in unserer Anwendung aufzuzeichnen. Das hilft uns, ein besseres Verständnis dafür zu bekommen, wie Anfragen in unserer Anwendung verarbeitet werden, wie viel Zeit wir in den Verarbeitungsschritten benötigen, wo Fehler aufgetreten sind und welche Kunden betroffen waren. Wir gehen später detaillierter auf das Thema Monitoring ein.

Für das Zugriffsmanagement unterstützt das Amazon API Gateway zahlreiche Verfahren. Neben der Bereitstellung öffentlicher APIs, die frei zugänglich sind und keine Authentifizierung erfordern, können wir ein Amazon API Gateway auch so bereitstellen, dass es nur von einer ausgewählten Amazon Virtual Private Cloud (VPC) [7] aus erreichbar ist. Das ist vor allem für APIs sinnvoll, die nur innerhalb unserer Firma genutzt werden.

Amazon API Gateway unterstützt auch die Nutzung von SigV4 [8]. Damit werden nur die Anfragen verarbeitet, die mit einer validen und gültigen Signatur versehen sind. Die zur Erzeugung der Signatur genutzten Schlüssel müssen zu dem AWS-Konto gehören, in dem das Amazon API Gateway läuft, und einer Rolle zugewiesen sein, die die Berechtigung hat, dieses Amazon API Gateway aufzurufen.

Eine weitere Möglichkeit des Zugriffsmanagements ist die Nutzung und Integration von Amazon Cognito [9] in das Amazon API Gateway. Dabei können wir uns entscheiden, ob wir die Autorisierungsdaten unserer Kunden sicher in einem Amazon Cognito User Pool speichern oder ob wir Amazon Cognito Federated Identities nutzen, um mit bestehenden Identity-Providern zu interagieren. Aktuell unterstützt Amazon Cognito Federation mit Facebook, Google, Amazon, Twitter, Digits oder mittels SAML 2.0 mit z. B. Microsoft AD FS, AuthO, Okta oder OneLogin.


Sollten all diese Möglichkeiten nicht unseren Anforderungen entsprechen – zum Beispiel, weil wir unsere eigene Benutzerverwaltung ansprechen müssen, um bereits existierende Zugangsdaten zu verifizieren – können wir einen AWS Lambda Authorizer verwenden. Dieser hat bei Bedarf Zugriff auf den gesamten Request und liefert in der Antwort die gewährten Berechtigungen in Form eines Amazon Identity and Access Management (IAM) Policy Document [10] zurück. Das ist der flexibelste Weg, unsere eigene Benutzerverwaltung zu integrieren. Diesen sollten wir aber nur wählen, wenn keine der vorherigen Möglichkeiten passend für uns ist, um die zusätzlichen Kosten der AWS-Lambda-Aufrufe [11] zu sparen.

Darüber hinaus bietet uns das Amazon API Gateway die Möglichkeit, Client SDKs für Android, JavaScript, iOS (Objective-C und Swift), Ruby oder Java zu generieren. Das vereinfacht die Implementierung unseres API bei unseren Kunden und kann helfen, die Adaption zu erhöhen.

Event-getriebene Architektur – funktioniert das mit APIs?

Wie oben bereits angedeutet, integriert sich das Amazon API Gateway nahtlos mit AWS Lambda. Das Amazon API Gateway erzeugt für jeden Aufruf unseres API ein Event, das alle Daten des Aufrufs beinhaltet (HTTP-Header, Query-Parameter, Payload, Security Context etc.) und übergibt dieses an AWS Lambda. Unsere AWS-Lambda-Funktion greift auf die Daten des Events zu, um die gewünschte Businesslogik auszuführen, und antwortet synchron auf den Aufruf, sofern eine Antwort gewünscht ist. Diese Integration möchten wir nutzen, um unsere Businesslogik in kleinen, dedizierten Funktionen bereitzustellen, die unsere API-Aufrufe verarbeiten. Das hilft uns, die Komplexität gering zu halten und die Entwicklungsaufgaben im Team gut zu verteilen. Dabei möchten wir uns nicht um die Provisionierung, die Hochverfügbarkeit, die Sicherheit oder die Skalierung von Servern oder Containern Gedanken machen müssen.

AWS Lambda erfüllt all diese Anforderungen. Die von uns hochgeladene Funktion wird in mehreren Verfügbarkeitszonen unserer gewählten Region provisioniert, sodass eine Störung in einer Verfügbarkeitszone keine Auswirkung auf die Verfügbarkeit unserer AWS-Lambda-Funktion hat. Den Zugriff auf unsere Funktionen und die Berechtigungen, auf welche Ressourcen die Funktionen zugreifen können, werden wie gewohnt in AWS mittels AWS IAM festgelegt. Durch die Zuordnung einer AWS-Lambda-Funktion auf genau einen Endpunkt macht es uns die Umsetzung von Least Privileges einfach. Jede Funktion bekommt nur genau die Rechte zugewiesen, die sie benötigt, um ihre Aufgabe zu erfüllen.

Sicherheit und Regelkonformität in serverlosen Architekturen

Sicherheit und Regelkonformität sind in AWS als geteilte Verantwortung [12] zwischen AWS und deren Kunden festgelegt. Wo genau die Verantwortung von AWS endet und die des Kunden beginnt, hängt vom gewählten Service ab. Grundsätzlich kann man sagen: Je stärker ein Service die darunterliegende Technology abstrahiert und je stärker er verwaltet wird, desto mehr Verantwortung übertragen Kunden an AWS. Wenn wir das beispielhaft für AWS Lambda betrachten (Abb. 3), stellen wir fest, dass sich AWS um die Netzwerksicherheit, Verschlüsselung des Codes, Firewallkonfiguration, Patches des Betriebssystems, das Patchen unserer Laufzeitumgebung (Java) und das Monitoring kümmert. Weiterhin sind Kunden für die Sicherheit ihres Codes verantwortlich. Das schließt die Vergabe von Zugriffsrechten ein sowie die sichere Speicherung von Passwörtern, Überprüfung von Drittbibliotheken auf bekannte Sicherheitsschwachstellen, Schutz vor der Einschleusung von Schadcode (SQL Injection oder XSS) und die sichere Übertragung, Verarbeitung und Speicherung der Kundendaten. Einen detaillierteren Einblick in dieses Thema bietet das Whitepaper „Security Overview of AWS Lambda“ [13].

Ähnlich verhält es sich mit den anderen gewählten Services in unserer Architektur. Auch hier haben wir weitere Verantwortlichkeiten wie das Patchen des Betriebssystems oder das Patchen der darunterliegenden Laufzeitumgebung an AWS übertragen. Das erhöht die Sicherheit unserer Anwendung und hilft unserem Team, sich auf die Geschäftslogik zu fokussieren.

Abb. 3: Geteilte Verantwortung in Bezug auf Sicherheit und Regelkonformität für AWS Lambda

Abb. 3: Geteilte Verantwortung in Bezug auf Sicherheit und Regelkonformität für AWS Lambda

AWS Lambda unterstützt die Umsetzung von Microservices, indem es diverse Laufzeitumgebungen unterstützt. So kann jedes Team die Sprache wählen, mit der es vertraut ist und die in die Problemdomäne passt. Aktuell sind das Node.js (10.x und 12.x), Python (2.7, 3.6, 3.7 und 3.8), Ruby (2.5), Go (1.x), .Net Core (2.1) und Java (8 und 11). Sollten wir die Unterstützung einer anderen Laufzeitumgebung benötigen, weil wir z. B. unsere Funktion mittels GraalVM in ein natives Image kompiliert haben, so unterstützt AWS Lambda das über eigene Laufzeitumgebungen, sogenannte Custom Runtimes.

Serverlose Datenbanken – Fiktion oder Realität?

Wie wir in einem vorherigen Artikel schon detaillierter ausgeführt haben, bietet Amazon RDS MySQL eine einfache und schnelle Möglichkeit, eine hochverfügbare Datenbank in AWS zu provisionieren. Darüber hinaus nimmt es unserem Team die Verantwortung ab, das Betriebssystem und das Datenbankmanagementsystem patchen zu müssen oder regelmäßig Back-ups zu erstellen.

In unserer Anwendung, die nur eine einfache CRUD-Schnittstelle (Create, Read, Update, Delete) bereitstellen muss, kennen wir das Zugriffsmuster auf die Daten sehr genau, und wir greifen immer mittels eines Schlüssels zu. Darüber hinaus rechnen wir mit einem sehr hohen Aufkommen von Lese- und Schreibaufrufen. In Spitzenzeiten muss unsere Anwendung in der Lage sein, ein Vielfaches des üblichen Aufkommens zu verarbeiten. Aus diesen Gründen entscheiden wir uns, unsere Amazon-RDS-MySQL-Datenbank durch eine Amazon-DynamoDB-Datenbank [14] zu ersetzen. Die Vorteile liegen klar auf der Hand. Im Unterschied zur Amazon-RDS-MySQL-Datenbank müssen wir uns nicht um die Überwachung des vorhandenen Speicherplatzes kümmern, um gegebenenfalls eine Skalierung des Festplattenspeichers vorzunehmen. Ähnlich verhält es sich mit der Überwachung anderer Metriken wie der Anzahl der etablierten Clientverbindungen oder der CPU- und Speicherauslastung. Auch diese Metriken würden wir heranziehen, um unsere Datenbank vertikal (Lese- und Schreibinstanz) oder horizontal (Leseinstanzen) zu skalieren. Auch wenn sich diese Dinge automatisieren lassen, warum sollten wir uns darum überhaupt kümmern wollen? Mit Amazon DynamoDB werden uns diese Verantwortlichkeiten abgenommen. Amazon DynamoDB kümmert sich automatisch um die Provisionierung des notwendigen Festplattenspeicherplatzes, und es wird nur der tatsächlich genutzte Speicherplatz in Rechnung gestellt. Auch das Skalieren der Amazon DynamoDB in Bezug auf Lese- und Schreibkapazitäten wird uns vollständig abgenommen, entscheiden wir uns für die On-Demand-Option.

Durch den Zugriff auf Datensätze über ihren Schlüssel in unter 10 Millisekunden profitiert unsere Anwendung auch von einem Performanceschub. Sollte das für unsere Anwendung nicht ausreichend sein, entscheiden wir uns für den Einsatz von Amazon DynamoDB Accelerator (DAX) [15]. Amazon DAX ist ein Write-through-Cache, der die Logik in unserer Anwendung weiter vereinfacht (wir erinnern uns – jede Zeile Code bringt Verantwortlichkeiten mit sich) und die Antwortzeiten von Millisekunden auf Mikrosekunden reduziert. Da Amazon DAX kompatibel mit dem Amazon DynamoDB API ist, können wir den Cache auch zu einem späteren Zeitpunkt zu unserer Architektur hinzufügen, ohne unsere Anwendung anpassen zu müssen.

Darüber hinaus bietet Amazon DynamoDB Global Tables die Möglichkeit, Tabellen in andere AWS-Regionen für Lese- und Schreibzugriffe zu replizieren, sodass wir unsere Anwendung für minimale Latenzen bei unseren Kunden in anderen Regionen optimieren können.

Tracing, Logging, Metriken – wie aufwendig ist das?

Nicht selten werden beim ersten Architekturentwurf Aspekte wie Logging, Tracing und die Erzeugung, Verarbeitung und Darstellung von Metriken außer Acht gelassen. Das sind jedoch wichtige Informationen, die wir benötigen, um den Zustand unserer Anwendung zu erkennen, Probleme schnell zu entdecken, zu isolieren und zu beheben. Das trifft auch auf serverlose Architekturen zu. Auch hier sollten wir in der Lage sein, die Kunden zu identifizieren, die von einem Problem betroffen waren, oder zu erkennen, ob eine neue Funktionalität zu einer stärkeren Nutzung unserer Anwendung geführt hat. Wir können diese Aspekte im Rahmen dieses Artikels nicht vertiefen, aber wäre es nicht wünschenswert, dass wir all diese Funktionen ebenfalls serverlos zur Verfügung stellen könnten? Und genau das können wir mit der Nutzung von Amazon CloudWatch und AWS X-Ray erreichen.

AWS Lambda und Amazon API Gateway schreiben ihre Logeinträge standardmäßig nach Amazon CloudWatch Logs. Für Amazon API Gateway muss das eingeschaltet und die Granularität festgelegt werden. Unsere anwendungsspezifischen Logs verwenden strukturierte Logeinträge im JSON-Format, die es uns im Nachgang erleichtern, die Informationen auszuwerten, zu aggregieren oder in einem Dashboard darzustellen (Abb. 4).

Zusätzlich emittieren die Services – wenn keine anderen Einstellungen vorgenommen werden – etliche Metriken nach Amazon CloudWatch Metrics [16]. Diese helfen uns, zu entscheiden, ob unsere Anwendung in einem normalen Zustand operiert oder nicht. Das schließt wichtige Informationen ein, wie z. B.:

  • Wie viele Anfragen haben unsere Anwendung erreicht und wie viele wurden davor erfolgreich verarbeitet?

  • Wie lange hat es gedauert, die Anfragen zu bearbeiten?

  • Wie viele Lese- oder Schreibvorgänge haben wir in unserer Amazon-DynamoDB-Tabelle durchgeführt?

  • Wurden unsere Anfragen gedrosselt oder sind sie in ein Time-out gelaufen?

Darüber hinaus bietet uns Amazon CloudWatch Metrics die Möglichkeit, eigene anwendungsspezifische Metriken zu emittieren. In einer Finanzanwendung könnte das zum Beispiel der transferierte Geldbetrag sein. Zusätzlich können diese Metriken mit Alarmen versehen werden, die es uns erlauben, eine Aktion automatisch anzustoßen, sollten diese Metriken außerhalb der Norm liegen. Diese Aktionen können die simple Benachrichtigung eines Administrators sein oder aber auch komplexe Workflowprozesse anstoßen – je nachdem, was erforderlich ist.

Abb. 4: Ein beispielhaftes Dashboard in Amazon CloudWatch mit diversen Visualisierungen

Abb. 4: Ein beispielhaftes Dashboard in Amazon CloudWatch mit diversen Visualisierungen

Mit der Nutzung von AWS X-Ray können wir Tracinginformationen unserer Aufrufe instrumentieren. In Amazon API Gateway und AWS Lambda können wir das über eine einfache Checkbox einschalten. Das hilft uns, einen schnellen Überblick zu erhalten, welche (Micro-)Services und Systeme involviert waren, um eine Anfrage zu beantworten, wie viel Zeit wir in jedem dieser Services zugebracht haben und ob die Bearbeitung in den Services erfolgreich war. Durch die Möglichkeit, eigene Segmente zu definieren, können wir auch externe Aufrufe oder interne Module mit in den Aufrufgraphen einbeziehen (Abb. 5).

Abb. 5: Aufrufgraph in AWS X-Ray mit Angabe der einzelnen Verarbeitungszeiten

Abb. 5: Aufrufgraph in AWS X-Ray mit Angabe der einzelnen Verarbeitungszeite

Fazit

Werfen wir in Abbildung 6 zunächst einen Blick auf unsere überarbeitete Architektur, nachdem wir alle oben beschriebenen Optimierungen angewandt haben.

Abb. 6: Unsere neue serverlose Architektur nach der Optimierung

Abb. 6: Unsere neue serverlose Architektur nach der Optimierung

Mit den hier beschriebenen Optimierungen konnten wir den Aufwand für den Betrieb unserer Anwendung in unserem DevOps-Team auf ein Minimum reduzieren, unsere Entwicklungszyklen weiter verkürzen und die Kosten größtenteils auf eine nutzungsbasierte Abrechnung umstellen. Das führt in der Regel zu einer Kostenreduzierung. Wir können nun mit unseren hochqualifizierten Kollegen noch mehr Zeit auf die Entwicklung neuer Funktionalitäten verwenden und Kundenfeedback in die Weiterentwicklung der Anwendung einfließen lassen. Das Experimentieren und Erproben neuer Ideen war noch nie so einfach für uns, so schnell zu realisieren und so günstig umzusetzen. Also, worauf warten wir noch?

 

Geschrieben von
Christian Müller
Christian Müller
In seiner Rolle als Senior Solutions Architekt bei Amazon Web Services (AWS) in Frankfurt hilft Christian Müller seinen Kunden, das volle Potenzial der AWS Cloud zu nutzen, um sie so noch erfolgreicher zu machen. Seine besonderen Interessen liegen dabei in den Bereichen Messaging, Event-getriebene Architekturen und Serverless.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: