3. 多租户模型设计:租户标识(Header/SNI/JWT)、租户资源池划分原则
多租户模型是流量隔离的基石。在 Envoy 网格中,租户标识决定了“流量属于谁”,而资源池划分决定了“流量能去哪里”。本章从标识机制与资源划分两个维度,构建可落地的多租户模型。
3.1 租户标识的三种主流机制
Envoy 支持在多个协议层提取租户标识,按优先级与适用场景分为以下三类:
| 标识机制 | 协议层 | 典型载体 | 适用场景 | 安全强度 |
|---|---|---|---|---|
| Header | HTTP/2 应用层 | x-tenant-id 自定义请求头 |
南北向网关、API 路由 | 低(可伪造,需配合验证) |
| SNI | TLS 传输层 | ClientHello 中的 Server Name | 东西向 mTLS、TCP 代理 | 高(加密通道内不可篡改) |
| JWT | HTTP 应用层(认证后) | Authorization: Bearer <> |
需要细粒度 RBAC 的场景 | 最高(签名验证 + 过期) |
实践建议:生产环境推荐“SNI + JWT”双层标识。SNI 用于传输层快速分流,JWT 用于应用层权限校验,Header 仅作为内部网格的 fallback 标识。
3.2 租户标识提取与路由策略
Envoy 通过 filter_chains 和 http_filters 组合实现标识提取。以下是一个基于 Header 和 SNI 的典型配置片段:
static_resources:
listeners:
- name: multi_tenant_listener
address:
socket_address: { address: 0.0.0.0, port_value: 8443 }
filter_chains:
# 租户 A:SNI = tenant-a.example.com
- filter_chain_match:
server_names: ["tenant-a.example.com"]
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: tenant_a_ingress
route_config:
name: tenant_a_routes
virtual_hosts:
- name: tenant_a_vhost
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: "tenant-a-backend" }
# 租户 B:SNI = tenant-b.example.com
- filter_chain_match:
server_names: ["tenant-b.example.com"]
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: tenant_b_ingress
route_config:
name: tenant_b_routes
virtual_hosts:
- name: tenant_b_vhost
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: "tenant-b-backend" }
关键点:SNI 匹配发生在 TLS 握手阶段,Envoy 根据
server_names 选择对应的 filter_chain,实现传输层隔离。Header 和 JWT 则在 HTTP 过滤器链中处理,适合更灵活的应用层路由。
3.3 租户资源池划分原则
资源池划分决定了租户流量的“运行边界”。以下是四个核心原则:
- 物理隔离优先原则:高安全等级租户(如金融、医疗)使用独立集群(Cluster),不与其他租户共享 Envoy 端点。
- 权重比例原则:共享集群内,按租户 SLA 设置权重(
weighted_clusters),避免“吵闹邻居”效应。 - 故障域对齐原则:租户资源池应跨可用区(AZ)部署,每个池内至少包含 2 个实例,防止单点故障。
- 可观测性标签原则:每个资源池必须携带租户标签(
tenant_id),便于监控、日志和追踪的租户级聚合。
以下 SVG 展示了基于 SNI 和 Header 的双层租户标识与资源池映射关系:
3.4 租户资源池划分示例
以下表格展示了一个典型的多租户资源池划分方案,包含隔离级别与故障域配置:
| 租户等级 | 示例租户 | 隔离级别 | 集群数量 | 每集群实例数 | 跨 AZ 部署 |
|---|---|---|---|---|---|
| 铂金 | 金融核心交易 | 物理独立集群 | 2(主备) | 4 | 是(3 AZ) |
| 黄金 | 电商订单 | 逻辑独立集群 | 1 | 6 | 是(2 AZ) |
| 白银 | 用户中心 | 共享集群(权重隔离) | 1(共享) | 10 | 是(2 AZ) |
避坑提醒:共享集群的权重隔离不能替代故障域隔离。如果白银租户的某个实例宕机,权重会自动调整,但不会影响其他租户的可用性。务必为每个租户设置独立的健康检查和熔断阈值。
3.5 租户标识与资源池的绑定策略
Envoy 通过 metadata 和 subset 实现租户与资源池的细粒度绑定。以下是一个基于 JWT 的租户路由示例:
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
tenant_a_provider:
issuer: tenant-a.example.com
audiences: ["service-a"]
from_headers:
- name: Authorization
value_prefix: "Bearer "
remote_jwks:
http_uri:
uri: https://auth.tenant-a.example.com/.well-known/jwks.json
cluster: jwks_cluster
rules:
- match:
prefix: "/api/tenant-a"
requires:
provider_name: tenant_a_provider
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
上述配置中,JWT 验证通过后,Envoy 将请求路由到对应的租户集群。结合 metadata 标签,可以实现更复杂的 subset 路由:
route_config:
virtual_hosts:
- name: tenant_routing
domains: ["*"]
routes:
- match:
prefix: "/api/"
route:
cluster: multi_tenant_cluster
metadata_match:
filter_metadata:
envoy.lb:
tenant_id: "%REQ(x-tenant-id)%"
高级技巧:使用
%REQ(x-tenant-id)% 动态读取请求头,结合 metadata_match 实现租户级别的 subset 负载均衡。这要求后端实例在注册时携带对应的 tenant_id 元数据。