paint-brush
So erstellen Sie mehrinstanzenfähige interne Dienste in AWS und CDK (Teil 1): API Gateway und AppSyncvon@filletofish
2,433 Lesungen
2,433 Lesungen

So erstellen Sie mehrinstanzenfähige interne Dienste in AWS und CDK (Teil 1): API Gateway und AppSync

von Filipp Fediakov11m2023/09/19
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Im ersten Teil dieser Multi-Tenant-Reihe untersuchen wir die Feinheiten des Aufbaus interner Multi-Tenant-Dienste auf AWS. Wir befassen uns intensiv mit AWS API Gateway und AppSync und decken wichtige Aspekte wie Mandantenisolierung, Überwachung und Skalierung ab. Entdecken Sie Best Practices zur Sicherung und Optimierung Ihrer mandantenfähigen Architektur mit praktischen Einblicken und Beispielen.
featured image - So erstellen Sie mehrinstanzenfähige interne Dienste in AWS und CDK (Teil 1): API Gateway und AppSync
Filipp Fediakov HackerNoon profile picture
0-item



In dieser Blogbeitragsreihe möchte ich Best Practices für den Aufbau mandantenfähiger Dienste in AWS diskutieren. Vorhandene Literatur zum Aufbau mandantenfähiger Dienste zielt in der Regel auf SaaS-Anwendungen mit Hunderten von Kunden ab (z. B. Aufbau einer mandantenfähigen SaaS-Lösung mithilfe von AWS Serverless Services ).


Der Hauptgrund für diese Serie besteht darin, sich auf den Aufbau mandantenfähiger Dienste für Anwendungsfälle mit weniger Clients zu konzentrieren, die alle auf AWS-Konten bereitgestellt werden. Normalerweise gilt dies für Szenarios, in denen Sie einen mandantenfähigen Dienst für den internen Gebrauch erstellen.


Ich werde die Reihe von Blogbeiträgen für jede Art der Service-zu-Service-Integration in drei Teile unterteilen: synchrone, asynchrone und Batch-Integration.


Teil 1 befasst sich mit der mandantenfähigen Architektur für zwei AWS-Dienste: API Gateway und AppSync. Im gesamten Artikel verweise ich auf den Code der Beispielanwendungs-App, die für diesen Artikel in Typescript und AWS CDK erstellt wurde: https://github.com/filletofish/aws-cdk-multi-tenant-api-example/tree/main .


Inhaltsübersicht

  1. Mandantenfähigkeit für interne Dienste

    1.1. Isolation der Mieter

    1.2. Multi-Tenant-Überwachung

    1.3. Skalierung

  2. Mandantenfähigkeit für interne Dienste

    2.1. Mieterisolation – Zugangskontrolle

    2.2 Mieterisolation – Problem mit lauten Nachbarn

    2.3 Multi-Tenant-Überwachung

    2.4 Metriken, Alarme, Dashboards

    2.5 Onboarding und Offboarding von API-Clients

  3. Mandantenfähigkeit mit AWS AppSync

  4. Abschluss

1. Mandantenfähigkeit für interne Dienste


Mandantenfähigkeit ist die Fähigkeit einer Software, mehrere Kunden oder Mandanten mit einer einzigen Instanz der Software zu bedienen.


Sobald Sie mehr als einem Team erlauben, Ihre Service- API aufzurufen, wird Ihr Service mehrmandantenfähig. Die mandantenfähige Architektur führt zu zusätzlicher Komplexität Ihrer Dienste, z. B. Mandantenisolation, Überwachung auf Mandantenebene und Skalierung.


Beispiel für einen internen Multi-Tenant-Dienst


1.1. Isolation der Mieter

Im Allgemeinen berücksichtigt die Mandantenisolierung Sicherheitsbedenken, indem sichergestellt wird, dass Mandanten nicht auf die Ressourcen eines anderen Mandanten zugreifen können. Außerdem ist eine Mandantenisolierung implementiert, um sicherzustellen, dass sich Fehler, die von einem Mandanten verursacht werden, nicht auf andere Mandanten Ihres Dienstes auswirken. Es wird oft auch als „Noisy Neighbor“-Problem bezeichnet. Weitere Informationen finden Sie im AWS-Whitepaper zu Mandantenisolationsstrategien https://d1.awsstatic.com/whitepapers/saas-tenant-isolation-strategies.pdf .


1.2. Multi-Tenant-Überwachung

Sobald mehrere Mandanten beginnen, Infrastrukturressourcen gemeinsam zu nutzen, müssen Sie überwachen, wie jeder Ihrer Mandanten Ihr System nutzt. Dies bedeutet normalerweise, dass der Name oder die Kennung des Mandanten in Ihren Protokollen, Metriken und Dashboards vorhanden sein sollte. Die Überwachung mehrerer Mandanten kann aus mehreren Gründen nützlich sein:


  1. Fehlerbehebung bei Problemen: Vereinfacht die Problemerkennung und -lösung und unterscheidet mieterspezifische Probleme von umfassenderen.
  2. Ressourcenzuweisung und Kapazitätsplanung. Mithilfe der Multi-Tenant-Überwachung können Sie den Ressourcenverbrauch pro Mandant für die Ressourcenzuteilung und Kapazitätsplanung verfolgen. Selbst wenn Ihr Dienst serverlos ist, müssen Sie dennoch den Ressourcenverbrauch Ihres Clients verstehen, um zu verstehen, ob Sie bald eines der AWS-Limits erreichen werden (ein typisches Beispiel ist ein Limit für die gleichzeitige Ausführung einer Lambda-Funktion).
  3. SLA-Management: Ermöglicht die Verfolgung der mandantenspezifischen Leistung anhand von SLAs.
  4. Abrechnung. Es ist unwahrscheinlich, dass Sie anderen Teams die Nutzung Ihres internen Dienstes in Rechnung stellen. Bei einem gewissen Grad des Unternehmenswachstums könnte es jedoch eine gute Idee sein, anderen Teams Rechnungen zu stellen, um eine sparsame Nutzung des Dienstes sicherzustellen.


1.3. Skalierung

Multi-Tenant-Dienste sind wahrscheinlich größeren Herausforderungen bei der Skalierung ausgesetzt als Single-Tenant-Dienste. Skalierbarkeit ist jedoch ein großes Thema und ich werde in diesem Blogbeitrag nicht darauf eingehen.


2. Mandantenfähigkeit mit API Gateway

Wenn Sie Ihren AWS-Webdienst mit REST , HTTP oder der WebSocket-API in AWS erstellen, verwenden Sie höchstwahrscheinlich API Gateway.


2.1. Mieterisolation – Zugangskontrolle

AWS empfiehlt, jeden Dienst in seinem/seinen eigenen AWS-Konto(n) bereitzustellen, um die Ressourcen und Daten des Dienstes zu isolieren, die Kostenverwaltung zu vereinfachen und eine Trennung zwischen Test- und Produktionsumgebungen zu ermöglichen (Einzelheiten finden Sie im AWS-Whitepaper „Organizing Your AWS Environment Using Multiple Accounts “).


Wenn Ihre Unternehmensdienste in AWS bereitgestellt werden, ist AWS IAM die naheliegendste Lösung für die Verwaltung des Zugriffs auf Ihr API-Gateway. AWS Cognito ist eine weitere Option zum Verwalten des Zugriffs auf eine mehrinstanzenfähige API (siehe Drosseln einer mehrstufigen, mehrinstanzenfähigen REST-API im großen Maßstab mithilfe von API Gateway , Die Argumente für und gegen Amazon Cognito ).


Der Vergleich zwischen AWS IAM und AWS Cognito verdient eine gesonderte eingehende Betrachtung. Aber für diesen Artikel würde ich bei AWS IAM bleiben, da es die einfachste Möglichkeit ist, den Zugriff zu verwalten, wenn sich Ihre Unternehmensdienste in AWS befinden.


Sobald Sie die AWS IAM-Autorisierung für die API-Gateway-Methode aktivieren (siehe CFN ), sollten alle API-Anfragen für diese Methode mit Anmeldeinformationen der IAM-Identität signiert werden, die Ihr API-Gateway aufrufen darf.


Standardmäßig ist kein Zugriff zwischen AWS-Konten zulässig. Beispielsweise schlägt der Aufruf Ihres API-Gateways mit den Anmeldeinformationen eines anderen AWS-Kontos fehl. Um Ihre Kunden mit Ihrer API zu integrieren, müssen Sie einen kontoübergreifenden Zugriff einrichten. Um kontoübergreifenden Zugriff auf Ihr API Gateway zu gewähren, können Sie zwei Methoden verwenden: ressourcenbasierte Autorisierung (nicht verfügbar für API Gateway HTTP API) und identitätsbasierte Autorisierung (weitere Informationen finden Sie unter https://repost.aws/knowledge-center/ access-api-gateway-account ):


  1. Onboarding eines Kunden mit ressourcenbasierter Autorisierung . Für den ressourcenbasierten Zugriff müssen Sie die API Gateway-Ressourcenrichtlinie aktualisieren und das AWS-Konto Ihres Clients hinzufügen. Der Hauptnachteil dieser Methode besteht darin, dass nach der Aktualisierung der Ressourcenrichtlinie die API-Gateway-Stufe erneut bereitgestellt werden muss, damit die Änderungen wirksam werden (siehe AWS-Dokumente [1] und [2] ). Wenn Sie jedoch CDK verwenden, können Sie die Bereitstellung neuer Phasen automatisieren (siehe AWS CDK-Dokumente für Api Gateway ). Ein weiterer Nachteil ist die Begrenzung der maximalen Länge der Ressourcenrichtlinie.


  2. Onboarding eines Kunden mit identitätsbasierter Autorisierung . Für die identitätsbasierte Zugriffskontrolle müssen Sie eine IAM-Rolle für den Client erstellen und dem Client erlauben, diese zu übernehmen, indem Sie die Ressourcenrichtlinie der Rolle aktualisieren (vertrauenswürdige Beziehungen). Sie könnten IAM-Benutzer verwenden, aber aus Sicherheitsgründen sind IAM-Rollen besser. Rollen ermöglichen die Authentifizierung mit temporären Anmeldeinformationen und erfordern keine Speicherung von IAM-Benutzeranmeldeinformationen. Es gibt ein Limit von 1.000 Rollen pro Konto, dieses Limit ist jedoch anpassbar. Ein weiterer Nachteil der rollenbasierten Methode für den kontenübergreifenden Zugriff auf Ihre API besteht darin, dass Sie für jeden neuen API-Client eine IAM-Rolle erstellen müssen. Allerdings kann die Rollenverwaltung mit CDK automatisiert werden (siehe Codebeispiel der bereitgestellten CDK-App ).



Mit der AWS IAM-Autorisierung können Sie nur den Zugriff auf das API Gateway steuern (mithilfe der IAM-Richtlinie können Sie angeben, welches AWS-Konto welche API Gateway-Endpunkte aufrufen darf). Es liegt in Ihrer Verantwortung, den Kontrollzugriff auf die Daten und andere zugrunde liegende Ressourcen Ihres Dienstes zu implementieren. Innerhalb Ihres Service können Sie den AWS IAM-ARN des Aufrufers verwenden, der mit der API-Gateway-Anfrage zur weiteren Zugriffskontrolle übergeben wird:


 export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => { // IAM Principal ARN of the api caller const callerArn = event.requestContext.identity.userArn!; // .. business logic based on caller return { statusCode: 200, body: JSON.stringify({ message: `Received API Call from ${callerArn}`, }) }; };



2.2. Mieterisolation – Problem mit lauten Nachbarn

Das Standard-API-Gateway-Limit beträgt 10.000 TPS ( API-Gateway-Kontingente und -Limits ). Aufgrund Ihrer Downstream-Abhängigkeiten erfordert Ihr Dienst jedoch möglicherweise einen niedrigeren TPS-Grenzwert. Um eine Überlastung von API-Anfragen von einem einzelnen Mandanten zu vermeiden, die sich auf die Verfügbarkeit des gesamten Systems auswirkt, sollten Sie eine API-Ratenbegrenzung pro Mandant implementieren (auch als „Drosselung“ oder „Zugangskontrolle“ bezeichnet).


Sie können API Gateway-API-Nutzungspläne und -Schlüssel verwenden, um Grenzwerte für jeden Client separat zu konfigurieren (Einzelheiten finden Sie in der AWS-Dokumentation [1], [2] und [3]).


2.3. Multi-Tenant-Überwachung

API Gateway verfügt über zwei Arten von Protokollen:

  1. API-Gateway-Ausführungsprotokolle: Enthält Daten wie Anforderungs- oder Antwortparameterwerte, welche API-Schlüssel erforderlich sind, ob Nutzungspläne aktiviert sind usw. Standardmäßig nicht aktiviert, kann aber konfiguriert werden.


  2. Funktion „API-Gateway-Zugriffsprotokolle“: Mit dieser Funktion können Sie protokollieren, wer auf Ihre API zugegriffen hat, wie darauf zugegriffen wurde, auf welchen Endpunkt zugegriffen wurde und welches Ergebnis der API-Aufruf hat. Sie können Ihr Protokollformat angeben und mit Kontextvariablen auswählen, was protokolliert werden soll (siehe Dokumentation im CDK).


Um die Anfragen Ihrer API-Clients zu überwachen, würde ich empfehlen, die Zugriffsprotokollierung zu aktivieren. Sie können zumindest den AWS IAM-ARN des Anrufers ( $context.identity.userArn ), den Anforderungspfad ( $context.path ), Ihren Service-Antwortstatuscode $context.status und die API-Aufruflatenz ( $context.responseLatency ) protokollieren. .


Persönlich fand ich für einen Service mit AWS IAM Auth und Lambda als Rechenfunktion diese API-Gateway-Zugriffsprotokollierungskonfiguration nützlich:

 const formatObject = { requestId: '$context.requestId', extendedRequestId: '$context.extendedRequestId', apiId: '$context.apiId', resourceId: '$context.resourceId', domainName: '$context.domainName', stage: '$context.stage', path: '$context.path', resourcePath: '$context.resourcePath', httpMethod: '$context.httpMethod', protocol: '$context.protocol', accountId: '$context.identity.accountId', sourceIp: '$context.identity.sourceIp', user: '$context.identity.user', userAgent: '$context.identity.userAgent', userArn: '$context.identity.userArn', caller: '$context.identity.caller', cognitoIdentityId: '$context.identity.cognitoIdentityId', status: '$context.status', integration: { // The status code returned from an integration. For Lambda proxy integrations, this is the status code that your Lambda function code returns. status: '$context.integration.status', // For Lambda proxy integration, the status code returned from AWS Lambda, not from the backend Lambda function code. integrationStatus: '$context.integration.integrationStatus', // The error message returned from an integration // A string that contains an integration error message. error: '$context.integration.error', latency: '$context.integration.latency', }, error: { responseType: '$context.error.responseType', message: '$context.error.message', }, requestTime: '$context.requestTime', responseLength: '$context.responseLength', responseLatency: '$context.responseLatency', }; const accessLogFormatString = JSON.stringify(formatObject); const accessLogFormat = apigw.AccessLogFormat.custom(accessLogFormatString);


Sobald die Protokollierung aktiviert ist, können Sie CloudWatch Insights verwenden, um ganz einfach die neuesten Aufrufe von einem ausgewählten API-Client abzurufen mit:

 fields @timestamp, path, status, responseLatency, userArn | sort @timestamp desc | filter userArn like 'payment-service' | limit 20


2.4. Metriken, Alarme, Dashboards

Von API Gateway standardmäßig unterstützte CloudWatch-Metriken werden für alle Anfragen aggregiert. Sie können jedoch API-Gateway-Zugriffsprotokolle analysieren, um benutzerdefinierte CloudWatch-Metriken mit einer zusätzlichen Dimension Ihres Clientnamens zu veröffentlichen und so die Client-(Mandanten-)Nutzung Ihrer API überwachen zu können. Als absolutes Minimum würde ich empfehlen, die CloudWatch-Metriken Count, 4xx, 5xx, Latency split by Dimension=${Client} pro Client zu veröffentlichen. Sie können auch Dimensionen wie Statuscode und API-Pfad hinzufügen.


2.4.1. Verwenden von Metrikprotokollfiltern zum Veröffentlichen von Metriken pro Client


Mit CloudWatch-Metrikprotokollfiltern (siehe Dokumente) können Sie einen benutzerdefinierten Filter bereitstellen und Metrikwerte aus API-Gateway-Zugriffsprotokollen extrahieren (siehe Beispiel unten). Metrikprotokollfilter ermöglichen auch das Extrahieren von Werten für benutzerdefinierte Metrikdimensionen aus Protokollen. Für die mandantenfähige Überwachung könnte die Dimension „Client“ der IAM-ARN des Aufrufers sein.


Die Hauptvorteile von metrischen Protokollfiltern bestehen darin, dass (1) kein Rechenaufwand zu verwalten ist und (2) sie einfach und kostengünstig sind. Sie können jedoch keine Datenänderungen vornehmen (z. B. besser lesbare Clientnamen anstelle von IAM-ARNs festlegen) und es gibt eine Grenze von 100 Metrikfiltern pro einzelner Protokollgruppe (Dokumente).


Beispiel eines CloudWatch-Metrikprotokollfilters zur Count mit den Dimensionen Client “ und Path .

 new logs.MetricFilter(this, 'MultiTenantApiCountMetricFilter', { logGroup: accessLogsGroup, filterPattern: logs.FilterPattern.exists('$.userArn'), metricNamespace: metricNamespace, metricName: 'Count', metricValue: '1', unit: cloudwatch.Unit.COUNT, dimensions: { client: '$.userArn', method: '$.httpMethod', path: '$.path',},}); });


Alle Metrikfilter für 4xx-, 5xx-Fehler- und Latenzmetriken finden Sie in der bereitgestellten Beispiel-CDK-Anwendung .


2.4.2. Verwendung der Lambda-Funktion zum Veröffentlichen von Metriken pro Client

Die alternative Option besteht darin, eine Lambda-Funktion zu erstellen, um die Protokolle zu analysieren, Metriken zu extrahieren und sie zu veröffentlichen. Auf diese Weise können Sie weitere benutzerdefinierte Aufgaben ausführen, z. B. unbekannte Clients herausfiltern oder Clientnamen aus dem userArn extrahieren.


Mit nur ein paar Zeilen CDK-Code können Sie die Lambda-Funktion für API-Gateway-Zugriffsprotokolle abonnieren:


 const logProcessingFunction = new lambda.NodejsFunction( this, 'log-processor-function', { functionName: 'multi-tenant-api-log-processor-function', } ); new logs.SubscriptionFilter(this, 'MultiTenantApiLogSubscriptionFilter', { logGroup: accessLogsGroup, destination: new logsd.LambdaDestination(logProcessingFunction), filterPattern: logs.FilterPattern.allEvents(), });


Sehen Sie sich das vollständige Beispiel im Code sowie die Implementierung der Log Processor Lambda-Funktion an.


Hinzufügen von Nutzungsschlüsseln und Lambda-Funktion zur Veröffentlichung von Metriken pro Client



Sobald Sie mit der Veröffentlichung von API-Gateway-Metriken begonnen haben, die nach Client aufgeteilt sind, können Sie jetzt CloudWatch-Dashboards und CloudWatch-Alarme für jeden Client separat erstellen.



Beispiel für Metriken pro Client im CloudWatch Dashboard



2.5. Onboarding und Offboarding von API-Clients

Ihre CDK-App könnte eine einfache Lösung zum Speichern einer Konfiguration mit Kundennamen, ihren AWS-Konten, angeforderten TPS-Grenzwerten und anderen Metadaten sein. Um einen neuen API-Client zu integrieren, müssen Sie ihn der im Code verwalteten Konfiguration hinzufügen:


 interface ApiClientConfig { name: string; awsAccounts: string[]; rateLimit: number; burstLimit: number; } const apiClients: ApiClientConfig[] = [ { name: 'payment-service', awsAccounts: ['111122223333','444455556666'], rateLimit: 10, burstLimit: 2, }, { name: 'order-service', awsAccounts: ['777788889999'], rateLimit: 1, burstLimit: 1, }, ];


Mithilfe dieser Konfiguration kann die CDK-App dann eine IAM-Rolle und einen API-Gateway-Nutzungsschlüssel erstellen und den Namen des Clients an die Lambda-Funktion übergeben, die Zugriffsprotokolle analysiert (siehe Beispielanwendungscode).


3. Mandantenfähigkeit mit AWS AppSync

Wenn Ihr Dienst über eine GraphQL- API verfügt, verwenden Sie wahrscheinlich AppSync. Ähnlich wie bei API Gateway können Sie IAM Auth verwenden, um AppSync-Anfragen zu autorisieren. AppSync verfügt über keine Ressourcenrichtlinie (siehe GH-Problem ), sodass Sie zum Einrichten der Zugriffskontrolle auf die AppSync-API nur eine rollenbasierte Autorisierung verwenden können. Ähnlich wie bei API Gateway würden Sie für jeden neuen Mandanten Ihres Dienstes eine separate IAM-Rolle erstellen.


Leider bietet AppSync nur begrenzte Unterstützung für die Drosselung pro Client, die wir für die Mandantenisolierung und -überwachung benötigen. Während Sie TPS-Limits für AppSync mit WAF einrichten können, können Sie keine separaten Limits pro Client erstellen, um Ihre Service-Mandanten zu isolieren. Ebenso stellt AppSync keine Zugriffsprotokolle bereit, wie dies bei API Gateway der Fall ist.


Lösung? Sie können API Gateway als Proxy zu Ihrem AppSync hinzufügen und alle oben beschriebenen API Gateway-Funktionen verwenden, um Mandantenfähigkeitsanforderungen wie Mandantenisolation und -überwachung zu implementieren. Darüber hinaus können Sie andere API-Gateway-Funktionen wie Lambda-Autorisierer, benutzerdefinierte Domänen und API-Lebenszyklusverwaltung verwenden, die in AppSync noch nicht vorhanden sind. Der Nachteil ist eine leichte zusätzliche Latenz für Ihre Anfragen.




Weiterleiten von Anfragen an AppSync mit API Gateway


4. Fazit

Das ist es. Wenn Sie Fragen oder Ideen haben, lassen Sie es mich in den Kommentaren wissen oder kontaktieren Sie mich direkt. Im nächsten Teil dieser Serie werde ich Best Practices für die asynchrone interne Integration mit AWS Event Bridge und AWS SQS/SNS besprechen.


Wenn Sie sich eingehend mit dem Thema Aufbau von mandantenfähigen Diensten auf AWS befassen möchten, finde ich diese Ressourcen hilfreich:

  1. AWS-Whitepaper zu SaaS-Mandantenisolationsstrategien
  2. Fairness in mandantenfähigen Systemen
  3. AWS re:Invent 2021 – SaaS-Architekturmuster: Vom Konzept bis zur Implementierung