多租户 SaaS 部署
概览
SaaS 公司运营适用于大量租户的解决方案。AWS 边缘服务(例如,CloudFront、AWS WAF 等...)的多租户部署要求谨慎的设计决策,以满足灵活性、成本、可扩展性和运营开销等业务需求。
架构决策
在使用 CloudFront 设计多租户解决方案时,请考虑以下架构决策:
- 您对所有租户使用相同的主机名(例如,saas.com/tenant1、saas.com/tenant2)还是单独的主机名? 如果您使用相同的主机名,则可以选择使用单个 CloudFront 发行版进行部署。
- 如果您使用不同的主机名,则使用的是相同的域名(例如,tenant1.saas.com、tenant2.saas.com)还是单独的域名(例如,tenant1.com、tenant2.com)? CloudFront 发行版可以与单个 TLS 证书相关联,该证书可以托管多个主机名(SAN 证书)。使用相同的域名时,您可以选择按租户部署 CloudFront 发行版,也可以为所有租户部署带有通配符 CNAME 和 TLS 证书(*.saas.com)的单一发行版。如果域不同,则可以选择按租户部署 CloudFront 发行版,也可以选择部署多个发行版,每个发行版都有 SAN 证书,可以托管多达 100 个不同的域。但是,您附加到同一 TLS 证书的域越多,给 TLS 证书颁发过程带来的阻碍就越大。
- 由谁控制域名? 如果租户控制其域,则需要采取其他步骤将其主机名作为 CNAME 添加到 CloudFront 发行版中。出于安全原因,CloudFront 要求您通过附上涵盖主机名的有效 TLS 证书来证明您对域的所有权。租户要么需要共享自己的 TLS 证书(您要将其上传到 ACM),要么允许您使用 ACM 代表他们颁发 TLS 证书。使用第二种方法,ACM 要求他们在自己的 DNS 中创建 CNAME 令牌以证明他们对该域的所有权。
- 租户共享相同的内容还是不同的内容? 如果他们共享通用内容,可以考虑将共享内容托管在可供不同租户使用的单个域上。这样可以提高共享内容的缓存命中率。
- 如果您为多个租户使用 CloudFront 发行版,您是在同一个源上托管租户还是在不同的源上托管租户? 如果您使用相同的源(例如,单个 S3 存储桶或单个 ALB),则可以通过 URL 路径或主机标头区分租户。如果您选择这种区分租户的方法,请将主机标头添加到缓存密钥中。如果您使用不同的源(例如,每个租户一个 S3 存储桶,或者在 ALB 集群上对租户进行分片),则需要对源请求事件使用 Lambda@Edge 以将流量路由到正确的源。请注意,如果您使用路径来区分租户(例如,saas.com/tenant1、saas.com/tenant2),则可以使用 CloudFront 缓存行为将不同的租户本地路由到其源,但对每个 CloudFront 发行版的行为数量有配额。
- 您的设计是否尊守每个 AWS 服务和每个 AWS 账户的 AWS 配额?
在设计中考虑以下折衷方案。请注意,您可以为多层产品实现差异化折衷。例如,在您控制域名的基本层级中,所有租户共享相同的 CloudFront 发行版。您的高级租户将通过 AWS WAF 各获得一个带有自定义域和安全保护的专用 CloudFront 发行版。
每个租户托管在专用的 CloudFront 发行版上 | 多个租户托管在同一个 CloudFront 发行版上 | |
可观测性 | 每个租户原生可用 | 在发行版层可用,需要额外的工作才能使用日志提取每个租户的指标 |
爆炸半径 | 更改仅影响一个租户 | 单个更改会影响所有租户 |
运营开销 | 需要大规模自动化,通过批量部署来避免 CloudFront API 级别的限制 | 低 |
自定义 | 每个租户可以有自己不同的配置 | 所有租户的配置相同。启用 WAF 时,将对所有请求收费 |
性能 | 每个 CloudFront 发行版都需要单独按流量预热(例如,与源的连接) | 所有租户都将从预热的 CloudFront 发行版受益 |
CloudFront 发行版可以连接到单个 AWS WAF WebACL。同一个 WebACL 可以在多个 CloudFront 发行版中使用。除了以下几点之外,前面提到的折衷也适用于 WAF 部署:
使用每个租户的 WebACL | 为多个租户使用相同的 WebACL | |
价格 | WebACL/规则成本随租户数量呈线性增长 | WebACL/规则成本与租户数量无关 |
误报 | 规则更新可能仅导致单个租户出现误报 | 规则更新可能导致多个单一租户出现误报 |
常见使用案例
每个租户的子域
在这种情况下,您需要为每个租户创建一个子域名(tenant1.saas.com)。Route 53 配置为对 CloudFront 发行版使用通配符别名记录(*.saas.com),还配置了通配符 CNAME(*.saas.com),缓存密钥中包含主机标头。提供动态请求的 ALB 源使用主机标头本地区分租户。请注意,在这种情况下,单个内容失效将适用于所有租户,因为 CloudFront 的失效与缓存密钥的标头部分无关,例如主机标头。提供静态内容的 S3 存储桶需要对查看器请求事件配置一个 CloudFront 函数来读取主机标头,并将 URL 重写到 S3 中的租户目录(例如,tenant1.saas.com/index.html -> s3://bucket:arn/tenant1/index.html)。如果您要向 S3 中的所有租户提供相同的内容,例如相同的单页应用程序,但使用 API 区分租户,请考虑使用此简单解决方案。
如果您在不同的源上托管租户,则需要对源请求事件配置一个 Lambda@Edge 函数,以将租户请求路由到托管租户的源。要了解有关此实施的更多信息,请阅读以下案例研究:
- OutSystems 使用 Lambda@Edge 在 NLB 集群之间路由租户,以了解 OutSystems 如何在其 NLB 集群之间实现这种路由。
- Arc XP 使用 Lambda@Edge 在 S3 存储桶之间路由租户
拥有自己域名的租户
在这种情况下,对每个租户专用 CloudFront 发行版,并自动执行该流程(例如,使用 Amazon Certificate Manager 创建发行版和 TLS 证书颁发)。确保事先提高相关配额(例如,每个账户的发行版数量、TLS 证书的数量)。在某些情况下,您需要在多个 AWS 账户之间对 CloudFront 发行版进行分片。
终止服务器上的 TLS
如果 CloudFront 无法满足您的需求,可以考虑建立反向代理队列(例如,基于 NLB 和 EC2)来终止 TCP/TLS 连接,并事先使用 Global Accelerator 以提高安全性和性能。请注意,在这种情况下,您应根据需要在反向代理中实现缓存。