AWS Germany – Amazon Web Services in Deutschland
Wie Hapag-Lloyd Observability für serverlose Multi-Account-Workloads ermöglicht
von Grzegorz Kaczor (Hapag-Lloyd AG), Michael Graumann und Daniel Moser – übersetzt durch Markus Ziller.
Einführung
Das Gewährleisten von Observability über den Status, die Leistung, Gesundheit und die Sicherheitslage von Anwendungen ist der Schlüssel für den erfolgreichen Betrieb von Multi-Account-Workloads in der Cloud. Mit zunehmender Anzahl und Größe der Workloads wird es herausfordernd, alle verfügbaren Informationen zu finden und zu korrelieren, die über mehrere AWS-Accounts und Systeme außerhalb von AWS verteilt sind. Eine wichtige operative Anforderung für DevOps-Teams ist es, eine einheitliche Übersicht zu haben, die ihnen tiefe Einblicke auf einfach konsumierbare Weise bietet. Dies hilft ihnen, ihre mittlere Reaktionszeit (MTTR) zu reduzieren.
Mit einer Flotte von 252 modernen Containerschiffen und einer Gesamttransportkapazität von 1,8 Millionen TEU ist Hapag-Lloyd eine der führenden Linienreedereien der Welt. TEU, oder Zwanzig-Fuß-Äquivalent-Einheit, ist eine Maßeinheit zur Bestimmung der Ladekapazität für Containerschiffe und Häfen. Das Unternehmen hat rund 14.500 Mitarbeiter und über 400 Büros in 137 Ländern. Hapag-Lloyd verfügt über eine Containerkapazität von 3,0 Millionen TEU – einschließlich einer der größten und modernsten Flotten von Kühlcontainern. Insgesamt 123 Liniendienste weltweit sorgen für schnelle und zuverlässige Verbindungen zwischen mehr als 600 Häfen auf allen Kontinenten. Hapag-Lloyd ist einer der führenden Anbieter im Transatlantik-, Nahost-, Lateinamerika- und Intra-Amerika-Verkehr.
Das Web- und Mobile-Team des Unternehmens ist ein verteiltes Team mit Standorten in Hamburg und Gdańsk und verantwortlich für die Web- und Mobilprodukte des Kundenkanals im Unternehmen.
In diesem Blog teilen wir, wie das Web- und Mobile-Team von Hapag-Lloyd die operativen Anforderungen für ihre serverlosen Cloud-Umgebungen erfüllt hat und die Bedeutung der Implementierung einer zentralen Logging- und Monitoring-Lösung hervorhebt. Dies wird umso wichtiger, je mehr Accounts und Anwendungen involviert sind.
In diesem Beitrag zeigen wir Ihnen, wie wir Ereignisse sammeln, anreichern, transformieren und bei AWS speichern sowie aussagekräftige Alarme und Dashboards erstellen. Wir beschreiben auch, wie die Automatisierung implementiert wurde, um Teams eine einfache Einbindung zu ermöglichen. Wir konzentrieren uns auf AWS Lambda und Amazon API Gateway als Datenquellen, derselbe Mechanismus kann jedoch auch für andere Datenquellen angewendet werden.
Hintergrund/Anforderungen
Da AWS-Accounts eine natürliche Isolierung von Ressourcen bieten, ist die Verwendung mehrerer Accounts eine Best Practice. Das Whitepaper „Organizing Your AWS Environment Using Multiple Accounts“ beschreibt, wie mehrere Accounts genutzt werden können, um Workloads nach Geschäftszweck und Eigentümerschaft zu gruppieren, unterschiedliche Umgebungen wie Entwicklung, Test und Produktion zu trennen, unterschiedliche Sicherheitskontrollen pro Umgebung anzuwenden, den Umfang der Auswirkungen von nachteiligen Ereignissen zu begrenzen oder Kosten zu verwalten. Eine zentrale Monitoring- und Logging-Lösung sollte daher solche Setups unterstützen.
- Zentralisierte Log-Speicherung: Eine Anforderung an unsere zentrale Monitoring- und Logging-Lösung ist die Automatisierung der Konfiguration von Log-Gruppen, sodass alle Logs automatisch an das zentrale Repository gestreamt werden. Die Lösung muss die Logs auch transformieren und/oder anreichern, sodass alle Logs für eine Anwendung leicht gefunden werden können.
- Automatisierte Alarmkonfiguration: Unsere Lösung sollte die Konfiguration von Alarmen automatisieren, sodass bei Erstellung, Aktualisierung oder Löschung eines neuen Endpunkts die entsprechenden Alarme ebenfalls aktualisiert werden. Da unsere Lambda-Funktionen im Geltungsbereich in Amazon API Gateway integriert sind, konzentrieren wir uns bei unseren Alarmen auf Letzteres.
Reduzierte mittlere Reaktionszeit: Da die Monitoring-Lösung viele Anwendungen abdeckt, die sich im Besitz verschiedener Teams befinden, ist es auch entscheidend, dass im Alarmfall die richtigen Personen benachrichtigt werden. Diese müssen dann in der Lage sein, schnell von dem Alarm zur Ursache des Problems zu gelangen. Unsere Lösung muss Benachrichtigungen an das richtige Team senden, mit zusätzlichem Kontext angereichert und eine Möglichkeit bieten, direkt in die relevanten Logs einzutauchen
Lösung
Eine zentrale Logging-Lösung ist für uns eine Art von Logging-Lösung, die es Organisationen ermöglicht, Logs aus mehreren Datenquellen zu sammeln, die Daten zu konsolidieren und sie auf einem zentralen, zugänglichen und benutzerfreundlichen Dashboard zu präsentieren. Obwohl sie Daten aus vielen Datenquellen sammeln kann, konzentrieren wir uns hier auf AWS Lambda und Amazon API Gateway. Zusätzliche Datenquellen können ebenfalls hinzugefügt werden. Eine zentralisierte Monitoring-Strategie ermöglicht es uns, Alarme zentral zu konfigurieren und Anwendungen von ihrer Monitoring-Konfiguration zu entkoppeln.
Denken wir über die Nutzer unserer Observability-Lösung nach. Der Konsument könnte ein Anwendungsteam sein. Dieses Team möchte
- (a) schnell wertvolle Einblicke in den Zustand ihrer Anwendungen gewinnen
- (b) minimalen Aufwand für die Einrichtung der Monitoring-Lösung aufbringen
- (c) schnell über kritische Alarme in dem Tool informiert werden, das sie für die Zusammenarbeit nutzen, in diesem Fall Microsoft Teams
Lassen Sie uns (a) für einen Moment beiseitelegen. Alles, was die Konsumenten für (b) tun müssen, um die Monitoring-Lösung zu nutzen, ist, ihre Ressourcen zu taggen und ein gemeinsam vereinbartes Log-Format zu verwenden. Der Rest ist für das Team automatisiert und transparent. Kritische Alarme werden als Benachrichtigungen über Microsoft Teams (c) versendet. Darüber hinaus enthält die Benachrichtigung Links, die direkt zu den spezifischen Logs führen, wie in dem folgenden Screenshot gezeigt, und sind mit wertvollen Kontextinformationen angereichert.
Die Architektur der Lösung ist in 14 Schritte unterteilt, die in Abbildung 2 gezeigt werden. Wir werden die verschiedenen Teile in den nächsten Abschnitten durchgehen.
Erfassen von Ereignissen
Das Erste, was eine Observability-Lösung benötigt, sind Daten. Wir möchten die zentrale Datenerfassung für unsere Entwickler so reibungslos wie möglich gestalten. Wir haben die Konfiguration der Log-Weiterleitung und eine Basisdefinition von Alarmen automatisiert. In diesem Beitrag konzentrieren wir uns auf Lambda-Funktionen und Amazon API Gateways, aber der gleiche Mechanismus kann für andere Datenquellen verwendet werden. Im vorherigen Architekturdiagramm ermöglichen die Schritte 1 bis 5 die automatisierte Einbindung von Umgebungen und die Datenerfassung.
Um die Log-Weiterleitung auf den Log-Gruppen automatisch zu konfigurieren und Alarme für Metriken zu erstellen, möchten wir benachrichtigt werden, wann immer ein Team Ressourcen erstellt oder aktualisiert. Wir nutzen AWS CloudTrail und Amazon EventBridge, um auf Änderungen an Amazon API Gateway und AWS Lambda zu reagieren. Bei Amazon API Gateway lauschen wir auf den UpdateStage AWS API-Aufruf, der ausgeführt wird, wenn eine API Gateway-Stage geändert wird. Bei AWS Lambda lauschen wir auf den CreateLogGroup API-Aufruf, den der Lambda-Service ausführt, wenn eine Lambda-Funktion zum ersten Mal aufgerufen wird. Das Lauschen auf den CreateFunction API-Aufruf würde nicht zum gewünschten Ergebnis führen, da die Log-Gruppe der Lambda-Funktion erst bei der ersten Ausführung erstellt und noch nicht verfügbar wäre, wenn unsere Automatisierung starten würde.
Wann immer einer dieser API-Aufrufe erfolgt, wird eine EventBridge-Regel aufgerufen, die eine Lambda-Funktion auslöst, die wir hier Automatisierungs-Lambda nennen.
Die Automatisierungs-Lambda hat zwei Aufgaben. Erstens ermöglicht sie, dass die Log-Ereignisse der Anwendungs-Lambda-Funktion oder des Amazon API Gateways an unseren zentralen S3-Bucket geliefert werden. Dies wird erreicht, indem ein Abonnement-Filter zur jeweiligen CloudWatch-Log-Gruppe der Anwendungs-Lambda oder des Amazon API Gateways hinzugefügt wird. In diesem Filter definieren wir ein Muster, um die Log-Ereignisse zu finden, die uns interessieren. Schließlich benötigt der Abonnement-Filter ein Ziel. Da wir Kontogrenzen überschreiten, werden die Logs zunächst an ein Amazon CloudWatch Logs-Ziel gesendet, das sie an einen Amazon Kinesis Data Firehose Delivery Stream im zentralisierten Logging-Konto und anschließend an den S3-Bucket liefert.
Zweitens erstellt die Automatisierungs-Lambda CloudWatch-Alarme für kritische Fehler, die letztendlich als Benachrichtigung im Microsoft Teams-Kanal des Anwendungsteams enden. Ein Alarm könnte beispielsweise sein, dass Amazon API Gateway einen bestimmten Prozentsatz an HTTP 4xx-Fehlern über einen bestimmten Zeitraum zurückgibt.
Da wir einen einfachen Weg finden möchten, die Ursache von Lambda-Fehlern zu ermitteln, müssen wir sicherstellen, dass jede fehlerhafte Ausführung Meldungen in einem gemeinsam vereinbarten Log-Format erzeugt. Dies ist etwas, das von den Anwendungsteams konfiguriert werden muss, die unsere Observability-Lösung nutzen. Wir stellen ihnen eine Bibliothek zur Verfügung, mit der Anwendungsteams gemäß unserer Konvention loggen können.
Transformation der Logs
Mit der Automatisierung haben wir nun einen Mechanismus, um Logs von Lambda-Funktionen und Amazon API Gateways zentral zu konfigurieren und zu sammeln. Obwohl wir ein Konventionen für das Log-Format für Fehlermeldungen haben, kommen reguläre Logs aus verschiedenen Quellen in unterschiedlichen Formen und teilweise semi-strukturiert. Durch das Anwenden von Transformationen auf diese Logs stellen wir sicher, dass sie später einfach von Diensten wie Amazon Athena oder Amazon OpenSearch Service konsumiert werden können. Dies wird erreicht, indem eine Transformations-Lambda-Funktion (Schritt 6 im vorherigen Diagramm) auf dem Amazon Kinesis Data Firehose Delivery Stream aufgerufen wird. Der folgende Screenshot (Abbildung 3) und das Codebeispiel zeigen eine Beispiel-Logmeldung und wie sie in JSON transformiert wurde.
{
"timestamp": 1656575735341,
"accountId": "222222222222",
"product": "SampleApp",
"source": "/aws/lambda/booking-confirmation",
"eventName": "Booking confirmed",
"eventData": {
"bookingId": 123,
"userId": "abc",
"sourceIP": "1.2.3.4",
},
"message": "Booking confirmed. bookingId=[123] userId=[abc] sourceIP=[1.2.3.4]"
}
Amazon S3 dient als unsere einzige Quelle der Wahrheit und als kosteneffiziente Langzeitspeicherung für all unsere Logs. S3-Lifecycle-Regeln erlauben es uns, selten genutzte Daten in niedrigere Speicherebenen zu verschieben und so die Kosten zu kontrollieren.
Um effiziente Analysen auf größeren Datensätzen zu ermöglichen und angesichts der Menge an generierten Logs, war es uns wichtig darüber nachzudenken, sie so zu speichern, dass sie performant konsumiert werden können. Normalerweise hat der Nutzer bei der Suche nach Anwendungs- oder Geschäftslogs eine bestimmte Anwendung und Umgebung im Sinn, für die er Logs untersuchen möchte. Wir nutzen dieses Wissen, indem wir die Logs entsprechend nach Anwendung und Umgebung partitionieren. Dies führt zu weniger gescannten Daten, besserer Abfrageperformance und reduzierten Kosten bei der Datenabfrage mit Tools wie Amazon Athena.
Die Partitionierung unserer Daten nach Anwendung und Umgebung wird durch die dynamische Partitionierungsfunktionalität von Amazon Kinesis Data Firehose erreicht, die Daten basierend auf Attributen innerhalb der Daten partitioniert, die wir definieren können.
Einspeisung in Amazon OpenSearch Service
Amazon OpenSearch Service bietet leistungsfähige Indexierungs- und Volltextsuche-Fähigkeiten, die uns helfen, Alarme anzureichern und relevante Logs schnell zu finden. Zusätzlich können wir nützliche und leicht zu konsumierende Dashboards für unsere Nutzer bereitstellen.
Um Daten in unseren Amazon OpenSearch Service-Cluster aufzunehmen, bringen wir zwei weitere Lambda-Funktionen ins Spiel (Schritt 8 im vorherigen Diagramm). Sie werden durch Amazon S3 PutObject-Ereignisse auf unserem S3-Bucket ausgelöst, lesen die Objekte und indizieren sie in unserem Amazon OpenSearch Service-Cluster. Anstatt unsere Logs zuerst von Amazon Kinesis Data Firehose an Amazon S3 zu liefern und sie dann in Amazon OpenSearch Service aufzunehmen, könnten wir Kinesis Data Firehose auch nutzen, um die Daten direkt an unseren Amazon OpenSearch Service-Cluster zu liefern. Wie jedoch im vorherigen Abschnitt erwähnt, dient Amazon S3 als die einzige Quelle der Wahrheit und für die Langzeitspeicherung. Zweitens ist die dynamische Partitionierung nur mit Amazon S3 als Ziel für Amazon Kinesis Data Firehose verfügbar. Die Partitionierung unserer Daten gibt uns mehr Kontrolle darüber, wie Logs in Amazon OpenSearch Service aufgenommen und indexiert werden.
Wir erstellen eine Funktion für die Lambda-Funktions-Logs und eine für die API Gateway-Logs. Obwohl Amazon OpenSearch Service jedes Feld in einem Dokument erkennen kann, ist es manchmal besser, zumindest für einige Felder explizit Typen zu definieren. Für die Anwendungslogs der Lambda-Funktionen indexieren wir nach Produkt, Ereignisnamen, Quelle, Account-ID und zusätzlichen Metadaten. Wir verwenden ein zentrales Indexmuster, das auf alle Lambda-Logs über alle Produkte hinweg verweist. Ebenso für API Gateway-Logs erstellen wir ein zentrales Indexmuster und nutzen dynamische Vorlagen. Diese erlauben die Definition von benutzerdefinierten Mappings, die auf dynamisch hinzugefügte Felder angewendet werden können. Das folgende Codebeispiel zeigt eine dynamische Vorlage, die wir verwenden.
{
"index_patterns": [
"access-logs-*"
],
"mappings": {
"dynamic_templates": [
{
"strings_as_keyword": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
],
"properties": {
"request_time": {
"type": "date"
}
}
}
}
Aussagekräftige Dashboards erstellen
Nutzer können das Amazon OpenSearch Service Dashboard verwenden, um Daten zu suchen, zu analysieren und zu visualisieren. Als Anwendungsbesitzer wollen wir als erstes den allgemeinen Gesundheitszustand der Workload sehen. Abbildung 4 zeigt ein solches Dashboard.
Ein Aspekt der Gesundheit ist die Success Ratio von API-Aufrufen, die wir wie folgt berechnen:
– (Anzahl der Antworten mit einem HTTP-Status zwischen 200-399) / (Gesamtzahl der erhaltenen Anfragen)
Eine Übersicht der Success Ratio pro Komponente (Microservice) ist im Average Success-Bereich oben im Dashboard zu sehen. Durch die Anzeige von kontextrelevanten Metriken nah beieinander können Dashboard-Nutzer wichtige Datenpunkte leicht korrelieren. Das hilft ihnen, die Komponente einzugrenzen, die eine Anomalie verursacht hat, die jeweiligen Fehlertypen abzurufen und verringert so die MTTR. Zum Beispiel hat im Dashboard die Fehlerrate für den grünen Resource-Pfad einen Spike (I). Um herauszufinden, welche Art von Fehler die Komponente hat, konzentrieren wir uns auf die Error Message Over Time-Visualisierung (II). Der Hauptfehlertyp hier ist ein HTTP 401 Unauthorized. Welche Auswirkungen haben diese Fehler für den Nutzer dieser Anwendung? Neben dem Nichterhalt einer erfolgreichen Antwort sehen sie zusätzlich eine erhöhte Latenz, die in der Average Latency Over Time-Visualisierung sichtbar ist (III). Mit diesen Informationen können Entwicklungsteams Probleme schnell eingrenzen und mit der Fehlersuche beginnen.
Um ein tieferes Verständnis dafür zu bekommen, warum eine Komponente fehlschlägt, möchten Anwendungsbesitzer die jeweiligen Logs auswerten. Wie bereits besprochen, wurden die Anwendungslogs transformiert und in Amazon OpenSearch Service geladen. Zum Beispiel zeigt der Screenshot in Abbildung 5, dass eine Lambda-Funktion bei der Ausführung fehlgeschlagen ist. Dank unseres vereinbarten Log-Formats können wir schnell das Problem erkennen. In diesem Fall ist der Funktion nicht erlaubt, ein Element in Amazon DynamoDB einzufügen.
Mit diesen Informationen kann das Anwendungsteam die entsprechende AWS Identity and Access Management (IAM)-Richtlinie korrigieren. Wenn die Korrektur erfolgreich ist, werden die Error Messages Over Time für diese Komponente abnehmen.
Operative Metriken zu haben ist ein guter Anfang, aber wir benötigen auch Business-Metriken. Hapag-Lloyd-Kunden können die Buchungsanwendung nutzen, um Container für eine bestimmte Route zwischen Häfen für ausgewählte Zeiträume zu buchen. Daher interessiert sich das Geschäft für die Anzahl der Nutzer, geöffnete Angebote und letztendlich erfolgreiche Übermittlungen pro Tag. Weitere Einblicke werden über ein Business-Dashboard (Beispieldaten, Abbildung 6) gegeben, wie etwa häufige Hafenpaare, TEU (Twenty-foot Equivalent Unit) pro Übermittlung und beliebte Stunden für Kunden, um Buchungen anzufragen. Das Dashboard wird auch genutzt, um zu bewerten, wie sich neue Features auf den gesamten Workflow auswirken, indem das Verhältnis zwischen gestarteten und abgeschlossenen Buchungen überwacht wird.
Unterschiedliche Nutzer können unterschiedliche Dashboards mit kontextrelevanten Metriken haben. Die Kombination von Business- und Operational-Metriken ermöglicht es Hapag-Lloyd, die Gesundheit der gesamten Workload zu verstehen und das gewünschte Geschäftsergebnis anzustreben.
Angereicherte Alarme erstellen
Obwohl Nutzer regelmäßig einen Blick auf die Dashboards werfen sollten, besteht die Notwendigkeit, sie im Falle kritischer Probleme so schnell wie möglich zu informieren. Wir können uns nicht darauf verlassen, dass die Teams die Dashboards rechtzeitig prüfen. Diese Herausforderung wird angegangen, indem Alarme erstellt werden, die in solchen Fällen an die Anwendungsbesitzer gesendet werden.
Bei der Behandlung von Alarmen gibt es eine feine Linie. Ein bekanntes Phänomen namens Alert Fatigue beschreibt, dass Teams gegenüber kritischen Alarmen abgestumpft werden, wenn sie zu oft damit konfrontiert werden. Daher benötigen wir einen Mechanismus, um nur in kritischen Situationen Benachrichtigungen zu senden, die relevante Kontextinformationen enthalten und leicht zu konsumieren sind.
Amazon CloudWatch bietet zusammengesetzte Alarme, um Alarm-Lärm zu reduzieren. Durch die Nutzung dieser Funktion aggregieren wir mehrere kleinere Alarme, melden den globalen Zustand der Anwendung und vermeiden es, die Teams mehrfach für dieselbe Hauptursache zu benachrichtigen. Ein zusammengesetzter Alarm wird nur einmal ausgelöst – nämlich dann, wenn der erste untergeordnete Endpunkt-Alarm in den Alarmzustand übergeht. Alarme werden an Amazon Simple Notification Service (Amazon SNS, Schritt 9 im Lösungsdiagramm) gesendet und von einer Lambda-Funktion (Schritt 10) verarbeitet, die eine angereicherte Nachricht erstellt, die an das Anwendungsteam gesendet wird. Wir nutzen die Alarmbeschreibung, um Metadaten wie den Produktnamen an die Lambda-Funktion zu übergeben, wie im folgenden Codeausschnitt skizziert.
def handle(event, _):
msg = json.loads(event['Records'][0]['Sns']['Message'])
description = json.loads(msg['AlarmDescription'])
account_id = msg['AWSAccountId']
period_to = datetime.strptime(msg["StateChangeTime"], '%Y-%m-%dT%H:%M:%S.%f%z')
period_from = (period_to - timedelta(seconds=description['evaluationPeriod'])).strftime('%Y-%m-%dT%H:%M:%S.%fZ')
period_to = period_to.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
threshold = int(description['threshold'] * 100)
header = [
{
"type": "TextBlock",
"size": "Large",
"weight": "Bolder",
"color": "attention",
"text": f"Affected application: {description['product']}"
},
{
"type": "TextBlock",
"text": f"status {description['statusCode']} reached {threshold}% for period {period_from[11:16]}-{period_to[11:16]}"
}
]
Die Lambda-Funktion fragt dann unseren Amazon OpenSearch Service-Cluster nach den Logs ab, die mit dem Alarm in Zusammenhang stehen (Schritt 11).
Schließlich sendet die Lambda-Funktion die Benachrichtigung über Amazon SNS. Der Microsoft Teams WebHook empfängt sie und das Anwendungsteam sieht die Benachrichtigung in seinem Kanal (Schritte 12 und 13). Die Benachrichtigung enthält prägnante Informationen über den Status der Anwendung und ermöglicht es Entwicklern, schnell zu den detaillierten Logs dieser Anwendung in Amazon OpenSearch Service zu navigieren.
Fazit und Ausblick
In diesem Blogbeitrag haben wir die Bedeutung hervorgehoben, dass eine Observability-Lösung relevante, kontextualisierte und leicht zu konsumierende Informationen über den Zustand von Workloads bereitstellen sollte. Wir haben gezeigt, wie wir Ereignisse bei AWS sammeln, transformieren und speichern. Wir haben uns mit der Erstellung von Alarmen und ihrer Anreicherung mit relevanten Informationen für den Empfänger befasst.
Anwendungsteams können von dort aus ihre eigene Automatisierung für Alarme aufbauen, die sie automatisch beheben können.