跳到主要内容

L6.3 平台级治理与架构模式

三维坐标 layer: L6(应用架构)level: Senior/Architectpillar: 训推框架

当一个推理平台从「服务一个团队」长成「服务整个公司」,技术挑战就从性能转向治理:如何让 50 个互不信任的团队共享同一片 GPU 集群,既不互相踩踏、又能精确计费、还要满足审计合规?本文讲清平台级治理的四块基石——多租户隔离、审计合规、零信任安全、架构师技术领导力

学习目标

  • 前置知识:读过 L6.1(RAG 检索增强生成,理解一次问答要穿过检索→rerank→LLM 的多段链路)、L6.2(Agent 与多步推理,理解一个入口背后可能触发多模型、多工具的编排);以及 L5 治理篇(可观测性、SLO、限流的基础概念)。会写基础 Python、看得懂 YAML、知道 Kubernetes 里 namespace/RBAC 大致是什么即可,无需安全或合规背景。
  • 学完产出:① 能说清平台治理的三个工业化命题——谁能用(鉴权/RBAC)、能用多少(配额/Quota)、用了什么(审计/Audit),并解释为什么单租户时它们都不存在、多租户时它们都变成生产事故;② 能画出多租户隔离的「逻辑 → 资源 → 硬件」三层纵深,说清每层的强制者(软件 vs GPU 固件)与各自防住的攻击;③ 能讲清一次跨租户请求穿过零信任平台时要经过哪几道显式策略关卡,理解「默认拒绝、任何一关失败就在数据面被拒」的设计;④ 能区分硬配额(HTTP 429 拒绝)与软配额(可突破但优先被抢占)两种语义,并说清审计为何「必须把 DENY 当一等公民」;⑤ 亲手跑一个纯 CPU 的 mock 推理网关,构造越权请求(超配额、跨租户、无权限角色、未知租户)验证它们全部被逐关拦截并落审计。
  • 阅读姿势:盯住一条主线——平台治理的一切,都是在「共享基础设施」与「租户隔离」这对天然矛盾之间,用策略(Policy)而非人工划边界。无论是 MIG 把显存切到硅片层,还是网关逐关校验后才放行,本质都在回答同一个问题:怎么让互不信任的租户安全地共用同一片 GPU,且每一次越界都被显式策略挡下、留痕、可追溯。

背景与现状

平台级治理(Platform Governance) 的本质,是在「共享基础设施」与「租户隔离」这对天然矛盾之间,用策略(Policy)而非人工划出边界。它要回答三个工业化问题:谁能用(鉴权/RBAC)、能用多少(配额/Quota)、用了什么(审计/Audit)

当平台只服务一个团队时,这三个问题都不存在——大家互相信任、资源随便抢。但一旦走向多租户,每个问题都会变成生产事故的根源

  • 没有隔离:某团队一个失控的压测脚本,把整个集群的 GPU 显存吃光,全公司的推理服务雪崩。这就是经典的 「吵闹邻居(Noisy Neighbor)」 问题。
  • 没有配额:月底账单出来,财务发现 GPU 成本超支 300%,却无法归因到具体团队——成本无法治理
  • 没有审计:安全合规审查时,无法回答「上周三谁调用了那个涉密模型」——合规直接挂掉

从产业演进看,平台治理的成熟度可以分三个阶段:

  • 裸奔期:用 namespace 做软隔离,配额靠 Excel 人工登记,出事靠人盯——绝大多数自建平台的起点。
  • 策略化期:引入 Kubernetes ResourceQuota + RBACNVIDIA MIG/vGPU 硬件分区,配额与权限变成声明式 YAML,由控制面强制执行。
  • 零信任期:默认不信任任何租户,最小权限 + mTLS 双向认证 + 全链路审计,每一次跨租户访问都被显式策略校验——这是金融、医疗等强合规场景的终态。

业界信号:NVIDIA 的 MIG(Multi-Instance GPU) 能把一块 A100/H100 在硬件层切成最多 7 个互相隔离的实例,每个实例有独立的显存、L2 缓存与计算单元——这说明「隔离」已经从软件下沉到硅片。云厂商(AWS Bedrock、Azure OpenAI)的多租户隔离与审计能力,正是其相对自建平台的核心竞争力。

原理与架构

理解平台治理,关键是看清一次跨租户请求要穿过多少道「策略关卡」,以及这些关卡分别由软件还是硬件强制执行。

2.1 多租户隔离的三个层次

隔离不是单点能力,而是从逻辑 → 资源 → 硬件层层加固的纵深防御:

层次隔离手段强制者防住的攻击
逻辑隔离namespace / tenant_id 标签、网络策略控制面(软件)误访问、命名冲突
资源隔离ResourceQuota(CPU/显存/QPS 配额)调度器(软件)吵闹邻居、配额超支
硬件隔离MIG / vGPU / SR-IOV 物理分区GPU 固件(硬件)显存侧信道、跨实例数据泄露

核心在于:软件隔离防的是「无意的越界」,硬件隔离防的是「有意的攻击」。强合规场景下,仅靠 namespace 是不够的——同一块物理 GPU 上的两个容器,理论上存在显存残留与侧信道风险,必须靠 MIG 把显存与计算单元在硅片层物理切开

2.2 零信任 AI 基础设施的请求穿越路径

下图是一个零信任多租户推理平台的完整架构——注意每一道箭头都对应一次显式的策略校验

按编号读这张图:客户端用 mTLS 双向证书 + JWT 接入网关 → 网关向 RBAC 引擎校验「这个身份能不能调这个模型」→ 向配额管理器校验「这个租户还有没有额度」→ 把这次访问写入审计总线 → 校验全部通过后才注入 tenant_id 转发给调度器 → 调度器按租户绑定,把请求只路由到该租户专属的 MIG 硬件实例。任何一关失败,请求在数据面就被拒,根本到不了 GPU。

2.3 配额管理的两种语义:硬配额 vs 软配额

配额不只是「一个数字」,工业实现里有两种截然不同的语义:

  • 硬配额(Hard Limit):超过即拒绝(HTTP 429)。用于保护集群稳定性——绝不允许某租户的并发数突破上限拖垮共享资源。
  • 软配额(Soft Limit / Burstable):允许临时突破,但优先级降级、并在资源紧张时最先被抢占。用于提升集群利用率——空闲资源借给需要的租户,避免「配额买了却闲置」的浪费。
# Kubernetes ResourceQuota 示例:给租户 A 的 namespace 设硬配额
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.nvidia.com/gpu: "4" # 最多申请 4 张 GPU(硬隔离)
requests.cpu: "32"
requests.memory: 128Gi
count/pods: "20" # 防止 Pod 数量失控

2.4 审计日志:合规治理的「黑匣子」

审计的核心是回答监管的灵魂三问——谁(who)、何时(when)、对什么资源做了什么(what action on what resource),且记录必须不可篡改、可追溯、长期留存。一条合规审计记录的最小字段集:

字段含义合规价值
timestamp精确到毫秒的 UTC 时间时间线还原
tenant_id / principal租户与具体身份责任归属
actioninvoke / read / delete行为分类
resource模型名 / 数据集 ID资产追踪
decisionALLOW / DENY + 原因越权取证
trace_id全链路追踪 ID跨服务关联

这里的关键在于:审计日志最忌「只记成功、不记拒绝」。一次被拒绝的越权尝试,比一千次正常访问更有安全价值——它可能是攻击的早期信号。合规级审计必须把 DENY 事件作为一等公民记录并告警。

动手实践:极简代码实操

实验目标:为一个多租户推理平台写一份配额 + RBAC 隔离方案,并用 Python 实现一个带租户隔离的 mock 推理网关——它在每次请求时做三件事:①RBAC 角色校验 ②per-tenant 配额计数 ③审计记录。然后构造越权请求(超配额、跨租户访问、无权限角色)验证全部被拒。纯 CPU 可跑,无需任何 GPU 或外部依赖。

3.1 隔离方案设计(先想清楚再写代码)

# tenant-policy.yaml —— 多租户隔离策略声明
tenants:
tenant-a:
quota: { max_requests: 3 } # 硬配额:最多 3 次请求
roles: [reader, invoker] # 拥有的角色
allowed_models: [llama-7b] # 可访问的模型(资源隔离)
tenant-b:
quota: { max_requests: 5 }
roles: [reader] # 只读,无 invoker 权限
allowed_models: [llama-13b]

# RBAC:角色 -> 允许的动作(最小权限原则)
rbac:
reader: [list_models]
invoker: [list_models, invoke] # 只有 invoker 能真正推理

3.2 代码:带租户隔离 + RBAC 的 mock 推理网关

"""mock_gateway.py —— 多租户推理网关:RBAC + 配额 + 审计(纯 CPU)"""
from dataclasses import dataclass, field
from datetime import datetime, timezone

# ---------- 1. 策略定义(对应 tenant-policy.yaml)----------
TENANTS = {
"tenant-a": {"max_requests": 3, "roles": {"reader", "invoker"},
"allowed_models": {"llama-7b"}},
"tenant-b": {"max_requests": 5, "roles": {"reader"},
"allowed_models": {"llama-13b"}},
}
RBAC = { # 角色 -> 允许动作(最小权限)
"reader": {"list_models"},
"invoker": {"list_models", "invoke"},
}

class AccessDenied(Exception):
"""所有越权都抛它,便于网关统一拒绝并审计"""

@dataclass
class Gateway:
usage: dict = field(default_factory=dict) # per-tenant 配额计数器
audit_log: list = field(default_factory=list)

def _audit(self, tenant, action, resource, decision, reason=""):
self.audit_log.append({
"ts": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
"tenant": tenant, "action": action, "resource": resource,
"decision": decision, "reason": reason,
})

def handle(self, tenant_id: str, action: str, model: str):
# 关卡 0:租户是否存在
cfg = TENANTS.get(tenant_id)
if cfg is None:
self._audit(tenant_id, action, model, "DENY", "unknown tenant")
raise AccessDenied(f"未知租户 {tenant_id}")

# 关卡 1:RBAC —— 该租户的角色集合里有没有人允许这个 action
permitted = set().union(*(RBAC.get(r, set()) for r in cfg["roles"]))
if action not in permitted:
self._audit(tenant_id, action, model, "DENY", "RBAC: 角色无此权限")
raise AccessDenied(f"{tenant_id} 的角色无权执行 {action}")

# 关卡 2:资源隔离 —— 只能访问本租户被授权的模型(跨租户访问拦截)
if action == "invoke" and model not in cfg["allowed_models"]:
self._audit(tenant_id, action, model, "DENY", "跨租户/越权模型访问")
raise AccessDenied(f"{tenant_id} 无权访问模型 {model}")

# 关卡 3:配额 —— 硬配额计数(吵闹邻居防护)
if action == "invoke":
used = self.usage.get(tenant_id, 0)
if used >= cfg["max_requests"]:
self._audit(tenant_id, action, model, "DENY", "超出配额(429)")
raise AccessDenied(f"{tenant_id} 配额已耗尽 ({used}/{cfg['max_requests']})")
self.usage[tenant_id] = used + 1

# 全部通过:执行(mock)并审计 ALLOW
self._audit(tenant_id, action, model, "ALLOW")
return f"[OK] {tenant_id} 执行 {action} on {model}"


# ---------- 2. 越权测试:构造非法请求验证全部被拒 ----------
def expect_deny(fn, label):
try:
fn()
print(f"❌ 安全漏洞!本应拒绝却放行:{label}")
except AccessDenied as e:
print(f"✅ 正确拒绝 [{label}]:{e}")

if __name__ == "__main__":
gw = Gateway()

# 正常请求:tenant-a 是 invoker,调自己的 llama-7b
print(gw.handle("tenant-a", "invoke", "llama-7b"))
print(gw.handle("tenant-a", "invoke", "llama-7b"))
print(gw.handle("tenant-a", "invoke", "llama-7b"))

print("\n--- 越权测试 ---")
# 越权 1:超配额(tenant-a 第 4 次 invoke,max=3)
expect_deny(lambda: gw.handle("tenant-a", "invoke", "llama-7b"), "超配额")
# 越权 2:跨租户访问(tenant-a 调 tenant-b 的 llama-13b)
expect_deny(lambda: gw.handle("tenant-a", "invoke", "llama-13b"), "跨租户模型访问")
# 越权 3:RBAC 不足(tenant-b 只有 reader,无权 invoke)
expect_deny(lambda: gw.handle("tenant-b", "invoke", "llama-13b"), "RBAC 权限不足")
# 越权 4:未知租户
expect_deny(lambda: gw.handle("ghost", "list_models", "llama-7b"), "未知租户")

print("\n--- 审计日志(DENY 是一等公民)---")
for r in gw.audit_log:
print(f"{r['ts']} | {r['tenant']:9} | {r['action']:11} | "
f"{r['resource']:9} | {r['decision']:5} | {r['reason']}")

3.3 运行与观察

python mock_gateway.py

你会看到前 3 次 tenant-a 的正常 invoke 成功;随后 4 个越权请求全部被 AccessDenied 拦截并打印 ✅——分别命中配额、资源隔离、RBAC、租户校验四道关卡。最后的审计日志里,每一条 DENY 都带着明确原因,正是合规取证的「黑匣子」。这就是一个最小但完整的零信任网关:默认拒绝、逐关校验、全程审计。

踩坑预警 (Gotchas)

  • 配额计数器并发不安全:本例的 self.usage[tenant_id] += 1 在多线程/多副本下会竞态超卖。生产必须用 Redis 原子 INCR + 滑动窗口,或在网关副本间共享一致的计数器,否则配额形同虚设。
  • 只校验不审计 = 合规裸奔:很多实现只在拒绝时返回 429,却不落审计日志。DENY 事件必须持久化并告警——它是攻击的早期信号。
  • RBAC 用「黑名单」是反模式:永远用白名单(默认拒绝,显式授权)。一旦改成「列出禁止的动作」,新增动作时默认放行,等于埋雷。
  • 软件隔离不等于硬件隔离namespace + 配额防的是「无意越界」,但同一物理 GPU 上的显存侧信道防不住。强合规场景必须叠加 MIG/vGPU 硬件分区
  • tenant_id 必须由网关在 mTLS 校验后注入,绝不能信客户端自报:否则攻击者伪造 tenant_id 即可冒充任意租户。

深入思考

下面三题每题先给题干,再用 <details> 折叠一份图文并茂的参考答案。建议先合上答案自己想 3 分钟,再展开对照。

思考题 1:隔离粒度与计费的分级权衡

你的平台有 200 个租户,但只有 80 块 H100。MIG 最多把一块卡切 7 份,硬件隔离做不到「每租户一实例」。结合 2.1 节的逻辑/资源/硬件三层隔离,设计一个分级隔离策略:哪些租户值得独占 MIG 实例(强合规/高付费),哪些可以共享 namespace 软隔离?如何让计费模型反映这种隔离成本差异?

展开参考答案(含分级隔离决策图 + 成本对比表)

结论:隔离粒度不是「越硬越好」,而是按租户的合规等级与付费意愿做分级——强合规/高付费租户独占 MIG 硬实例(贵但安全),长尾低敏租户共享 namespace 软隔离(省但弱),并让计费直接按隔离层级定价,使「想要更硬的隔离」的租户为其占用的物理资源买单。

把 2.1 节的三层隔离映射成一棵分级决策树:

三个层级的成本与隔离强度对比

层级隔离手段隔离强度资源利用率计费基准(示意)适用租户
Tier-3 独占 MIG硬件物理分区最强(防侧信道)低(独占即闲置也占用)按整片硬实例计,约 40 美元/卡·天强合规、涉密
Tier-2 大切片/整卡资源配额 + MIG中(防吵闹邻居)按预留配额计,约 25 美元/卡·天高付费、稳定高 QPS
Tier-1 共享 namespace逻辑 + 软配额弱(仅防误访问)高(可超卖空闲资源)按实际用量计,约 8 美元/卡·天长尾、低敏、突发

用具体数字算一遍(200 租户 / 80 卡的容量分配):

  1. 假设 200 个租户里 10% 是强合规(20 个),每个独占 1 个 MIG 实例。一块 H100 切 7 份,则 20 个实例约需 ⌈20/7⌉ = 3 块卡
  2. 30% 高付费(60 个) 走 Tier-2,按预留切片,假设平均 2 个租户共享一块卡的预留容量 → 约 30 块卡
  3. 剩下 60% 长尾(120 个) 走 Tier-1 共享 namespace,靠软配额超卖跑在剩余的 47 块卡上——这一层故意做高密度,因为大多数长尾租户的真实负载是「偶发且低」。
  4. 计费闭环:Tier-3 租户为「独占即闲置也付费」买单(隔离成本内化为价格),Tier-1 租户因接受被抢占而拿到最低价——这样「想要更硬隔离」的需求会被价格信号自动调节,而不是人人都来抢 MIG。

一句话:隔离策略的本质是把「物理资源的稀缺」翻译成「分级的价格」,让合规需求与成本对齐,而不是靠平台团队人工拍板谁配独占。

思考题 2:审计日志的存储与查询矛盾

全量审计日志每天产生 TB 级数据,既要满足「7 年留存」的合规要求,又要支持「秒级查询某租户上周的所有 DENY 事件」。结合 2.4 节的审计字段集,权衡冷热分层的架构,并说明 trace_id 在跨服务关联中为何不可或缺。

展开参考答案(含审计冷热分层架构图 + 冷热对比表)

结论:留存与查询是两个目标不同的需求——「7 年留存」要的是便宜、不可篡改;「秒级查询」要的是索引、低延迟。把它们硬塞进一套存储必然两头不讨好,正解是冷热分层:近期热数据进 Elasticsearch 支撑秒级检索,历史冷数据落对象存储 + Parquet 列式压缩满足长期合规,两者用统一 schema 与 trace_id 串起来。

冷热两层的取舍对比

维度热层(Elasticsearch)冷层(对象存储 + Parquet)
覆盖范围近 30 天全部 7 年
查询延迟秒级(倒排索引)数十秒~分钟(全扫/分区裁剪)
存储单价高(约 0.2 美元/GB·月)极低(约 0.02 美元/GB·月)
不可篡改靠权限控制WORM(一次写多次读)锁定
典型查询「某租户上周所有 DENY」「2 年前那次合规事件取证」

用具体数字算一遍(为什么必须分层):

  1. 假设每天 2 TB 审计日志,7 年 ≈ 2 × 365 × 7 ≈ 5110 TB ≈ 5 PB
  2. 若全量塞进 Elasticsearch(约 0.2 美元/GB·月):5 × 1024 × 1024 GB × 0.2 美元 ≈ 每月约 105 万美元——纯属烧钱,且 99% 的老数据根本不会被查。
  3. 分层后:热层只存近 30 天 ≈ 60 TB,月成本约 60 × 1024 × 0.2 ≈ 12000 美元;冷层 5 × 1024 × 1024 GB × 0.02 美元 ≈ 每月约 10.5 万美元且还能再用归档存储压到更低。整体成本降一个数量级,查询体验反而更好。

为什么 trace_id 不可或缺:一次跨租户请求会穿过网关 → RBAC 引擎 → 配额管理器 → 调度器多个服务,每个服务各自落一条审计记录。没有 trace_id,这些记录就是一堆孤立的点,取证时无法拼出「这一次请求到底发生了什么」。trace_id 是把分散在不同服务、不同存储层(热层 ES 与冷层 Parquet)的记录重新缝合成一条完整时间线的唯一外键——它让「秒级查某次越权的全链路」成为可能。

思考题 3:零信任的性能代价与缓存风险

每次请求都做 mTLS 握手 + RBAC 校验 + 配额查询 + 审计落盘,会显著增加 P99 延迟。结合 2.2 节的请求穿越路径,分析你会在哪些环节引入缓存(RBAC 决策缓存、JWT 校验缓存) 来摊薄开销?缓存又会带来什么「权限回收延迟」的安全风险,如何用 TTL 与主动失效兼顾安全与性能?

展开参考答案(含缓存命中与权限回收时序图 + 关卡耗时对比表)

结论:零信任的关卡里,mTLS 握手、JWT 校验、RBAC 决策是「读多写少、结果短期不变」的,适合缓存;而配额计数是「每次都变」的强一致状态,不能缓存。缓存的代价是「权限回收延迟」——管理员撤销权限后,缓存仍可能在 TTL 内放行旧决策,正解是「短 TTL 兜底 + 撤权事件主动失效」双保险。

各道关卡能否缓存、收益如何

关卡单次耗时(示意)能否缓存缓存策略
mTLS 握手~5 ms能(连接复用)TLS session 复用/长连接,避免每请求重握手
JWT 校验~2 ms缓存验签结果至 token 过期,跳过重复验签
RBAC 决策~10 ms(远程策略引擎)按「身份+动作+资源」缓存决策,短 TTL
配额计数~1 ms(Redis INCR)不能强一致状态,每次必须真实计数
审计落盘~3 ms不能(但可异步)走异步缓冲(Kafka),不阻塞主路径

用具体数字算一遍(缓存对 P99 的摊薄):

  1. 无缓存时一次请求的策略开销 ≈ 5 + 2 + 10 + 1 + 3 = 21 ms,在高 QPS 下这部分直接抬高 P99。
  2. 加上 RBAC + JWT 缓存(命中率假设 95%):命中时省掉 2 + 10 = 12 ms,平均开销 ≈ 21 − 0.95 × 12 ≈ 9.6 msP99 策略开销腰斩
  3. mTLS 用长连接复用后,握手 5 ms 仅在建连时付一次,稳态下趋近 0。

权限回收延迟风险与对策:缓存的本质是「用一段时间的旧决策换性能」。若管理员刚撤销某租户对涉密模型的权限,而网关缓存里还存着 60 秒前的 ALLOW,这 60 秒就是越权窗口——攻击的关键时间点。兼顾安全与性能的标准做法是双保险:

  • 短 TTL 兜底:决策缓存 TTL 设得足够短(如 30~60 秒),即使主动失效失败,最坏也只暴露一个 TTL 周期。
  • 撤权事件主动失效:RBAC 引擎在权限变更时,向所有网关副本广播 invalidate 事件,立即清除对应缓存 key——把回收延迟从「一个 TTL」压到「一次事件传播」(毫秒级)。
  • 配额永不缓存:配额是安全与稳定性的硬边界,必须走 Redis 原子计数实时校验,否则缓存会直接导致超卖。

一句话:零信任不是「拒绝缓存」,而是「分清哪些决策可缓存、并为缓存配一套失效机制」——让性能优化不偷走安全性。

架构师技术领导力补遗:上述权衡没有「标准答案」,落地它们靠的是技术 RFC(Request for Comments) 驱动的共识。架构师推动系统与编译器架构演进的核心动作,是把「多租户隔离方案」「审计存储分层」这类决策写成 RFC——清晰陈述问题背景、备选方案、权衡矩阵、决策与理由,让全团队在文档上 review、留痕、达成共识。RFC 的价值不在于「写对」,而在于把隐性的架构权衡显性化,让一年后接手的人能读懂「当初为什么这么切」。这正是从「写代码的高级工程师」跃迁到「定方向的架构师」的关键能力。

延伸阅读

1. 核心规范与论文

  • NIST SP 800-207 Zero Trust Architecture — 零信任的权威定义,理解「默认不信任、持续验证」的第一性原理。
  • SPIFFE/SPIRE 规范 — 云原生工作负载身份(mTLS 证书自动签发)的事实标准,零信任落地的基石。

2. 相关高 Star 仓库与源码必读路径

  • open-policy-agent/opa — 看 Rego 策略语言如何把 RBAC/ABAC 决策从代码中解耦,平台治理的策略引擎首选。
  • kubernetes/kubernetes — 看 pkg/quota/plugin/pkg/auth/ 下 ResourceQuota 与 RBAC 的工业级实现。
  • NVIDIA/k8s-device-plugin + MIG 文档 — 理解 GPU 硬件隔离如何暴露给调度器。

3. 优质博客 / 工程实践

  • NVIDIA「Multi-Instance GPU (MIG)」官方技术文档系列 — 硬件隔离的权威细节。
  • 各大云厂商(AWS Bedrock / Azure OpenAI)的多租户隔离与合规白皮书 — 学习产品级治理的边界设计。
  • Google「Site Reliability Engineering」中关于配额与限流(Handling Overload)的章节 — 软/硬配额权衡的经典论述。

下一篇L6.4 终极工程愿景:统一 IR、AI-First 编译器与 LLM-Native 编译框架:跳出单点治理,从「整个 AI 平台的工程哲学」视角,收束 L0–L6 的全栈认知,构建架构师的终极系统观。