多租户与权限
HeteroFlow 是本站的异构算力与大模型调度产品。多租户与权限。多租户与权限 多租户(Multi-Tenancy)是 HeteroFlow 平台的企业级核心能力:把一套 GPU 集群安全地划分给多个团队或客户使用。 租户(Tenant)是平台最基本的隔离单元 ——每个租户拥有独立的成员名单、独立的资源配额、独立的数据边界与独立的用量账目,租户…
多租户与权限
多租户(Multi-Tenancy)是 HeteroFlow 平台的企业级核心能力:把一套 GPU 集群安全地划分给多个团队或客户使用。租户(Tenant)是平台最基本的隔离单元——每个租户拥有独立的成员名单、独立的资源配额、独立的数据边界与独立的用量账目,租户之间默认完全隔离,一个租户在任何接口上都无法看到、更无法操作另一个租户的资源。可以把它类比成一栋写字楼:整栋楼是集群,每个租户占一层,每层有自己的工位配额(GPU 配额)、门禁卡(API Key)、水电表(用量核算)和访客登记(操作审计)。
平台采用两级管理域设计:平台管理员管理"有哪些租户"——创建租户、调整配额、划拨节点、配置跨租户共享组,面向平台运营方;租户管理员管理"租户内部的人与资源"——邀请成员、分配角色、设定成员密钥限额、查看本租户用量与审计。两级管理域互不越权:租户管理员不能决定跨租户事务,平台管理员的敏感代管操作则全程留痕。这套体系让"一套集群服务多个客户"在数据、资源、凭据、用量核算四个维度上同时成立。
功能总览
| 能力 | 说明 | 生效层 |
|---|---|---|
| 角色体系 | 平台角色 + 租户内三级角色(admin/member/viewer),逐资源归属判定 | API / 业务层 |
| 成员管理 | 创建并加入、邀请、改角色、移除,成员变更即时吊销旧会话 | 管理面 |
| 成员级 API Key | 每个成员自助签发 sk-tenant- Key,权限等同持有者本人 | 凭据层 |
| 数据隔离 | tenant_id 全链路注入,跨租户访问统一 404,不泄露资源存在性 | 中间件 / SQL / handler 三层 |
| 资源配额 | GPU 数 / 推理端点数 / QPS / 存储四维配额,并发安全不超卖 | 调度层 |
| QPS 限流 | 租户级滑动窗口限流,可叠加 Key 级限流与日/月额度 | 网关层 |
| 用量统计 | Token 用量与 GPU 时长双轨计量,可按成员拆分 | 计量层 |
| 跨租户共享组 | 平台管理员显式配组,组内可共卡/复用,组外完全隔离 | 调度 / 网关 |
| 租户生命周期 | 创建(可自举首个管理员)、停用、软删除均有明确行为定义 | 管理面 |
| 操作审计 | 全部修改型操作留痕,敏感字段脱敏,代管操作标记目标租户 | 审计层 |
---
一、角色体系
三层权限模型
权限判定由三层组成,逐层收窄:
| 层 | 载体 | 回答的问题 |
|---|---|---|
| 平台角色 | users.role(admin / user) | 这个人是不是平台运维? |
| 租户边界 | tenant_id | 这个人属于哪个租户? |
| 租户内角色 | tenant_users.role(admin / member / viewer) | 这个人在租户里能做什么? |
两个"角色"概念务必分清:平台角色回答"是否平台运维",租户内角色回答"在租户内部是管理员、普通成员还是只读者"。一个平台角色普通的账号完全可以是自己租户里的租户管理员(团队负责人的常见形态);平台运维则在任何租户上都被判定为等价管理员,不查 tenant_users 表。
租户内三级角色能力矩阵
| 能力 | 租户 admin | member | viewer |
|---|---|---|---|
| 查看本租户全部资源(任务/任务组/端点/卷) | ✅ | 仅自己创建的 | 仅自己创建的 |
| 创建任务 / 任务组 / 推理端点 / 存储卷 | ✅ | ✅ | ❌(一律 403) |
| 修改 / 删除资源 | 本租户任意(代管留痕) | 仅自己的 | ❌(即便自己的资源也 403) |
| 签发自己的 API Key | ✅(scope 上限 read+write) | ✅(scope 上限 read+write) | ✅(scope 上限仅 read) |
| 撤销 / 编辑自己的 Key | ✅ | ✅ | ❌(只能等 admin 代为撤销) |
| 管理他人的 Key(撤销/限额/路由策略) | ✅ | ❌ | ❌ |
| 邀请 / 移除成员、修改角色 | ✅ | ❌ | ❌ |
| 读取本租户审计日志 | ✅ | ❌ | ❌ |
| 按成员拆分查看用量(group_by=user) | ✅ | ❌ | ❌ |
| 节点管理 / 节点监控页面 | ✅ | ❌(入口隐藏并重定向) | ❌ |
| 删除本租户独占告警 | ✅ | ❌ | ❌ |
两条容易被忽略的红线:
- viewer 的只读先于归属判定生效——viewer 不能发起任何写操作,哪怕对象是自己创建的资源(例如 viewer 无法撤销自己名下的 Key,需租户管理员代为处理)。
- 角色查不出来时按最窄档处理(fail-closed)——查库失败或不在成员表里的调用方,一律按 member/viewer 的最窄口径执行,绝不因"查不到"而放宽。
API Key scope:叠加在角色之上的第二层闸门
每把 Key 签发时声明 scope,每次请求由中间件强制校验:
| Scope | 允许的方法 | 谁能签发 |
|---|---|---|
read | GET / HEAD | 任意成员(含 viewer) |
write | 全部方法 | 仅 admin / member(viewer 申请 write 会被 403) |
scope 上限由持有者的租户内角色决定:viewer 的所有 Key 强制为 read。这样"viewer 只读"在资源归属判定与 Key scope 两道闸门上同时成立,单独绕过其中一道都没有用。
四层视角
| 视角 | 典型用户 | 看到的世界 |
|---|---|---|
| 平台管理员 | 平台运维 | 全平台:所有租户、所有节点(含划归各租户的专属节点)、跨租户用量与审计 |
| 租户 admin | 团队负责人 | 本租户全部资源(不区分是哪个成员创建的)+ 共享节点 |
| member | 算法工程师 | 本租户内自己创建的资源 + 共享节点 |
| viewer | 业务方 / 审计员 | 与 member 相同的可见范围,但任何写操作被拒绝 |
聚合类读接口(仪表盘、任务统计、告警、分片统计、健康明细)与上面的三档可见范围同源判定——同一个人在任何页面看到的范围一致。
---
二、成员管理
租户成员的四类管理操作:
| 操作 | API | 权限 | 说明 |
|---|---|---|---|
| 创建并加入 | POST /api/v1/tenant/users/new | 租户 admin / 平台管理员 | 一步同时创建全新账号并挂载到本租户,单事务完成;新账号平台角色固定为普通用户,不可指定(防升权) |
| 邀请成员 | POST /api/v1/tenant/users | 租户 admin / 平台管理员 | 目标必须是已存在的账号;公共池账号纳入独立租户仅平台管理员可做 |
| 修改角色 | PUT /api/v1/tenant/users/{id} | 租户 admin / 平台管理员 | 角色白名单 admin / member / viewer,字段名敲错返回 400 而非静默兜底 |
| 移除成员 | DELETE /api/v1/tenant/users/{id} | 租户 admin / 平台管理员 | 见下方级联清理规则 |
"创建并加入"与"邀请成员"的区别:前者面向"人还不存在"的场景,建号与挂载在同一数据库事务里,要么都成要么都不成;后者面向"账号已在别处注册"的场景。租户管理员给租户加人优先用"创建并加入"。
移除成员的级联清理:移除时按账号来源分两支——经"创建并加入"产生的账号在最后一个成员关系被移除时连账号带全部 Key 一起删除(释放用户名);先在别处注册、后被邀请进来的既有账号只解绑:撤销其在本租户的所有 Key、账号归属回落到公共池。
成员变更即时吊销会话:以下操作与业务写入同一事务让当事人已签发的 JWT 立即失效(多实例部署下最多滞后 5 秒):
| 操作 | 对旧会话的影响 |
|---|---|
| 修改角色 | 旧 token 失效,需重新登录换取与新角色一致的会话 |
| 移除成员(解绑分支) | 旧 token 失效 + 撤销其本租户全部 Key |
| 移除成员(连号删除分支) | 账号删除,token 自然无效 |
| 修改密码 | 旧 token 失效 |
---
三、成员级 API Key
Key 类型
| Key 类型 | 格式 | 用途 |
|---|---|---|
| 成员级 Tenant Key | sk-tenant-{32位hex} | 成员自助签发,绑定持有者身份,可用于资源创建与推理调用 |
| 端点 Key | sk- 开头,创建推理服务时签发 | 仅推理调用,绑定单一端点,无成员身份 |
| 平台管理员 Key | sk-admin- 开头 | 平台级跨租户操作(须与服务端配置的管理员密钥完全一致) |
签发与存储
- 本租户任意已认证成员(含 viewer)均可自助创建自己的 Key——不需要、也不能由管理员代创建。这是刻意设计:资源归属要精确到人,必须让每个成员持有自己的 Key;管理员冒充成员身份操作会破坏追责链。
- 服务端用加密随机数生成 32 位 hex,拼成
sk-tenant-前缀;数据库只存 SHA-256 哈希,明文仅在创建响应里返回一次,列表接口只回显前缀。 - 请求体严格解码:
expires_in_days等字段名敲错会被 400 拒绝,不会静默签出永不过期的密钥。
权限继承
Key 天然继承持有者的全部权限判定:持有者的租户归属、租户内角色、资源归属(本人资源可读写、他人资源 404)对 Key 调用与本人登录态完全同口径——下游判定的是同一个身份值,不区分它来自登录还是来自 Key。因此若持有者是租户管理员,其 Key 同样能执行成员管理等管理面操作。
Key 级限流与额度
每把 Key 可单独配置五项限额:qps_limit(每秒请求数)、concurrency_limit(并发在途请求数)、daily_call_limit / monthly_call_limit(日/月调用次数)、monthly_token_quota(月 Token 额度,当前仅占位不强制)。全部为 0(默认)表示不限制。超限返回 429,带 Retry-After 与 X-RateLimit-* 响应头,额度类带重置时间。只作用于推理调用接口,不影响管理面。
成员 Key 限额上限
租户管理员可为整个租户设置一组"成员 Key 限额上限"(GET/PUT /api/v1/tenant/member-key-limit-caps):设置后,非管理员成员(含 viewer)新建或编辑自己的 Key 时,五项限额每一项都不得超过上限——"填 0 表示不限"也算超过。上限只约束成员之后的建/改操作,不追溯调整存量 Key;租户管理员与平台管理员不受约束。前端建 Key 弹窗会展示当前上限并默认填入。
撤销与编辑
- 撤销(DELETE)立即生效:置撤销标记并即时清空校验缓存,下一个请求即 401。
- 编辑限额(PUT)权限同撤销:本人(viewer 除外)或租户管理员/平台管理员。成员编辑自己的 Key 时限额只能收紧、不能放宽或取消;新建不限额 Key 绕过检查的路子则被上面的租户级上限堵住。
- 智能路由覆盖:开启模型智能路由后,可对单把 Key 覆盖
model=auto的开关与策略(quality/speed/cost),不影响其他 Key。
---
四、认证集成:JWT 与 API Key 双轨
平台同时接受两种凭据,覆盖"人"与"程序"两类调用方:
| 凭据 | 适合谁 | 获得方式 | 失效机制 |
|---|---|---|---|
| JWT(登录态) | 人:Web 控制台、交互式操作 | 用户名密码登录签发 | 过期时间 + 版本号即时吊销(改密/改角色/移除/删号) |
| API Key | 程序:脚本、业务系统、推理调用 | 成员自助签发 | 撤销立即生效 / 到期自动失效 |
两轨在中间件层汇合成同一身份上下文:租户归属、用户身份、scope 三要素注入后,下游所有权限与配额逻辑对两种凭据一视同仁。推理接口(/v1/chat/completions 等)两条认证路径都可用——端点专属 Key(无成员身份,用量不按人拆分)或成员自己的 Key / 登录态(用量带成员归属,可按人拆账)。平台级账号体系(登录、密码、平台管理员账号)的完整说明见《用户管理》篇,本篇聚焦租户内部的凭据与权限。
每次请求经过四层中间件依次处理:租户上下文(解析凭据注入身份)→ 审计(旁路捕获,被拒绝的请求也会被审计)→ scope 校验(read 仅放行 GET/HEAD)→ QPS 限流(滑动窗口)。
---
五、数据隔离机制
三层强制
| 层 | 机制 |
|---|---|
| 中间件层 | 每个请求解析凭据,注入租户归属与"租户委托主体"标记;只有成员 Key 与带租户声明的登录态会被标记为需逐资源校验的主体 |
| SQL 层 | 业务查询自动附加 WHERE tenant_id = ? 过滤 |
| Handler 层 | 逐资源归属校验:非本人的资源一律拒绝 |
统一 404 语义:跨租户访问不返回 403 而返回 404——"不存在"与"存在但无权"使用同一响应,调用方无法通过状态码枚举其他租户的资源 ID。这是平台安全基线,适用于任务、任务组、端点、节点、存储卷等全部资源类型。
逐资源类型的隔离行为
| 资源 | 隔离行为 |
|---|---|
| 任务 / 任务组 | member 只见/只管自己创建的;租户 admin 本租户任意;跨租户 404;任务租户必须与所属任务组一致(数据库触发器兜底拒绝不一致写入) |
| 推理端点 / 模型路由 | 端点创建仅限任务组创建者本人或租户 admin;路由归属随所指任务组;跨租户 404;推理选路严格按调用方租户收窄 |
| 模型 | 租户可见共享节点 + 本租户私有节点的扫描结果;其它租户私有节点的模型信息不返回 |
| 告警 | 节点类告警按节点可见性(仅 admin 可见节点类告警)、任务类告警按任务归属且 member 只看自己任务触发的 |
| 节点 | 共享节点所有租户可见;私有节点仅属主租户可见(列表、详情、调度三处同判据);节点管理页对 member/viewer 隐藏入口 |
| 存储卷 | 与任务组同一套闸门:member 只见自己的卷,跨租户 404;挂载校验同样按租户,跨租户引用与"不存在"同文案 |
| 用量 / 审计 | 用量按租户隔离、可再按成员拆分;审计仅本租户,平台管理员可定向查询任意租户 |
调度边界:排队与共存是两回事
排队等空闲资源不看租户——一张卡释放后任何租户的任务都能按先来后到排上;同时共存复用才看租户——同一张物理卡被多个任务同时占用(GPU 分片、推理显存比例共卡、训练借用休眠推理卡)必须"同租户或同共享组",组关系查询失败一律按不允许处理(fail-closed)。无卡可配时明确失败并提示需要共享组,绝不静默降级为跨租户放置。
---
六、配额体系
配额维度
| 配额 | 缺省值 | 执行状态 |
|---|---|---|
max_gpus 最大 GPU 数 | 1 | 强制执行(含休眠任务) |
max_endpoints 最大推理端点数 | 5 | 强制执行 |
max_qps 最大每秒请求数 | 100 | 强制执行(滑动窗口限流) |
max_storage_gb 最大存储容量 | 10 | 字段已落库,校验暂未实现(见已知限制) |
配额与套餐对应关系:Free(1 GPU / 5 端点 / 100 QPS / 10 GB,个人评估)、Pro(10 / 50 / 1000 / 100,中小团队)、Enterprise(不限,大企业)。实际结算规则由业务方制定,平台提供用量数据。
并发安全:advisory lock fail-closed
GPU 配额检查在"进程内互斥锁 + PostgreSQL advisory lock(跨实例)"的临界区内执行:多实例部署下并发请求也不会突破配额上限(双实例多轮压测恒为上限)。取锁失败时 fail-closed——宁可拒绝请求,绝不放行后超卖。
配额语义细则
- 显式设为 0 就是真的 0:管理员把某项配额降为 0 表示"封零该租户的这一能力"(例如临时冻结推理请求而不删除租户),系统按字面值拒绝新请求,不会误解为"不限"。
- 降级检查:调低配额前先检查当前用量,新值低于已有活跃资源数时返回 409 并说明数量,不允许配额降到资源存量之下。
- 超限行为:GPU 配额超限时创建任务组直接失败(403);端点配额超限时仅跳过端点创建、任务组本身仍创建成功。
- 自动扩容同样封顶:自动扩容与调度窗口扩容按"剩余配额可容纳的副本数"封顶;滚动更新允许临时超额(否则满额租户永远无法不中断更新),但超额部分有明确上限。
- 豁免面:仅平台管理员与系统级内部调用豁免配额检查;公共默认租户与其它租户同等受配额约束。
---
七、QPS 滑动窗口限流
窗口机制
限流按租户维度执行滑动窗口算法:以当前时刻为终点取最近 1 秒的真实请求时间戳,窗口内请求数达到 max_qps 即拒绝。相比固定窗口,滑动窗口不存在"窗口边界处瞬间双倍流量"的漏洞。
超限响应
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 1
{"error": "rate limit exceeded: 100/100 QPS for tenant-abc123"}分层与豁免
| 层级 | 维度 | 说明 |
|---|---|---|
| 租户级 QPS | 租户 | max_qps 配额驱动,覆盖该租户全部请求 |
| Key 级 QPS / 并发 | 单把 Key | 可选配置,只作用于推理调用接口 |
| Key 级日/月额度 | 单把 Key | 按调用次数计,超限 429 并带重置时间 |
豁免面只有两类:平台管理员;以及不归属任何租户的系统级内部调用(节点心跳、探针等)。租户被显式设为 max_qps=0 时其全部请求被拒绝——这是管理员的冻结手段,不是"不限"。
---
八、用量统计与核算
双轨计量
| 计量轨 | 覆盖 | 结算时机 |
|---|---|---|
| Token 用量 | 推理调用(/v1/*) | 每次请求完成后异步记账:明细行落库 + 按日聚合同步 UPSERT(无需定时任务) |
| GPU 时长 | 训练任务与普通容器任务(推理任务是常驻容器 + Token 计量,两套不叠加) | 任务进入终态时一次性结算,幂等设计保证重复触发不重复记账 |
Token 提取对流式响应同样生效:转发前自动注入 stream_options.include_usage,从 SSE 数据行中提取用量;失败请求 Token 记 0,状态按真实结果记 success/error,retry 不重复核算。
按成员拆分
用量查询支持 ?user_id=(查单个成员)与 ?group_by=user(按成员汇总,仅租户 admin / 平台管理员):每行含成员的 Token 两列、推理请求数与 GPU 秒数。成员身份发起的推理调用天然带归属;端点共享 Key 发起的调用无成员归属,计入租户总量但不进按人拆分(各行相加不一定等于总量,属预期)。
用量面板
Web 控制台"用量统计"页提供:推理请求次数、推理 Token 数、GPU 时长(GPU·小时)、任务数、活跃端点数五个维度,按日聚合可回溯。
---
九、跨租户共享租户组
默认完全隔离;共享租户组是唯一能打开跨租户共享的开关,也是唯一的授权来源。 由平台管理员显式配置:把若干租户放进同一个组,组内租户之间允许复用推理服务、共用同一张物理卡;组外租户之间完全隔离。一个租户可以同时属于多个组,判定谓词是"存在至少一个共同的启用中分组"。
| 语义点 | 行为 |
|---|---|
| 配置入口 | 平台管理员在控制台"共享租户组"页或 /api/v1/admin/share-groups 系列接口;单租户管理员无权决定跨租户事务 |
| 成员变更 | members 是整体替换而非增量;清空成员集即回到隔离 |
| 即时断 | 禁用组 / 解散组 / 移出成员当场生效,依赖该授权的存量跨租户模型路由被同步删除,不是"新任务不再共享、老的继续跑" |
| fail-closed | 组关系查询失败一律按"不同组"处理;授权关系不缓存,实时性优先 |
| 跨租户复用推理服务 | 需推理去重总闸与跨租户复用开关都打开,再过共享组判定,三级全通过才复用;否则各建各的(正常创建路径,不报错) |
| 共卡类路径 | GPU 分片、推理显存比例共卡、训练借用休眠推理卡三条路径统一走"同租户或同组"判定,逐一核对卡上每个占用租户,防传递性绕过 |
| 跨租户休眠唤醒 | 直接调用命中一条指向休眠中任务组的跨租户路由时,网关会自动尝试唤醒并有界等待,不会立即失败 |
共享组内的共卡是同组内的显存与算力复用(软配额语义,防善意超用);真正的硬件级隔离由 MIG 等硬件切分能力提供(硬件切分当前仅 NVIDIA 系真机可用),两者是不同层次。
---
十、租户生命周期
创建
创建租户时填写名称、套餐与四维配额(严格 JSON 解码,未知字段 400,防止配额字段名敲错后静默建出零配额租户;名称唯一,重名 409)。可选字段 admin_user_id 一步指定首个租户管理员:与建租户同一事务播种管理员角色,要么都成、要么都不成;不传时租户照常建成,但响应带 warning 提示该租户暂无管理员,需后续补种(平台管理员可"创建并加入"一个管理员账号)。这解决了"新租户没人能管"的自举缺口。
修改
可修改显示名、套餐、四维配额、状态与联系信息;租户名称不可改(带上不同名称返回 400)。配额降级有前置检查(见配额体系)。
停用(suspended)
停用后,该租户的资源申请类操作(创建任务、任务组扩容、创建推理端点、自动扩容与失败重建)被拒绝(403);读接口不受影响——停用租户的成员仍能登录查看自己的任务、用量与审计。这是刻意设计:欠费停用后客户要能看到用量、去付费。成员 Key 认证同样保持通过(仅排除已软删除的租户)。
删除(软删除)
删除是软删除(置删除标记,数据保留可恢复),带两级前置检查:还有活跃任务组 → 409;还有推理端点 → 409;全部通过后单事务级联:租户置删除态、清成员映射行、成员账号归属回落到公共池。补充约束:默认租户禁删(403,后端硬保护);已软删租户不可复活(对其执行更新一律 409)。注意删除租户与移除单个成员的凭据后果不同:软删除租户后,其成员 Key 因租户已删除而立即无法通过认证;但成员登录态 JWT 不会被吊销(与移除单个成员时"立即失效"不同),靠后续登录自然过渡。
---
十一、操作审计
记录范围与内容
全部修改型请求(POST/PUT/PATCH/DELETE)旁路捕获并异步写入审计表,记录:租户、用户、方法、路径、资源类型与 ID、状态码、错误信息、耗时、客户端 IP、User-Agent 与脱敏后的请求摘要(≤500 字符)。被 scope 或限流拒绝的请求同样留痕(审计中间件位于二者之前)。整租户数据导出这类敏感读操作也纳入审计。节点侧 agent 的例行状态回写不入审计,避免淹没真正的人工操作记录。
敏感字段脱敏
请求摘要落库前对 JSON 请求体递归脱敏:命中敏感键名(口令、密钥、凭据类)的值一律替换为 [REDACTED],采用子串/边界匹配——新增的 ssh_password、client_secret 等组合字段同样被覆盖,同时避免误伤 keyword 这类无关词。非 JSON 请求体不落原文,只记录字节数。历史明文行已清理。
跨租户可追溯
- 平台管理员用
?tenant_id=代其它租户操作时,目标租户写入独立的结构化列(可检索,审计页高亮显示);仅当"调用方是平台管理员且目标租户不同于自身"时记录,不会为普通调用方随手带的无效参数造出误导行。 - 代管留痕:租户 admin / 平台管理员操作他人资源时,审计行带"代管对象"字段,证明这次操作的真正归属。
- 平台管理员代发基础设施上报的审计行带专属标记,与 agent 自身上报严格区分。
保留期与查询
审计日志默认保留 365 天(可配置,下限 30 天),到期的清理分批异步执行。查询入口 GET /api/v1/tenant/audit-logs(分页 + 日期区间过滤),仅租户 admin / 平台管理员可读;平台管理员可定向查询任意租户。
---
十二、完整业务场景:客户接入全程
以客户 Acme 公司接入为例,串起多租户的全部能力:
| 步骤 | 操作方 | 动作 | 生效机制 |
|---|---|---|---|
| 1 | 平台管理员 | 创建租户 acme-corp(Pro 套餐,10 GPU),指定首个租户管理员 | 创建即隔离单元建立 |
| 2 | 租户管理员 | 用自己的账号登录,自助创建 API Key(write,90 天) | Key 绑定持有者身份 |
| 3 | 租户管理员 | "创建并加入"5 名工程师(member)+ 1 名业务方(viewer) | 角色决定各自可见范围 |
| 4 | 租户管理员 | 设置成员 Key 限额上限(如单 Key 并发 50) | 成员建 Key 自动受上限约束 |
| 5 | 工程师 | 创建推理服务(任务组 + 端点) | scope 校验 → 配额检查(advisory lock)→ 端点端口闸门 |
| 6 | 工程师 | 用自己的 Key 调用推理 API | 中间件链 → 按租户收窄选路 → Token 计量带成员归属 |
| 7 | 业务方(viewer) | 查看服务状态与用量 | 只读,任何写操作 403 |
| 8 | 租户管理员 | 月底查账:按成员拆分的用量 + 本租户审计日志 | group_by=user + 审计查询 |
| 9 | 平台管理员 | 按用量对 Acme 出账;如需与另一客户共卡,把两租户加入同一共享组 | 共享组是唯一跨租户授权来源 |
全程任何一步,Acme 的成员都无法看到其它租户的资源(404 语义),所有管理动作留审计痕。
---
十三、API 参考
租户管理(平台管理员)
| 路径 | 方法 | 用途 |
|---|---|---|
/api/v1/admin/tenants | GET / POST | 租户列表 / 创建租户 |
/api/v1/admin/tenants/{id} | GET / PUT / DELETE | 租户详情 / 修改(含配额降级检查)/ 删除(含前置检查) |
/api/v1/admin/share-groups | GET / POST | 共享组列表 / 建组 |
/api/v1/admin/share-groups/{id} | GET / PUT / DELETE | 组详情 / 改成员集 / 解散 |
成员与 Key(租户自助)
| 路径 | 方法 | 用途 | 权限 |
|---|---|---|---|
/api/v1/tenant/api-keys | GET / POST | Key 列表(仅回显前缀)/ 自助创建 | 本租户已认证成员 |
/api/v1/tenant/api-keys/{id} | PUT / DELETE | 编辑限额 / 撤销 | 本人(viewer 除外)或 admin |
/api/v1/tenant/api-keys/{id}/routing-policy | PUT | 智能路由 Key 级覆盖 | 同上 |
/api/v1/tenant/member-key-limit-caps | GET / PUT | 读 / 设置成员 Key 限额上限 | GET 全员;PUT 仅 admin |
/api/v1/tenant/users/new | POST | 创建并加入 | 租户 admin / 平台管理员 |
/api/v1/tenant/users | GET / POST | 成员列表 / 邀请 | 列表全员;邀请 admin |
/api/v1/tenant/users/{id} | PUT / DELETE | 改角色 / 移除 | admin |
/api/v1/tenant/usage | GET | 用量统计(?user_id= / ?group_by=user) | 全员;按人拆分仅 admin |
/api/v1/tenant/audit-logs | GET | 审计日志查询 | admin |
/api/v1/tenant/my-role | GET | 查自己在当前租户的角色 | 全员 |
/api/v1/tenant/share-group | GET | 本租户所在共享组(只读) | 全员 |
示例:创建租户(指定首个管理员)
curl -X POST http://your-server:3333/api/v1/admin/tenants \
-H "Authorization: Bearer <平台管理员JWT>" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-corp",
"display_name": "Acme Corporation",
"plan": "pro",
"max_gpus": 10,
"max_endpoints": 50,
"max_qps": 1000,
"admin_user_id": "user-1a2b3c4d"
}'
# 201 —— admin_user_id 与建租户同事务生效,该用户即刻成为本租户首个租户管理员
# 不传 admin_user_id 时租户照常建成,响应带 warning 提示需补种管理员示例:成员自助创建 Key
curl -X POST http://your-server:3333/api/v1/tenant/api-keys \
-H "Authorization: Bearer <成员JWT或已有Key>" \
-H "Content-Type: application/json" \
-d '{
"name": "production-key",
"scopes": ["read", "write"],
"expires_in_days": 90,
"qps_limit": 200,
"concurrency_limit": 50
}'
# 201 —— api_key 明文仅此一次返回;数据库只存哈希,列表只回显 sk-tenant-ab 前缀
# viewer 传 write 会被 403(角色天花板);各项限额若超过租户上限同样 403---
十四、最佳实践与故障排查
API Key 安全建议
1. 环境隔离:生产与测试环境使用不同 Key,避免测试侧泄露波及生产。
2. 定期轮换:建议每 90 天更换一次;签发时直接设置过期时间,到期自动失效。
3. 最小权限:只读场景签 read,业务读写才给 write;每把 Key 配置 QPS / 并发 / 日月额度限额,单把 Key 泄露的爆炸半径可控。
4. 人员变动:成员离职时由租户管理员立即撤销其名下全部 Key 并移除成员——移除操作本身会连带吊销其会话与 Key,两道都做最稳妥。
配额规划参考
| 业务场景 | 建议套餐 | GPU 配额 |
|---|---|---|
| 个人开发者测试 | Free | 1 |
| 小团队(5-10 人) | Pro | 5-10 |
| 中型企业(50-100 人) | Pro / Enterprise | 20-50 |
| 大型企业 / AI 公司 | Enterprise | 100+ |
常见问题速查
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| API 返回 403 | scope 不足 / 配额超限 / 租户停用 / viewer 发起写操作 | 核对 Key 的 scope、用量面板、租户状态 |
| API 返回 404 | 资源不存在,或它属于别的成员/租户(统一 404 语义) | 确认资源归属;跨租户访问本就不被允许 |
| API 返回 429 | 租户 QPS 超限,或单把 Key 的 QPS/并发/额度超限 | 降低请求频率;查看响应头的限额与重试提示 |
| API 返回 409 | 删除/配额降级时存在活跃资源,或租户名/模型路由名冲突 | 先清理活跃任务与端点再重试 |
| 改了成员角色但行为未变 | 旧登录会话已被吊销需重新登录;Key 侧角色判定有约 5 秒缓存 | 让成员重新登录;Key 调用稍候片刻即按新角色执行 |
---
十五、已知限制
以下事项如实列出,避免误依赖:
| 事项 | 现状 |
|---|---|
| 存储配额(max_storage_gb) | 字段已落库、页面可配,但运行时校验暂未实现(配置值暂不产生强制效果) |
| QPS 限流的多实例精度 | 租户级 QPS 限流为单实例内存计数,多实例部署时各实例独立计数(实际阈值约为配置值×实例数);配额检查已有跨实例锁,不受此影响;分布式限流在规划中 |
| 分片操作历史接口的租户过滤 | 分片操作历史的查询接口尚未按租户收窄,登录用户可读到全平台历史(收窄排期中);分片统计接口已收窄 |
| 节点类告警对 member/viewer 不可见 | 节点没有"创建者"概念,节点类告警整支仅 admin 可见——成员的任务因节点离线失败时能看到任务失败告警,看不到对应的节点离线告警 |
| 删除租户的成员去向语义 | 当前行为是成员账号统一回落公共池;"是否应强制先转移成员或禁止删除非空租户"的产品语义待定案 |
| 删除租户的二次确认 | 仅前端弹窗确认,后端未要求输入租户名称复核(规划中) |
| 创建任务时指定归属租户 | 仅 API 支持(平台管理员),前端创建任务表单暂未开放该字段 |
| 滚动更新临时超额 | 滚动更新允许有界临时超额(满额租户不中断更新的必要设计),超额上限受每批 surge 配置约束 |