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 .
Mandantenfähigkeit für interne Dienste
1.1. Isolation der Mieter
1.2. Multi-Tenant-Überwachung
1.3. Skalierung
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
Mandantenfähigkeit mit AWS AppSync
Abschluss
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.
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 .
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:
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.
Wenn Sie Ihren AWS-Webdienst mit REST , HTTP oder der WebSocket-API in AWS erstellen, verwenden Sie höchstwahrscheinlich API Gateway.
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 ):
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.
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}`, }) }; };
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]).
API Gateway verfügt über zwei Arten von Protokollen:
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.
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
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',},}); });
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.
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.
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).
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.
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:
Auch hier veröffentlicht.