资讯动态

OpenClaw进阶实战(三十七):番外2——多租户隔离方案

发布时间:2026/10/9 1:42:58 来源:尧图企业网站定制
1. 为什么 OpenClaw 多租户隔离在 K8s 上总翻车OpenClaw 多租户隔离说白了就是让多个团队或客户共用一套 K8s 集群跑 Agent但彼此的资源、数据、凭证完全看不见。它适合已经把 OpenClaw 部署到 K8s、开始接第二个业务方的团队。我见过太多人一上来就复制粘贴一个 Namespace 就以为隔离完了结果 A 租户的 Agent 能读到 B 租户的 PVC或者一个批量任务把整个集群的 CPU 吃满全员响应变慢。问题的根子在于K8s 的 Namespace 只是「逻辑分组」它默认不拦网络、不拦资源、不拦凭证。你不显式加 NetworkPolicyPod 之间就是通的你不加 ResourceQuota一个租户能占满节点你不做 RBAC默认 ServiceAccount 可能带着过高权限。OpenClaw 的 Agent 工作负载又比较特殊——它要挂载 workspace、要调模型 API、要执行技能脚本这三件事分别对应存储隔离、出口网络隔离、凭证隔离缺一个都算漏。所以这篇不聊虚的架构图直接给你能kubectl apply的清单Namespace RBAC ResourceQuota NetworkPolicy 四件套再讲怎么用 TaoToken 把多租户的模型调用入口收敛成一个统一 Key/API 通道避免每个租户各自管一堆 Key 导致审计黑洞。预期达成租户间资源互不挤占、网络互不可达、数据互不可见、调用可追溯。先明确一个前提你已经完成 OpenClaw 在 K8s 的部署集群版本 1.24有 cluster-admin 权限做初始配置。下面所有 YAML 都可以直接改租户名复用。2. TaoToken 前置把多租户模型调用入口收敛多租户场景下最容易被忽略的是「出口」。每个租户的 Agent 都要调模型如果每个租户配一套自己的 Key你会面临三个问题Key 散落在各租户 Secret 里无法统一轮换、用量无法按租户归集、某个租户 Key 泄露影响面不可控。我的做法是把所有租户的模型调用都指向 TaoToken 的统一 API 通道租户侧只拿到一个受控的 Key真正的上游模型凭证由平台侧管理。TaoToken 在这里扮演的是「统一 Key/API 通道」的角色它兼容 OpenAI 风格的接口OpenClaw 的 model provider 配置里把 base URL 指过去就行。这样租户的 Agent 配置里不出现任何上游厂商的原始凭证平台侧换模型、调额度、做审计都在一个入口完成。你需要先拿到两样东西一个 API Key以及确认接入文档里的 Base URL 格式。Key 在控制台的 API Keys 页面创建建议按租户维度建多个 Key命名带上租户标识方便后续按 Key 归集用量。接入文档里有完整的 endpoint 说明和参数示例配置前扫一遍能省很多试错。具体操作路径打开 https://taotoken.net/api-keys 创建 Key命名如tenant-a-openclaw创建后立即复制保存页面不会二次展示完整 Key。打开 https://taotoken.net/doc 确认当前 Base URL 和模型 ID 命名规范不同模型 ID 写法有差异。如果你要验证某个模型是否可用用 https://taotoken.net/chat 直接对话测试比在集群里 debug 快得多。长期跑 Agent 编码任务、需要稳定额度的看 https://taotoken.net/coding-plan 比按量计费更适合持续负载。这里有个关键设计每个租户一个 TaoToken Key但共享同一个 Base URL。租户的 Secret 里只存自己的 Key平台侧通过 Key 前缀或标签区分租户。这样既做到了凭证隔离A 租户的 Key 泄露不影响 B又做到了入口收敛所有调用都经过同一个通道审计和限流有统一落点。配置到 OpenClaw 时模型 provider 的写法大致是这样具体字段名以你部署版本的文档为准{ model: { primary: openai/gpt-4o-mini, providers: { openai: { baseUrl: https://taotoken.net/api, apiKey: ${TAOTOKEN_API_KEY} } } } }注意apiKey用环境变量引用不要硬编码。这个环境变量从租户自己的 Secret 注入下一节的 Deployment 里会体现。把这段配置和后面的 K8s 清单配合起来租户的模型调用就完全走统一通道了。3. 可复制配置Namespace/RBAC/ResourceQuota/NetworkPolicy 四件套这一节是全文的核心所有 YAML 都可以直接改tenant-a复用。我按「先建边界、再限资源、后锁网络、最后绑权限」的顺序给你照着 apply 就行。3.1 Namespace 与租户标签Namespace 本身不隔离但它是所有其他策略的挂载点。关键是打上tenant标签后面 NetworkPolicy 靠它做选择器。apiVersion: v1 kind: Namespace metadata: name: tenant-a labels: tenant: tenant-a isolation-level: standardisolation-level这个标签是我自己加的用来区分普通租户和高合规租户后面可以针对不同值套不同策略。3.2 ResourceQuota 与 LimitRangeResourceQuota 管的是「这个 Namespace 总共能用多少」LimitRange 管的是「单个 Pod 默认给多少」。两个都要配只配 Quota 的话一个 Pod 不写 resources 就能无限申请。apiVersion: v1 kind: ResourceQuota metadata: name: tenant-a-quota namespace: tenant-a spec: hard: requests.cpu: 20 requests.memory: 50Gi limits.cpu: 30 limits.memory: 100Gi persistentvolumeclaims: 10 pods: 50 --- apiVersion: v1 kind: LimitRange metadata: name: tenant-a-limits namespace: tenant-a spec: limits: - default: cpu: 1 memory: 2Gi defaultRequest: cpu: 500m memory: 1Gi type: Containerpods: 50这个限制很重要防止租户用大量小 Pod 绕过 CPU 配额。persistentvolumeclaims: 10防止 PVC 无限创建占满存储。3.3 RBAC租户内 ServiceAccount 与 RoleOpenClaw 的 Agent Pod 用独立的 ServiceAccount只给租户 Namespace 内的最小权限。绝对不要用 default ServiceAccount它的权限边界不清晰。apiVersion: v1 kind: ServiceAccount metadata: name: openclaw-agent namespace: tenant-a --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: openclaw-agent-role namespace: tenant-a rules: - apiGroups: [] resources: [configmaps, secrets] verbs: [get, list] - apiGroups: [] resources: [pods] verbs: [get, list] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: openclaw-agent-binding namespace: tenant-a subjects: - kind: ServiceAccount name: openclaw-agent namespace: tenant-a roleRef: kind: Role name: openclaw-agent-role apiGroup: rbac.authorization.k8s.io这个 Role 只允许读自己 Namespace 的 configmap/secret 和 pod不能跨 Namespace不能改资源。如果你的 Agent 需要动态创建 Job再单独加batch组的权限别一股脑给 cluster-admin。3.4 NetworkPolicy默认拒绝 白名单放行这是隔离的命门。K8s 默认所有 Pod 互通你必须先「默认拒绝」再按需放行。下面这条策略实现租户 A 的 Pod 只能和同租户 Pod 通信出口只能走 DNS 和公网模型 API。apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: tenant-a-isolation namespace: tenant-a spec: podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: tenant: tenant-a egress: - to: - namespaceSelector: matchLabels: tenant: tenant-a - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53 - ports: - protocol: TCP port: 443注意最后那条port: 443的 egress 规则没有to选择器意思是允许访问任意 IP 的 443 端口——这是给模型 API 出口用的。如果你要更严格把to换成具体的 CIDR 或域名解析后的 IP 段。DNS 那条必须放行否则 Pod 解析不了域名模型调用直接失败。3.5 租户 Secret 注入把 TaoToken 的 Key 存成租户自己的 SecretPod 通过环境变量引用。apiVersion: v1 kind: Secret metadata: name: taotoken-credential namespace: tenant-a type: Opaque stringData: TAOTOKEN_API_KEY: sk-替换成你在控制台创建的租户Key创建命令建议用kubectl create secret generic配合--from-literal避免 Key 写进 YAML 文件被提交到 Gitkubectl create secret generic taotoken-credential \ --namespace tenant-a \ --from-literalTAOTOKEN_API_KEYsk-你的租户Key然后在 OpenClaw 的 Deployment 里引用env: - name: TAOTOKEN_API_KEY valueFrom: secretKeyRef: name: taotoken-credential key: TAOTOKEN_API_KEY到这里一个租户的完整边界就建好了。复制这套 YAML把tenant-a全局替换成tenant-b再 apply 一遍两个租户就物理隔离了。4. 验证请求确认租户间真的互不可见配置写完不代表隔离生效必须实测。我一般分四步验证资源配额生效、网络隔离生效、RBAC 生效、模型调用走通。4.1 验证 ResourceQuota在租户 A 的 Namespace 里尝试创建一个超出配额的 Podkubectl run quota-test --imagenginx --namespace tenant-a \ --requestscpu100 --restartNever预期报错Error from server (Forbidden): error when creating quota-test: pods quota-test is forbidden: exceeded quota: tenant-a-quota, requested: requests.cpu100, used: requests.cpu0, limited: requests.cpu20看到exceeded quota就说明配额生效了。再查一下当前用量kubectl get resourcequota tenant-a-quota -n tenant-a -o yamlstatus.used字段会显示实际占用。4.2 验证网络隔离这是最关键的一步。在租户 A 起一个测试 Pod尝试访问租户 B 的 Servicekubectl run nettest --imagebusybox --namespace tenant-a \ --restartNever -- sleep 3600 kubectl exec -n tenant-a nettest -- \ wget -qO- --timeout5 http://openclaw.tenant-b.svc.cluster.local预期结果是超时或连接被拒。如果返回了内容说明 NetworkPolicy 没生效检查两点集群的 CNI 插件是否支持 NetworkPolicyCalico、Cilium 支持Flannel 默认不支持以及策略是否真的 apply 到了正确的 Namespace。再验证同租户内通信正常kubectl exec -n tenant-a nettest -- \ wget -qO- --timeout5 http://openclaw.tenant-a.svc.cluster.local这条应该能通否则说明你的 ingress 规则写错了。4.3 验证 RBAC用租户 A 的 ServiceAccount 尝试读租户 B 的 Secretkubectl auth can-i get secrets \ --namespace tenant-b \ --assystem:serviceaccount:tenant-a:openclaw-agent预期返回no。如果返回yes说明有 ClusterRoleBinding 把权限放大了去查一下有没有全局绑定。4.4 验证模型调用走通最后确认 Agent 能通过 TaoToken 调通模型。在租户 A 的 Pod 里直接 curl 测试kubectl exec -n tenant-a nettest -- \ wget -qO- --timeout10 \ --headerAuthorization: Bearer $TAOTOKEN_API_KEY \ https://taotoken.net/api/v1/models能返回模型列表就说明出口网络和凭证都对了。如果超时回到 NetworkPolicy 检查 443 出口规则如果返回 401检查 Key 是否正确注入。四步全过隔离就算落地了。任何一步失败下一节的排查清单能帮你快速定位。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth多租户环境下的报错往往和单租户不一样因为多了网络策略和 RBAC 的干扰。下面是我踩过的坑按报错原文对照。5.1 401 Unauthorized{error:{message:Invalid API key provided,type:invalid_request_error}}这个在租户场景下 90% 是 Secret 没注入对。排查顺序先确认 Pod 里环境变量存在kubectl exec -n tenant-a pod -- env | grep TAOTOKEN再确认 Secret 的 key 名和 Deployment 里secretKeyRef.key完全一致大小写敏感最后确认 Key 本身没被控制台删除或过期。如果环境变量在但值不对检查是不是stringData和data混用了——data里的值必须是 base64 编码。5.2 local proxy failedError: local proxy failed: dial tcp 10.x.x.x:443: i/o timeout这是 NetworkPolicy 出口规则没放行导致的。local proxy failed说明 OpenClaw 的出口代理连不上目标。检查你的 egress 规则里有没有放行 443 端口以及 DNS 的 53 端口。特别注意如果你的集群用了 NodeLocal DNSCacheDNS 请求的目标 IP 是节点本地地址namespaceSelector那条可能匹配不到需要额外加一条针对169.254.0.0/16或节点 CIDR 的规则。5.3 reading choices 相关报错Error: reading choices: unexpected end of JSON input这个通常不是隔离问题而是模型返回体被截断或格式不对。在多租户场景下常见诱因是租户的 ResourceQuota 把内存限得太死Agent 处理大响应时 OOM 被 kill导致流式响应中断。检查kubectl describe pod有没有OOMKilled。如果是把 LimitRange 的默认内存从 2Gi 提到 4Gi或者给这个租户单独放宽配额。5.4 OAuth 相关报错Error: oauth token exchange failed: invalid_grant如果你用的是需要 OAuth 的模型 provider多租户下每个租户的 token 必须独立。常见错误是把平台侧的 OAuth 凭证共享给了所有租户导致 refresh token 互相覆盖。正确做法是每个租户在 TaoToken 侧用独立 KeyOAuth 的刷新逻辑由通道侧统一处理租户侧只持有静态 Key。这样就不会出现invalid_grant。5.5 跨租户访问尝试的告警如果你在审计日志里看到某个租户的 Pod 尝试访问另一个租户的 Service别慌这往往不是攻击而是配置错误——比如某个 Agent 的配置里硬编码了另一个租户的 endpoint。检查该租户的 ConfigMap把所有跨租户的 URL 改成自己 Namespace 内的 Service 名。NetworkPolicy 会拦住请求但日志里的告警值得清理。排查完这些你的多租户环境基本就稳了。记住一个原则任何跨租户的连通性都是 bug不是 feature。6. 长期编码与 Agent 场景的入口选择多租户隔离落地之后下一个问题是租户多了模型调用量上来了怎么管额度和成本。按量计费适合波动大的场景但如果你的租户里有长期跑编码 Agent、持续消耗 token 的按量计费月底账单会很难看。这种场景我建议看 Coding Planhttps://taotoken.net/coding-plan 它更适合持续负载额度可预期配合前面的按租户 Key 设计每个租户的消耗能单独归集。控制台 https://taotoken.net/console 里能看用量明细做月度报表直接导出。如果你还在验证阶段先用模型对话 https://taotoken.net/chat 快速测通再上集群。接入细节以文档 https://taotoken.net/doc 为准API Keys 在 https://taotoken.net/api-keys 创建。官网入口 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有完整的方案说明。最后给一个实操建议租户 Key 的命名一定要带租户标识和创建日期比如tenant-a-20260101。等你管到几十个租户的时候没有命名规范的 Key 列表就是灾难。轮换 Key 时先建新的、灰度切换、观察一周再删旧的别直接删——正在跑的 Agent 会立刻 401。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑