Nesta série de postagens do blog, gostaria de discutir as práticas recomendadas para a construção de serviços multilocatários na AWS . A literatura existente sobre como construir serviços multilocatários geralmente é voltada para aplicativos SaaS com centenas de clientes (por exemplo , Construindo uma solução SaaS multilocatário usando serviços sem servidor da AWS ).
A principal justificativa para esta série é focar na criação de serviços multilocatários para casos de uso com menos clientes, todos implantados em contas da AWS. Normalmente, isso se aplicaria a cenários em que você cria um serviço multilocatário para uso interno.
Dividirei a série de postagens do blog em três partes para cada tipo de integração serviço a serviço: integração síncrona, assíncrona e em lote.
A Parte 1 discutirá a arquitetura multilocatário para dois serviços AWS: API Gateway e AppSync. Ao longo do artigo, refiro-me ao código do aplicativo de exemplo criado para este artigo em Typescript e AWS CDK: https://github.com/filletofish/aws-cdk-multi-tenant-api-example/tree/main .
Multilocação para serviços internos
1.1. Isolamento do inquilino
1.2. Monitoramento multilocatário
1.3. Dimensionamento
Multilocação para serviços internos
2.1. Isolamento de inquilino - controle de acesso
2.2 Isolamento do inquilino – problema do vizinho barulhento
2.3 Monitoramento multilocatário
2.4 Métricas, Alarmes, Painéis
2.5 Integração e desativação de clientes API
Multilocação com AWS AppSync
Conclusão
Multilocação é a capacidade do software de atender vários clientes ou locatários com uma única instância do software.
Depois que você permitir que mais de uma equipe chame sua API de serviço, seu serviço se tornará multilocatário. A arquitetura multilocatário introduz complexidade adicional aos seus serviços, como isolamento de locatário, monitoramento em nível de locatário e escalabilidade.
Geralmente, o isolamento do inquilino aborda questões de segurança, garantindo que os inquilinos são impedidos de aceder aos recursos de outro inquilino. Além disso, o isolamento do inquilino é implementado para garantir que quaisquer falhas causadas por um inquilino não afetem outros inquilinos do seu serviço. Também é frequentemente referido como um problema de vizinho barulhento. Veja mais no whitepaper da AWS sobre estratégias de isolamento de locatários https://d1.awsstatic.com/whitepapers/saas-tenant-isolation-strategies.pdf .
Assim que vários inquilinos começarem a partilhar recursos de infraestrutura, será necessário monitorizar a forma como cada um dos seus inquilinos utiliza o seu sistema. Geralmente significa que o nome ou identificador do locatário deve estar presente em seus logs, métricas e painéis. O monitoramento multilocatário pode ser útil por vários motivos:
Os serviços multilocatários estão provavelmente mais expostos a desafios de expansão do que os serviços monolocatários. No entanto, escalabilidade é um tópico enorme e não vou abordá-lo nesta postagem do blog.
Se você estiver construindo seu serviço da web AWS com API REST , HTTP ou WebSocket na AWS, provavelmente estará usando o API Gateway.
A AWS recomenda implantar cada serviço em sua(s) própria(s) conta(s) da AWS para isolar os recursos e dados do serviço, facilitar o gerenciamento de custos e a separação entre ambientes de teste e produção (consulte detalhes no whitepaper da AWS Como organizar seu ambiente AWS usando várias contas ).
Se os serviços da sua empresa forem implantados na AWS, a solução mais óbvia para gerenciar o acesso ao seu API Gateway é o AWS IAM. O AWS Cognito é outra opção para gerenciar o acesso à API multilocatário (consulte Limitação de uma API REST em camadas e multilocatário em escala usando o API Gateway , Os argumentos a favor e contra o Amazon Cognito ).
A comparação entre AWS IAM e AWS Cognito merece um aprofundamento separado. Mas para este artigo, eu ficaria com o AWS IAM, pois é a maneira mais simples de gerenciar o acesso quando os serviços da sua empresa estão na AWS.
Depois de habilitar a autorização do AWS IAM para o método API Gateway (consulte CFN ), todas as solicitações de API para esse método deverão ser assinadas com credenciais de identidade do IAM com permissão para chamar seu API Gateway.
Por padrão, nenhum acesso é permitido entre contas da AWS. Por exemplo, invocar seu API Gateway com credenciais de outra conta da AWS falhará. Para integrar seus clientes à sua API, você precisa configurar o acesso entre contas. Para conceder acesso entre contas ao seu API Gateway, você pode usar dois métodos: autorização baseada em recursos (não disponível para API HTTP do API Gateway) e autorização baseada em identidade (veja mais em https://repost.aws/knowledge-center/ access-api-gateway-account ):
Integrando um cliente com autorização baseada em recursos . Para acesso baseado em recursos, você precisa atualizar a política de recursos do API Gateway e adicionar a conta AWS do seu cliente. A principal desvantagem desse método é que, depois de atualizar a política de recursos, o estágio do API Gateway precisa ser reimplantado para que as alterações entrem em vigor (consulte os documentos da AWS [1] e [2] ). No entanto, se você usar o CDK, poderá automatizar a implantação de novos estágios (consulte Documentos do AWS CDK para Api Gateway ). Outra desvantagem é o limite para a duração máxima da política de recursos.
Integrando um cliente com autorização baseada em identidade . Para controle de acesso baseado em identidade, você precisa criar uma função IAM para o cliente e permitir que o cliente a assuma atualizando a política de recursos da função (relacionamentos confiáveis). Você poderia usar usuários do IAM, mas as funções do IAM são melhores do ponto de vista da segurança. As funções permitem a autenticação com credenciais temporárias e não exigem o armazenamento de credenciais de usuário do IAM. Há um limite de 1.000 funções por conta, mas esse limite é ajustável. Além disso, outra desvantagem do método baseado em função para obter acesso entre contas à sua API é que você precisa criar uma função IAM para cada novo cliente de API. No entanto, o gerenciamento de funções pode ser automatizado com o CDK (consulte o exemplo de código do aplicativo CDK fornecido ).
A autorização do AWS IAM permite apenas que você controle o acesso ao API Gateway (usando a política do IAM, você pode especificar qual conta da AWS pode chamar quais endpoints do API Gateway). É sua responsabilidade implementar o controle de acesso aos dados e outros recursos subjacentes do seu serviço. Dentro do seu serviço, você pode usar o AWS IAM ARN do chamador que é transmitido com API Gateway Request para controle de acesso adicional:
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}`, }) }; };
O limite padrão do API Gateway é 10.000 TPS ( API Gateway Quotas and Limits ). No entanto, devido às suas dependências downstream, seu serviço pode exigir um limite de TPS mais baixo. Para evitar uma sobrecarga de solicitações de API de um único locatário que afetará a disponibilidade de todo o sistema, você deve implementar a limitação de taxa de API por locatário (também chamada de “estrangulamento” ou “controle de admissão”).
Você pode usar planos e chaves de uso de API do API Gateway para configurar limites para cada cliente separadamente (para obter detalhes, consulte a documentação da AWS [1], [2] e [3])
O API Gateway tem dois tipos de logs:
Logs de execução do API Gateway: contém dados como valores de parâmetros de solicitação ou resposta, quais chaves de API são necessárias, se os planos de uso estão habilitados e assim por diante. Não habilitado por padrão, mas pode ser configurado.
Recurso API Gateway Access Logs: permite registrar quem acessou sua API, como ela foi acessada, qual endpoint foi acessado e o resultado da chamada de API. Você pode fornecer seu formato de log e escolher o que registrar com variáveis de contexto (consulte a documentação, no CDK).
Para monitorar as solicitações dos seus clientes API, recomendo ativar o registro de acesso. Você pode registrar pelo menos o ARN do AWS IAM do chamador ( $context.identity.userArn
), o caminho da solicitação ( $context.path
), o código de status da resposta do serviço $context.status
e a latência de chamada da API ( $context.responseLatency
) .
Pessoalmente, para um serviço com função AWS IAM Auth e Lambda como computação, achei útil esta configuração do API Gateway Access Logging:
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);
Depois que o registro em log estiver ativado, você poderá usar o CloudWatch Insights para obter facilmente as chamadas mais recentes de um cliente de API escolhido com:
fields @timestamp, path, status, responseLatency, userArn | sort @timestamp desc | filter userArn like 'payment-service' | limit 20
As métricas do CloudWatch suportadas pelo API Gateway por padrão são agregadas para todas as solicitações. Mas você pode analisar logs de acesso do API Gateway para publicar métricas personalizadas do CloudWatch com uma dimensão adicional do nome do seu cliente para poder monitorar o uso da sua API pelo cliente (locatário). No mínimo, eu recomendaria publicar métricas do CloudWatch por cliente Count, 4xx, 5xx, Latency split by Dimension=${Client}
. Você também pode adicionar dimensões como código de status e caminho da API.
2.4.1. Usando filtros de log de métricas para publicar métricas por cliente
Os filtros de log de métricas do CloudWatch (consulte a documentação) permitem fornecer um filtro personalizado e extrair valores de métricas dos logs de acesso do API Gateway (veja o exemplo abaixo). Os filtros de log de métricas também permitem extrair valor para dimensões de métricas personalizadas dos logs. Para monitoramento de multilocação, a dimensão Client pode ser o IAM ARN do chamador.
As principais vantagens dos filtros de log métrico são (1) nenhuma computação para gerenciar (2) é simples e barato. Mas você não pode fazer nenhuma modificação nos dados (por exemplo, definir nomes de clientes mais legíveis em vez de ARNs do IAM) e há um limite de 100 filtros de métrica por único grupo de logs (documentos).
Exemplo de filtro de log de métricas do CloudWatch para publicar Count
com dimensão Client
e 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. Usando a função Lambda para publicar métricas por cliente
A opção alternativa é criar uma função Lambda para analisar os logs, extrair métricas e publicá-los. Isso permite que você faça coisas mais personalizadas, como filtrar clientes desconhecidos ou extrair o nome do cliente do userArn.
Com apenas algumas linhas de código CDK para inscrever a função Lambda nos logs de acesso do 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(), });
Veja o exemplo completo em código , bem como a implementação da função Log Processor Lambda .
Depois de começar a publicar métricas do API Gateway divididas por cliente, agora você pode criar painéis do CloudWatch e alarmes do CloudWatch para cada cliente separadamente.
Seu aplicativo CDK pode ser uma solução fácil para armazenar uma configuração com nomes de clientes, suas contas AWS, limites de TPS solicitados e outros metadados. Para integrar um novo cliente API, você precisaria adicioná-lo à configuração gerenciada no código:
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, }, ];
Usando essa configuração, o aplicativo CDK pode criar uma função IAM, API Gateway Usage Key, e passar o nome do cliente para a função Lambda que analisa os logs de acesso (veja-o no exemplo de código do aplicativo).
Se o seu serviço possui uma API GraphQL , você provavelmente usa o AppSync. Da mesma forma que o API Gateway, você pode usar o IAM Auth para autorizar solicitações do AppSync. O AppSync não possui uma política de recursos (consulte o problema do GH ), portanto, você só pode usar uma autorização baseada em função para configurar o controle de acesso à API AppSync. Da mesma forma que no API Gateway, você criaria uma função IAM separada para cada novo locatário do seu serviço.
Infelizmente, o AppSync tem suporte limitado para limitação por cliente necessária para isolamento e monitoramento de locatários. Embora seja possível configurar limites de TPS para AppSync com WAF, não é possível criar limites separados por cliente para isolar seus locatários de serviço. Da mesma forma, o AppSync não fornece logs de acesso como o API Gateway.
Solução? Você pode adicionar o API Gateway como um proxy ao seu AppSync e usar todos os recursos do API Gateway descritos acima para implementar requisitos de multilocação, como isolamento e monitoramento de locatários. Além disso, você pode usar outros recursos do API Gateway, como autorizadores Lambda, domínio personalizado e gerenciamento do ciclo de vida da API que ainda não existem no AppSync. A desvantagem é uma ligeira latência adicional para suas solicitações.
É isso. Se você tiver alguma dúvida ou ideia, deixe-me saber nos comentários ou entre em contato comigo diretamente. Na próxima parte desta série, revisarei as práticas recomendadas para integração interna assíncrona com AWS Event Bridge e AWS SQS/SNS.
Se você quiser se aprofundar no tópico de construção de serviços multilocatários na AWS, achei estes recursos úteis:
Também publicado aqui.