Dans cette série d'articles de blog, j'aimerais discuter des meilleures pratiques pour créer des services multi-locataires dans AWS . La littérature existante sur la façon de créer des services multi-tenants est généralement destinée aux applications SaaS avec des centaines de clients (par exemple , création d'une solution SaaS multi-tenant à l'aide des services sans serveur AWS ).
La principale raison de cette série est de se concentrer sur la création de services multi-locataires pour des cas d'utilisation avec moins de clients, tous déployés sur des comptes AWS. Habituellement, cela s’applique aux scénarios dans lesquels vous créez un service multi-tenant pour un usage interne.
Je diviserai la série d'articles de blog en trois parties pour chaque type d'intégration de service à service : intégration synchrone, asynchrone et par lots.
La première partie abordera l'architecture multi-tenant pour deux services AWS : API Gateway et AppSync. Tout au long de l'article, je fais référence au code de l'exemple d'application créé pour cet article dans Typescript et AWS CDK : https://github.com/filletofish/aws-cdk-multi-tenant-api-example/tree/main .
Multilocation pour les services internes
1.1. Isolement des locataires
1.2. Surveillance multi-locataires
1.3. Mise à l'échelle
Multilocation pour les services internes
2.1. Isolation des locataires - contrôle d'accès
2.2 Isolement des locataires – problème de voisin bruyant
2.3 Surveillance multi-tenant
2.4 Métriques, alarmes, tableaux de bord
2.5 Intégration et désintégration de clients API
Multilocation avec AWS AppSync
Conclusion
La multi-location est la capacité d'un logiciel à servir plusieurs clients ou locataires avec une seule instance du logiciel.
Une fois que vous autorisez plusieurs équipes à appeler l'API de votre service, votre service devient multi-tenant. L'architecture multi-tenant introduit une complexité supplémentaire à vos services, comme l'isolation des locataires, la surveillance au niveau des locataires et la mise à l'échelle.
Généralement, l'isolement des locataires répond aux problèmes de sécurité en garantissant que les locataires ne peuvent pas accéder aux ressources d'un autre locataire. En outre, l'isolation des locataires est mise en œuvre pour garantir que les pannes causées par un locataire n'affectent pas les autres locataires de votre service. On parle aussi souvent de problème de voisinage bruyant. Pour en savoir plus, consultez le livre blanc AWS sur les stratégies d'isolation des locataires https://d1.awsstatic.com/whitepapers/saas-tenant-isolation-strategies.pdf .
Une fois que plusieurs locataires commencent à partager des ressources d’infrastructure, vous devrez surveiller la manière dont chacun de vos locataires utilise votre système. Cela signifie généralement que le nom ou l'identifiant du locataire doit être présent dans vos journaux, métriques et tableaux de bord. La surveillance multi-tenant peut être utile pour plusieurs raisons :
Les services multi-locataires sont probablement plus exposés aux problèmes de mise à l’échelle que les services à locataire unique. Cependant, l’évolutivité est un sujet énorme et je ne l’aborderai pas dans ce billet de blog.
Si vous créez votre service Web AWS avec l'API REST , HTTP ou WebSocket dans AWS, vous utilisez probablement API Gateway.
AWS recommande de déployer chaque service dans son(ses) propre(s) compte(s) AWS pour isoler les ressources et les données du service, faciliter la gestion des coûts et séparer les environnements de test et de production (voir les détails dans le livre blanc AWS Organisation de votre environnement AWS à l'aide de plusieurs comptes ).
Si les services de votre entreprise sont déployés dans AWS, la solution la plus évidente pour gérer l'accès à votre passerelle API est AWS IAM. AWS Cognito est une autre option pour gérer l'accès à l'API multi-tenant (voir Limitation d'une API REST multi-locataire à plusieurs niveaux à grande échelle à l'aide d'API Gateway , Les arguments pour et contre Amazon Cognito ).
La comparaison entre AWS IAM et AWS Cognito mérite une analyse approfondie distincte. Mais pour cet article, je m'en tiendrai à AWS IAM car c'est le moyen le plus simple de gérer l'accès lorsque les services de votre entreprise sont dans AWS.
Une fois que vous avez activé l'autorisation AWS IAM pour la méthode API Gateway (voir CFN ), toutes les demandes d'API pour cette méthode doivent être signées avec les informations d'identification de l'identité IAM autorisées à appeler votre API Gateway.
Par défaut, aucun accès n'est autorisé entre les comptes AWS. Par exemple, l'appel de votre API Gateway avec les informations d'identification d'un autre compte AWS échouera. Pour intégrer vos clients à votre API, vous devez configurer un accès entre comptes. Pour accorder un accès entre comptes à votre API Gateway, vous pouvez utiliser deux méthodes : l'autorisation basée sur les ressources (non disponible pour l'API HTTP API Gateway) et l'autorisation basée sur l'identité (pour en savoir plus, consultez https://repost.aws/knowledge-center/ access-api-gateway-account ):
Intégration d'un client avec une autorisation basée sur les ressources . Pour un accès basé sur les ressources, vous devez mettre à jour la stratégie de ressources API Gateway et ajouter le compte AWS de votre client. Le principal inconvénient de cette méthode est qu'une fois que vous avez mis à jour la stratégie de ressources, l'étape API Gateway doit être redéployée pour que les modifications prennent effet (voir les documents AWS [1] et [2] ). Toutefois, si vous utilisez CDK, vous pouvez automatiser le déploiement de nouvelles étapes (voir AWS CDK Docs for Api Gateway ). Un autre inconvénient est la limite de la durée maximale de la politique de ressources.
Intégration d'un client avec une autorisation basée sur l'identité . Pour le contrôle d'accès basé sur l'identité, vous devez créer un rôle IAM pour le client et permettre au client de l'assumer en mettant à jour la stratégie de ressources du rôle (relations de confiance). Vous pouvez utiliser des utilisateurs IAM, mais les rôles IAM sont meilleurs du point de vue de la sécurité. Les rôles permettent l'authentification avec des informations d'identification temporaires et ne nécessitent pas de stockage des informations d'identification de l'utilisateur IAM. Il existe une limite de 1 000 rôles par compte, mais cette limite est réglable. De plus, un autre inconvénient de la méthode basée sur les rôles pour obtenir un accès entre comptes à votre API est que vous devez créer un rôle IAM pour chaque nouveau client API. Cependant, la gestion des rôles peut être automatisée avec CDK (voir l'exemple de code de l'application CDK fournie ).
L'autorisation AWS IAM vous permet uniquement de contrôler l'accès à API Gateway (à l'aide de la stratégie IAM, vous pouvez spécifier quel compte AWS peut appeler quels points de terminaison API Gateway). Il est de votre responsabilité de mettre en œuvre un contrôle d'accès aux données et aux autres ressources sous-jacentes de votre service. Au sein de votre service, vous pouvez utiliser l'ARN AWS IAM de l'appelant transmis avec API Gateway Request pour un contrôle d'accès plus poussé :
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}`, }) }; };
La limite par défaut d'API Gateway est de 10 000 TPS ( API Gateway Quotas and Limits ). Toutefois, en raison de vos dépendances en aval, votre service peut nécessiter une limite TPS inférieure. Pour éviter une surcharge de requêtes API provenant d'un seul locataire qui aurait un impact sur la disponibilité de l'ensemble du système, vous devez mettre en œuvre une limitation du débit API par locataire (également appelée « limitation » ou « contrôle d'admission »).
Vous pouvez utiliser les plans et les clés d'utilisation de l'API API Gateway pour configurer les limites pour chaque client séparément (pour plus de détails, consultez la documentation AWS [1], [2] et [3]).
API Gateway propose deux types de journaux :
Journaux d'exécution d'API Gateway : contiennent des données telles que les valeurs des paramètres de demande ou de réponse, les clés API requises, si les plans d'utilisation sont activés, etc. Non activé par défaut, mais peut être configuré.
Fonctionnalité API Gateway Access Logs : vous permet de consigner qui a accédé à votre API, comment elle a été accédée, quel point de terminaison a été accédé et le résultat de l'appel d'API. Vous pouvez fournir votre format de journal et choisir ce que vous souhaitez enregistrer avec des variables contextuelles (voir la documentation, dans CDK).
Pour surveiller les requêtes de vos clients API, je vous recommande d'activer la journalisation des accès. Vous pouvez enregistrer au moins l'ARN AWS IAM de l'appelant ( $context.identity.userArn
), le chemin de la demande ( $context.path
), le code d'état de votre réponse de service $context.status
et la latence des appels d'API ( $context.responseLatency
). .
Personnellement, pour un service avec AWS IAM Auth et la fonction Lambda comme calcul, j'ai trouvé cette configuration API Gateway Access Logging utile :
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);
Une fois la journalisation activée, vous pouvez utiliser CloudWatch Insights pour obtenir facilement les derniers appels d'un client API choisi avec :
fields @timestamp, path, status, responseLatency, userArn | sort @timestamp desc | filter userArn like 'payment-service' | limit 20
Les métriques CloudWatch prises en charge par API Gateway par défaut sont regroupées pour toutes les demandes. Mais vous pouvez analyser les journaux d'accès à API Gateway pour publier des métriques CloudWatch personnalisées avec une dimension supplémentaire du nom de votre client afin de pouvoir surveiller l'utilisation par le client (locataire) de votre API. Au minimum, je recommanderais de publier les métriques CloudWatch par client Count, 4xx, 5xx, Latency divisée par Dimension=${Client}
. Vous pouvez également ajouter des dimensions telles que le code d'état et le chemin de l'API.
2.4.1. Utilisation de filtres de journaux de métriques pour publier des métriques par client
Les filtres de journaux de métriques CloudWatch (voir la documentation) vous permettent de fournir un filtre personnalisé et d'extraire les valeurs de métriques des journaux d'accès à API Gateway (voir l'exemple ci-dessous). Les filtres de journaux de métriques permettent également d'extraire la valeur des dimensions de métriques personnalisées à partir des journaux. Pour la surveillance multi-client, la dimension Client peut être l'ARN IAM de l'appelant.
Les principaux avantages des filtres de journaux métriques sont (1) aucun calcul à gérer (2) c'est simple et bon marché. Mais vous ne pouvez apporter aucune modification aux données (par exemple, définir des noms de clients plus lisibles au lieu des ARN IAM) et il existe une limite de 100 filtres de métriques par groupe de journaux unique (documents).
Exemple de filtre de journal de métriques CloudWatch pour publier Count
avec la dimension Client
et 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. Utilisation de la fonction Lambda pour publier des métriques par client
L'option alternative consiste à créer une fonction Lambda pour analyser les journaux, extraire les métriques et les publier. Cela vous permet d'effectuer des tâches plus personnalisées, comme filtrer les clients inconnus ou extraire le nom du client de l'utilisateurArn.
Avec seulement quelques lignes de code CDK pour abonner la fonction Lambda aux journaux d'accès à l'API Gateway :
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(), });
Voir l'exemple complet dans le code ainsi que l'implémentation de la fonction Lambda du processeur de journaux .
Une fois que vous avez commencé à publier des métriques API Gateway réparties par client, vous pouvez désormais créer des tableaux de bord CloudWatch et des alarmes CloudWatch pour chaque client séparément.
Votre application CDK pourrait être une solution simple pour stocker une configuration avec les noms des clients, leurs comptes AWS, les limites TPS demandées et d'autres métadonnées. Pour intégrer un nouveau client API, vous devrez l'ajouter à la configuration gérée dans le code :
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, }, ];
À l'aide de cette configuration, l'application CDK peut ensuite créer un rôle IAM, une clé d'utilisation de la passerelle API et transmettre le nom du client à la fonction Lambda qui analyse les journaux d'accès (voir l'exemple de code d'application).
Si votre service dispose d'une API GraphQL , vous utilisez probablement AppSync. De la même manière qu'API Gateway, vous pouvez utiliser IAM Auth pour autoriser les requêtes AppSync. AppSync n'a pas de stratégie de ressources (voir problème GH ), vous ne pouvez donc utiliser qu'une autorisation basée sur les rôles pour configurer le contrôle d'accès à l'API AppSync. De la même manière qu'API Gateway, vous créeriez un rôle IAM distinct pour chaque nouveau locataire de votre service.
Malheureusement, AppSync offre une prise en charge limitée de la limitation par client dont nous avons besoin pour l'isolation et la surveillance des locataires. Bien que vous puissiez configurer des limites TPS pour AppSync avec WAF, vous ne pouvez pas créer de limites distinctes par client pour isoler vos locataires de service. De même, AppSync ne fournit pas de journaux d'accès comme le fait API Gateway.
Solution? Vous pouvez ajouter API Gateway en tant que proxy à votre AppSync et utiliser toutes les fonctionnalités d'API Gateway décrites ci-dessus pour implémenter les exigences de multi-location telles que l'isolation et la surveillance des locataires. En plus de cela, vous pouvez utiliser d'autres fonctionnalités d'API Gateway telles que les autorisations Lambda, le domaine personnalisé et la gestion du cycle de vie des API qui n'existent pas encore dans AppSync. L'inconvénient est une légère latence supplémentaire pour vos requêtes.
C'est ça. Si vous avez des questions ou des idées, faites-le-moi savoir dans les commentaires ou contactez-moi directement. Dans la prochaine partie de cette série, j'examinerai les meilleures pratiques pour l'intégration interne asynchrone avec AWS Event Bridge et AWS SQS/SNS.
Si vous souhaitez approfondir le sujet de la création de services multi-locataires sur AWS, j'ai trouvé ces ressources utiles :
Également publié ici.