在本博文系列中,我想讨论在AWS中构建多租户服务的最佳实践。有关如何构建多租户服务的现有文献通常针对具有数百个客户的 SaaS 应用程序(例如使用 AWS 无服务器服务构建多租户 SaaS 解决方案)。
本系列的主要目的是专注于为客户端较少且全部部署到 AWS 账户的用例构建多租户服务。通常,这适用于构建供内部使用的多租户服务时的场景。
我将针对每种类型的服务到服务集成将这一系列博客文章分为三个部分:同步、异步和批量集成。
第 1 部分将讨论两种 AWS 服务的多租户架构:API Gateway 和 AppSync。在整篇文章中,我参考了 Typescript 和 AWS CDK 中为本文构建的示例应用程序应用程序中的代码:https: //github.com/filletofish/aws-cdk-multi-tenant-api-example/tree/main 。
内部服务的多租户
1.1.租户隔离
1.2.多租户监控
1.3.缩放
内部服务的多租户
2.1.租户隔离-访问控制
2.2 租户隔离-嘈杂的邻居问题
2.3 多租户监控
2.4 指标、警报、仪表板
2.5 加入和退出 API 客户端
使用 AWS AppSync 进行多租户
结论
多租户是指软件通过单个软件实例为多个客户或租户提供服务的能力。
一旦您允许多个团队调用您的服务API ,您的服务就变成了多租户。多租户架构给您的服务带来了额外的复杂性,例如租户隔离、租户级监控和扩展。
通常,租户隔离通过确保阻止租户访问其他租户的资源来解决安全问题。此外,实施租户隔离是为了确保由一个租户引起的任何故障不会影响服务的其他租户。它也经常被称为嘈杂的邻居问题。有关更多信息,请参阅有关租户隔离策略的 AWS 白皮书https://d1.awsstatic.com/whitepapers/saas-tenant-isolation-strategies.pdf 。
一旦多个租户开始共享基础架构资源,您将需要监控每个租户如何使用您的系统。这通常意味着租户名称或标识符应该出现在您的日志、指标和仪表板中。多租户监控可能有用的原因如下:
多租户服务可能比单租户服务更容易面临扩展挑战。然而,可扩展性是一个很大的话题,我不会在这篇博文中讨论它。
如果您在 AWS 中使用REST 、HTTP 或 WebSocket API 构建 AWS Web 服务,您很可能会使用 API Gateway。
AWS 建议将每项服务部署在其自己的 AWS 账户中,以隔离服务的资源和数据、更轻松地进行成本管理以及测试和生产环境之间的分离(请参阅AWS 白皮书使用多个账户组织您的 AWS 环境中的详细信息)。
如果您的公司服务部署在 AWS 中,那么管理 API 网关访问的最明显的解决方案就是 AWS IAM。 AWS Cognito 是管理多租户 API 访问的另一种选择(请参阅使用 API Gateway 大规模限制分层多租户 REST API 、支持和反对 Amazon Cognito 的案例)。
AWS IAM 和 AWS Cognito 之间的比较值得单独深入探讨。但对于本文,我会坚持使用 AWS IAM,因为当您的公司服务位于 AWS 中时,这是管理访问的最简单方法。
一旦您为 API 网关方法启用 AWS IAM 授权(请参阅CFN ),该方法的所有 API 请求都应使用允许调用 API 网关的 IAM 身份凭证进行签名。
默认情况下,AWS 账户之间不允许访问。例如,使用另一个 AWS 账户的凭证调用您的 API 网关将会失败。要将您的客户与您的 API 集成,您需要设置跨账户访问。要授予对 API Gateway 的跨账户访问权限,您可以使用两种方法:基于资源的授权(不适用于 API Gateway HTTP API)和基于身份的授权(请参阅https://repost.aws/knowledge-center/访问 api-网关-帐户):
使用基于资源的授权来引导客户。对于基于资源的访问,您需要更新 API Gateway 资源策略并添加客户端的 AWS 账户。此方法的主要缺点是,一旦更新资源策略,就需要重新部署 API Gateway 阶段才能使更改生效(请参阅 AWS 文档[1]和[2] )。但是,如果您使用 CDK,则可以自动部署新阶段(请参阅Api Gateway 的 AWS CDK 文档)。另一个缺点是资源策略最大长度的限制。
使用基于身份的授权来引导客户。对于基于身份的访问控制,您需要为客户端创建 IAM 角色,并通过更新角色的资源策略(可信关系)来允许客户端代入该角色。您可以使用 IAM 用户,但从安全角度来看,IAM 角色更好。角色允许使用临时凭证进行身份验证,并且不需要存储 IAM 用户凭证。每个帐户的角色限制为 1,000 个,但此限制是可调的。此外,用于跨账户访问 API 的基于角色的方法的另一个缺点是,您需要为每个新 API 客户端创建一个 IAM 角色。但是,角色管理可以使用 CDK 实现自动化(请参阅提供的 CDK 应用程序中的代码示例)。
AWS IAM 授权仅允许您控制对 API 网关的访问(使用 IAM 策略您可以指定哪些 AWS 账户可以调用哪些 API 网关终端节点)。您有责任实现对服务的数据和其他底层资源的控制访问。在您的服务中,您可以使用通过 API Gateway 请求传递的调用者的 AWS IAM ARN 来进行进一步的访问控制:
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}`, }) }; };
默认 API 网关限制为 10,000 TPS( API 网关配额和限制)。但是,由于您的下游依赖性,您的服务可能需要较低的 TPS 限制。为了避免单个租户的 API 请求过载而影响整个系统的可用性,您应该实施每个租户的 API 速率限制(也称为“限制”或“准入控制”)。
您可以使用 API Gateway API 使用计划和密钥单独配置每个客户端的限制(有关详细信息,请参阅 AWS 文档 [1]、[2] 和 [3])
API网关有两种类型的日志:
API网关执行日志:包含请求或响应参数值、需要哪些API密钥、是否启用使用计划等数据。默认情况下不启用,但可以配置。
API网关访问日志功能:允许您记录谁访问了您的API、访问方式、访问的端点以及API调用的结果。您可以提供日志格式并选择使用上下文变量记录的内容(请参阅 CDK 中的文档)。
要监控 API 客户端的请求,我建议启用访问日志记录。您至少可以记录调用者的 AWS IAM ARN ( $context.identity.userArn
)、请求路径 ( $context.path
)、您的服务响应状态代码$context.status
和 API 调用延迟 ( $context.responseLatency
) 。
就个人而言,对于使用 AWS IAM Auth 和 Lambda 函数作为计算的服务,我发现此 API 网关访问日志记录配置很有用:
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);
启用日志记录后,您可以使用 CloudWatch Insights 通过以下方式轻松获取来自所选 API 客户端的最新调用:
fields @timestamp, path, status, responseLatency, userArn | sort @timestamp desc | filter userArn like 'payment-service' | limit 20
默认情况下,API Gateway 支持的 CloudWatch 指标会针对所有请求进行聚合。但您可以解析 API Gateway 访问日志以发布自定义 CloudWatch 指标以及客户端名称的附加维度,以便能够监控客户端(租户)对 API 的使用情况。至少,我建议发布每个客户端的 CloudWatch 指标 Count、4xx、5xx、Latency(按Dimension=${Client}
划分)。您还可以添加状态代码和 API 路径等维度。
2.4.1.使用指标日志过滤器发布每个客户端指标
CloudWatch 指标日志过滤器(请参阅文档)允许您提供自定义过滤器并从 API 网关访问日志中提取指标值(请参阅下面的示例)。指标日志过滤器还允许从日志中提取自定义指标维度的值。对于多租户监控,维度“客户端”可以是调用者的 IAM ARN。
指标日志过滤器的主要优点是(1)无需计算来管理(2)它简单且便宜。但您无法进行任何数据修改(例如,设置更易读的客户端名称而不是 IAM ARN),并且每个日志组的指标过滤器数量限制为 100 个(文档)。
使用维度Client
和Path
发布Count
的 CloudWatch 指标日志过滤器示例
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',},}); });
在提供的示例 CDK 应用程序中查看 4xx、5xx 错误和延迟指标的所有指标过滤器。
2.4.2.使用 Lambda 函数发布每个客户端指标
另一种选择是创建 Lambda 函数来解析日志、提取指标并发布它们。这允许您执行更多自定义操作,例如过滤未知客户端或从 userArn 中提取客户端名称。
只需几行 CDK 代码即可将 Lambda 函数订阅 API 网关访问日志:
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(), });
请参阅代码中的完整示例以及日志处理器 Lambda 函数的实现。
一旦您开始发布按客户端划分的 API Gateway 指标,您现在可以分别为每个客户端创建 CloudWatch 控制面板和 CloudWatch 警报。
您的 CDK 应用程序可能是一个简单的解决方案,用于存储包含客户端名称、其 AWS 帐户、请求的 TPS 限制和其他元数据的配置。要加入新的 API 客户端,您需要将其添加到代码中管理的配置中:
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, }, ];
使用此配置,CDK 应用程序可以创建 IAM 角色、API Gateway 使用密钥,并将客户端的名称传递给解析访问日志的 Lambda 函数(请参阅示例应用程序代码)。
如果您的服务有GraphQL API,您可能会使用 AppSync。与 API Gateway 类似,您可以使用 IAM Auth 来授权 AppSync 请求。 AppSync 没有资源策略(请参阅GH 问题),因此您只能使用基于角色的授权来设置对 AppSync API 的访问控制。与 API Gateway 类似,您可以为服务的每个新租户创建一个单独的 IAM 角色。
不幸的是,AppSync 对每个客户端的限制支持有限,而我们需要进行租户隔离和监控。虽然您可以为带有 WAF 的 AppSync 设置 TPS 限制,但您无法创建单独的每个客户端限制来隔离您的服务租户。同样,AppSync 不像 API Gateway 那样提供访问日志。
解决方案?您可以将 API Gateway 作为代理添加到 AppSync,并使用上述所有 API Gateway 功能来实现租户隔离和监控等多租户要求。除此之外,您还可以使用 AppSync 中尚不存在的其他 API Gateway 功能,例如 Lambda 授权方、自定义域和 API 生命周期管理。缺点是您的请求会产生轻微的额外延迟。
就是这样。如果您有任何问题或想法,请在评论中告诉我或直接与我联系。在本系列的下一部分中,我将回顾与 AWS Event Bridge 和 AWS SQS / SNS 进行异步内部集成的最佳实践。
如果您想深入了解在 AWS 上构建多租户服务的主题,我发现以下资源很有用:
也发布在这里。