一个 namespace 一个租户,隔离就足够了吗

不够。租户 A 与 B 在同一隔离实验集群内分别运行累计 frontend → orders → inventory 服务,可以复用同一合成 SKU、请求 ID 前缀和库存账本,但四类边界仍需分开证明:谁能提交规则(Kubernetes RBAC)、规则可见及附着到谁(Istio/Gateway API 作用域)、业务请求能否越权(数据面认证授权),以及共享控制面/waypoint/ztunnel 被租户 A 占满时租户 B 的可用性。前面认证与授权章节讨论的执行点不能由“两个 namespace”替代,HTTPRoute 的同 namespace 生产者规则还可能作用于所有调用该 Service 的来源。

租户 API 写权限、规则目标与共享代理容量是三条不同边界;底部业务账本验证实际影响

Namespace 给资源命名空间,不自动为所有业务流量隔离网络,不自动防止错误的全局策略,也不为共享 waypoint 建立硬性的按租户容量限额。RBAC 能禁止 A 修改 B 的资源,却无法据此推出 A 对 B Service 的 HTTP 请求一定会被拒绝;相反,数据面策略即使拒绝了 A,也不能阻止 A 以被授予的 API 权限提交错误配置。要识别依赖关系,至少记录 namespace、ServiceAccount、Route/Policy 的创建主体与目标引用、实际代理状态以及同一请求 ID 的客户端/库存日志。

三张权限表互相不能替代

要回答的问题 验证点 正例与负例
A 可改 B 的 Route/Policy 吗 RBAC 授权、实际 API 请求或 auth can-i,含服务账号和 namespace A 只能改 A;跨 B 修改被拒并留下非零退出
A 的规则会影响 B 的调用吗 生产者/消费者 Route 附着、Istio 配置可见性、代理当前路由 固定来源 namespace/请求头,比较 B 的库存版本;错误目标规则不得悄悄越界
A 能访问 B 的库存写接口吗 工作负载身份、目标入站授权、网络强制范围 B 自有请求允许,A 的错误身份请求拒绝且 B 账本不新增
A 的负载会拖慢 B 吗 共享 waypoint/ztunnel/Istiod 的 CPU、连接与队列 B 正常基线与 A 人工受控高负载期对照;超过门禁即失败

不要把正例“B 业务 200”当作 RBAC 测试结果;也不要把 API Forbidden 的错误响应当作应用拒绝。资源可见性由 Istio 配置范围、Gateway API 引用规则和所选版本共同决定;跨 namespace 后端引用及 ReferenceGrant 的作用需要逐对象核对,不能把允许引用解释为获得任意策略编辑权。共享 waypoint 的处理资源与指标标签基数也需计入租户成本,在未采集前不写任何隔离性能数字。

在隔离集群复跑的次序

先在 examples/service-mesh/ 只读检查具名 context 与合成 namespace,检查失败就立即停止:

1
SM_CONTEXT=service-mesh-lab SM_NAMESPACE=service-mesh-lab bash scripts/check-02.sh

再在明确不包含真实业务的集群准备 A/B 两组累积应用与不同 ServiceAccount,固定 sku=paper,请求 ID 包含租户及尝试号,合成写接口仅使用实验账本。先保存两个租户独立成功调用及当前配置;逐层构造负例:以 A 的测试凭据尝试修改 B 规则;保留两条 Route 分别从 A/B 访问 B;让 A 的错误身份访问 B 库存并确认 B 账本无新记录;在受限额度内增加 A 负载,监测 B 的错误率与延迟。每例保存资源、策略、代理实际执行点、客户端退出码、应用端结果;应拒却放行或者污染账本时断言非零。不得靠配成空 EndpointSlice 人为拖垮他人服务,也不试图打满共享生产网关。

尚未创建租户环境、网格代理或权限绑定;RBAC、配置可见、跨租户流量和资源压力均 NOT_RUN。两个进程或两个 namespace 的静态 YAML 不能替代真实隔离验证。

namespace、身份和网络策略各限制什么

本例在 sidecar 集群中新建 tenant-a、tenant-b 两个实验 namespace,应用仍是累计工程的相同镜像、端口与 ServiceAccount。mainline/31/run.sh apply 若发现任一同名 namespace 已存在就拒绝,避免把实验资源覆盖到已有租户;清理也要求本章所有权标签。镜像须先由 setup-kind.sh sidecar 载入该 kind 集群。

1
2
SM_CONTEXT=kind-service-mesh-sidecar bash mainline/31/run.sh apply
SM_CONTEXT=kind-service-mesh-sidecar bash mainline/31/run.sh capture

isolation.yaml 的 Role/RoleBinding 只允许 tenant-a 的 orders ServiceAccount 修改本 namespace 的路由和授权对象。它约束 Kubernetes API 操作,不控制业务 TCP;kubectl auth can-i 的跨 namespace 负例必须为 no。Role 可编辑授权意味着租户能放宽自身授权,是否允许这一权限需由平台设计决定,不能把该 Role 当作不可绕过的全局安全基线。

tenant-b 的库存使用 STRICT 加 L7 ALLOW,仅允许 cluster.local/ns/tenant-b/sa/orders 的 GET /quote。即使两租户的 ServiceAccount 都叫 orders,它们的完整主体也不同。另有 NetworkPolicy 只允许来自 tenant-b namespace 且具有 app: orders 标签的 Pod 访问库存 8080;两个 selector 放在同一个 from 条目里表示 AND,拆成两个条目会扩大为 OR。

1
2
3
4
5
from:
- namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: tenant-b}
podSelector:
matchLabels: {app: orders}

网络策略依赖 CNI 实现。kind 的默认网络环境不能仅凭 API 接受对象就宣称执行了 NetworkPolicy;应确认支持的网络插件并用跨租户 TCP 实测。NetworkPolicy 也不验证 SPIFFE 主体与 HTTP 路径,namespace 内有创建 Pod 权限者可能使用相同 label;网格授权补上这一层语义。本章没有把 L7 selector 策略搬到 ambient,ambient 应使用第 27 篇 targetRefs/waypoint 方案。

用下列探针模板分别在 tenant-b 和 tenant-a 创建同名但不同 namespace 的 orders 身份。它们带 app: orders,避免网络层标签差异干扰身份对照。先创建后等 Ready,再发请求;模板中的 namespace 由 kubectl 的 -n 指定。

1
2
3
4
5
6
kubectl --context kind-service-mesh-sidecar -n tenant-b run tenant-probe --image=curlimages/curl:8.12.1 --labels=app=orders --overrides='{"spec":{"serviceAccountName":"orders"}}' --command -- sleep 600
kubectl --context kind-service-mesh-sidecar -n tenant-a run tenant-probe --image=curlimages/curl:8.12.1 --labels=app=orders --overrides='{"spec":{"serviceAccountName":"orders"}}' --command -- sleep 600
kubectl --context kind-service-mesh-sidecar -n tenant-b wait --for=condition=Ready pod/tenant-probe --timeout=120s
kubectl --context kind-service-mesh-sidecar -n tenant-a wait --for=condition=Ready pod/tenant-probe --timeout=120s
kubectl --context kind-service-mesh-sidecar -n tenant-b exec tenant-probe -c tenant-probe -- curl -sv --max-time 5 -H 'x-request-id: tenant-own' 'http://inventory.tenant-b:8080/quote?sku=paper'
kubectl --context kind-service-mesh-sidecar -n tenant-a exec tenant-probe -c tenant-probe -- curl -sv --max-time 5 -H 'x-request-id: tenant-other' 'http://inventory.tenant-b:8080/quote?sku=paper'

正常情况下本租户成功、跨租户失败。为了分辨哪一层拒绝,可以只在隔离实验中暂时删除 NetworkPolicy,重发跨租户请求:网格主体授权仍应拒绝;重新应用 isolation.yaml 恢复。再用本租户 POST 请求检查 L7 拒绝,结合 inventory 应用缺失该 ID 与 proxy 拒绝日志定位,不能把所有失败都归因于 NetworkPolicy。

1
SM_CONTEXT=kind-service-mesh-sidecar bash mainline/31/run.sh cleanup

当前集群和 CNI 策略执行为 NOT_RUN。原则是分别验证 API 操作权限、网络来源约束和业务主体权限;它们可以互补,不能互相替代。Kubernetes NetworkPolicy 官方定义 与 Istio 1.27 授权策略生成源码 提供核对依据。

练习与参考解答

  1. 租户 A 不能编辑 B 的 AuthorizationPolicy,是否已证明 A 无法访问 B 库存?参考解答:没有。RBAC 只限制资源 API;还需从 A 的 ServiceAccount 发真实请求、核对 B 的入站策略与代理拒绝位置、B 应用日志与账本。配置误附着或默认允许会使业务通路与 API 权限分离。
  2. B 的正常读请求一直成功,把 A 的同一服务调用并发提高后 B 出现尾延迟,怎样判别这是共享代理压力而不是 B 自己的慢依赖?参考解答:对照 A 负载前/期间/停止后的 B 请求 ID 与库存/外部 stub 日志,读取 waypoint/ztunnel 的 CPU、队列和连接计数及 B 自身耗时;改变 A 负载保持 B 输入不变,若现象跟随共享代理负载而非应用处理时间变化,才有证据定位资源竞争;未完成测量不可宣称因果。

官方参考与系列导航

系列导航:12 Gateway API 网格路由 → 30 升级与回退 → 31 多租户边界(本文)→ 32 多集群。