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 租户资源池划分原则

资源池划分决定了租户流量的“运行边界”。以下是四个核心原则:

  1. 物理隔离优先原则:高安全等级租户(如金融、医疗)使用独立集群(Cluster),不与其他租户共享 Envoy 端点。
  2. 权重比例原则:共享集群内,按租户 SLA 设置权重(weighted_clusters),避免“吵闹邻居”效应。
  3. 故障域对齐原则:租户资源池应跨可用区(AZ)部署,每个池内至少包含 2 个实例,防止单点故障。
  4. 可观测性标签原则:每个资源池必须携带租户标签(tenant_id),便于监控、日志和追踪的租户级聚合。

以下 SVG 展示了基于 SNI 和 Header 的双层租户标识与资源池映射关系:

多租户标识与资源池映射架构 客户端 A 客户端 B Envoy 入口网关 SNI 匹配层 tenant-a.example.com tenant-b.example.com Header/JWT 验证 路由分发层 租户 A → Cluster-A 租户 B → Cluster-B 权重 / 故障域 Cluster-A 租户 A 池 Cluster-B 租户 B 池 说明: SNI 在传输层完成租户识别,Header/JWT 在应用层做二次校验。路由层根据租户标识将流量分发到独立的资源池(Cluster),实现故障域隔离。

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 元数据。

3.6 本章小结