Kubernetes 基础体系 · 第 51/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 多租户:Namespace、RBAC、NetworkPolicy、Quota 和隔离极限
Kubernetes 多租户(multi-tenancy)不是一个单独的 API,而是把多个控制面和数据面机制组合起来,使多个团队、项目、客户或环境共享一个集群时,能够分别控制:
- 资源作用域:对象属于哪个租户;
- 谁能做什么:用户和工作负载可以访问哪些 API;
- 谁能和谁通信:Pod 之间以及 Pod 到外部的网络流量;
- 最多能消耗多少资源:CPU、内存、存储、对象数量;
- 隔离失败时的影响范围:一个租户是否能影响其他租户或整个集群。
Namespace、RBAC、NetworkPolicy 和 ResourceQuota 分别解决不同问题。它们叠加后可以形成较好的“软多租户”边界,但通常不能单独提供强安全边界,更不能自动抵御内核漏洞、容器逃逸或恶意集群管理员。
一、先建立正确的多租户模型
设一个共享集群中有租户集合:
对每个租户 ,我们通常希望满足四类条件:
-
对象作用域隔离
租户 只能管理自己的 namespaced 对象:
-
权限隔离
租户 不能执行未授权的 API 动作:
其中 是主体, 是动词, 是资源, 是 API 组和资源名称。
-
网络隔离
未被允许的 Pod 到 Pod 或 Pod 到外部流量应被拒绝:
-
资源预算
租户的实际使用量不得超过预算:
这几个条件并不等价:
- Namespace 能划分对象作用域,但不决定网络;
- RBAC 能阻止 API 操作,但不阻止已经运行的 Pod 访问网络;
- NetworkPolicy 能限制网络,但不阻止租户读取 Kubernetes API;
- ResourceQuota 能限制对象创建和资源申请,但不防止权限越权;
- 即使四者都配置正确,具有特权的 Pod 仍可能访问节点或破坏更高层边界。
因此,多租户设计首先应明确隔离目标:
| 隔离目标 | 主要机制 | 仍然依赖 |
|---|---|---|
| 团队不能修改其他团队的 Deployment | Namespace + RBAC | 管理员不授予越权 ClusterRole |
| Pod 不能跨租户访问 | NetworkPolicy | CNI 正确实现并执行策略 |
| 单个团队不能耗尽集群资源 | ResourceQuota + LimitRange | 调度器、节点容量和配额配置 |
| 租户不能创建特权容器 | Pod Security Admission 等 | Admission 配置和策略治理 |
| 对抗恶意租户或容器逃逸 | 独立集群、虚拟机、硬件和运行时边界 | 云平台或基础设施隔离 |
二、Namespace:对象作用域,不是完整安全边界
2.1 Namespace 解决什么问题
Namespace 是 Kubernetes 中对一组 namespaced 资源进行逻辑划分的对象。常见 namespaced 资源包括:
- Pod;
- Deployment、StatefulSet、DaemonSet;
- Service、Ingress;
- ConfigMap、Secret;
- ServiceAccount;
- Role、RoleBinding;
- ResourceQuota、LimitRange;
- NetworkPolicy;
- PVC。
创建一个 Namespace:
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
labels:
tenant.example.com/id: tenant-a
kubectl apply -f namespace.yaml
kubectl get namespace tenant-a --show-labels
Namespace 的核心效果是:API 请求中,许多资源需要携带 namespace,资源名称只需在该 Namespace 内唯一。
例如,tenant-a 和 tenant-b 都可以各自创建名为 api 的 Deployment:
tenant-a/api
tenant-b/api
它们不是同一个对象,因为完整标识包含:
但 Namespace 不是所有资源的作用域。以下对象是典型的集群级资源:
- Node;
- Namespace 本身;
- PersistentVolume;
- StorageClass;
- ClusterRole;
- ClusterRoleBinding;
- CustomResourceDefinition;
- Webhook 配置;
- APIService。
因此,“给租户一个 Namespace”并不等于“给租户集群管理员权限”。如果给租户绑定了能够读取或修改集群级资源的 ClusterRole,Namespace 作用域就无法挽救这个越权授权。
2.2 Namespace 对 RBAC 的含义
Role 是 namespaced 对象,通常描述某个 Namespace 内的权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-developer
namespace: tenant-a
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Role 只存在于 tenant-a。即使 Role 中写了 pods,它也只能匹配 tenant-a 中的 Pod,不能匹配 tenant-b 中的 Pod。
但是,ClusterRole 可以被 RoleBinding 引用:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-developer-binding
namespace: tenant-a
subjects:
- kind: Group
name: tenant-a-developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-developer
apiGroup: rbac.authorization.k8s.io
也可以引用 ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-view-binding
namespace: tenant-a
subjects:
- kind: Group
name: tenant-a-developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: view
apiGroup: rbac.authorization.k8s.io
这里的关键点是:
RoleBinding本身位于tenant-a;- 它把引用的 ClusterRole 权限限制在
tenant-a; - 这和
ClusterRoleBinding不同; - 如果改成 ClusterRoleBinding,权限通常会作用于整个集群,风险显著扩大。
2.3 Namespace 标签也是安全输入
NetworkPolicy 可以根据 Namespace 标签选择通信来源,因此 Namespace 标签不是普通装饰信息。
例如:
namespaceSelector:
matchLabels:
tenant.example.com/id: tenant-a
如果一个租户能够修改 Namespace 标签,它可能把自己的 Namespace 改成受信任标签,从而满足其他租户 NetworkPolicy 的选择条件。因此:
- 租户开发者通常不应拥有
update namespaces; - Namespace 创建和标签变更应由平台控制器或管理员完成;
- 不能把“标签值正确”当成不可伪造的身份认证,除非有专门的准入策略保护它。
2.4 Namespace 删除是异步过程
删除 Namespace 不是立即删除目录,而是一个控制器驱动的清理流程:
kubectl delete namespace tenant-a
kubectl get namespace tenant-a
常见状态变化是:
Active -> Terminating -> 删除完成
在 Terminating 期间,Namespace 控制器会发现并清理其中的 namespaced 对象。若某个对象或 API 资源无法删除,Namespace 可能长时间停留在 Terminating。
常见原因包括:
- 对象带有无法执行的 finalizer;
- 某个 CRD 被删除后,剩余自定义资源无法被正确发现;
- 某个聚合 API 服务不可用;
- 控制器或 webhook 阻塞了删除请求。
排查:
kubectl describe namespace tenant-a
kubectl get namespace tenant-a -o json
kubectl api-resources --verbs=list --namespaced -o name
强制移除 Namespace finalizer 是高风险操作。它可能使 Namespace 对象消失,但遗留资源、外部云资源或控制器状态未必已经清理。生产环境应先确定阻塞者和残留资源,再决定是否处理 finalizer,而不是把强制 finalize 当作常规删除命令。
三、RBAC:控制 Kubernetes API,不控制所有运行时行为
3.1 RBAC 的授权输入
Kubernetes 请求通常经过以下逻辑:
flowchart LR
A[客户端或 Pod] --> B[认证 Authentication]
B --> C[授权 Authorization]
C --> D[准入 Admission]
D --> E[API Server 持久化]
E --> F[控制器/调度器/节点执行]
各阶段职责不同:
- 认证:请求者是谁,例如用户、组、ServiceAccount;
- 授权:请求者能否对某个资源执行某个动作;
- 准入:对象是否满足额外策略,例如 Pod Security、ResourceQuota、ValidatingAdmissionPolicy;
- 持久化和执行:API Server 保存对象,控制器和 kubelet 进一步执行。
RBAC 授权规则通常由以下字段匹配:
apiGroups:API 组,核心 API 组写成"";resources:资源名称,例如pods、secrets;resourceNames:可选,限定具体对象名;verbs:动作,例如get、list、watch、create、update、patch、delete;- 作用域:Role 绑定到某个 Namespace,ClusterRoleBinding 则可作用于集群范围。
RBAC 默认是“无匹配即拒绝”,但授权规则是累加的,没有显式 deny 规则。用户通过多个 RoleBinding 获得的权限会并集化。
3.2 最小权限示例
为租户创建一个只能管理自己 Namespace 中应用对象的角色:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-app-editor
namespace: tenant-a
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
将权限绑定给用户组:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-app-editor-binding
namespace: tenant-a
subjects:
- kind: Group
name: tenant-a-developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-app-editor
apiGroup: rbac.authorization.k8s.io
应用和验证:
kubectl apply -f tenant-a-rbac.yaml
kubectl auth can-i create deployments \
--namespace tenant-a \
--as alice \
--as-group tenant-a-developers
# 预期:yes
kubectl auth can-i get secrets \
--namespace tenant-a \
--as alice \
--as-group tenant-a-developers
# 预期:no
kubectl auth can-i delete deployments \
--namespace tenant-b \
--as alice \
--as-group tenant-a-developers
# 预期:no
kubectl auth can-i 只验证授权判断,不会替代真实 API 请求验证,也不检查后续 Admission、Webhook、ResourceQuota 或 NetworkPolicy。
3.3 Secret 权限需要单独审慎处理
能够读取 Secret 的主体通常可以获得应用凭据、数据库密码、TLS 私钥或云平台访问令牌。因此不应因为“用户能查看 Pod”就顺便授予:
resources: ["secrets"]
verbs: ["get", "list", "watch"]
还要注意,Pod 的创建权限本身可能产生间接能力。例如,若用户可以在某个 Namespace 创建 Pod,并能引用该 Namespace 中的 Secret,那么即使用户没有直接 get secret 权限,工作负载也可能把 Secret 读取出来并暴露给用户。更危险的是,如果用户可以创建带任意 ServiceAccount 的 Pod,就可能继承该 ServiceAccount 的权限。
因此,评估 RBAC 时不能只查看显式的 secrets/get,还要检查:
- 是否能创建 Pod;
- 是否能指定任意
serviceAccountName; - 是否能创建 RoleBinding;
- 是否能修改现有高权限 Pod 或 Deployment;
- 是否能创建能访问节点或云元数据的工作负载。
3.4 不能把“只读”理解成“低风险”
内置 view、edit、admin ClusterRole 方便快速授权,但它们适合经过审查后使用,不应自动等价为租户模型。
例如:
- 能读取 ConfigMap 可能获取配置中的敏感信息;
- 能创建 Deployment 通常意味着能运行任意镜像;
- 能修改 Deployment 的 Pod 模板,通常意味着能指定任意容器参数;
- 能管理 RoleBinding 可能把自己提升为管理员;
- 能读取某些自定义资源,可能泄露租户信息。
RBAC 权限应按工作流拆分,而不是仅以“开发者”“运维者”这种职位名称授权。
四、ResourceQuota 和 LimitRange:预算与默认值是两件事
4.1 ResourceQuota 的语义
ResourceQuota 是 Namespace 级别的配额对象。它在 API 请求的准入阶段检查该 Namespace 的资源使用量和对象数量。
一个基础配额:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
requests.storage: 100Gi
persistentvolumeclaims: "10"
pods: "50"
services: "20"
services.loadbalancers: "2"
查看配额和当前使用量:
kubectl apply -f quota.yaml
kubectl get resourcequota -n tenant-a
kubectl describe resourcequota tenant-a-quota -n tenant-a
典型结果类似:
Resource Used Hard
-------- ---- ----
limits.cpu 2 8
limits.memory 4Gi 16Gi
pods 3 50
requests.cpu 1 4
requests.memory 2Gi 8Gi
requests.storage 20Gi 100Gi
其中:
Used是当前被配额统计的使用量;Hard是上限;requests.cpu和requests.memory统计调度请求;limits.cpu和limits.memory统计容器限制;pods、services等统计对象数量;requests.storage通常统计 PVC 的存储请求,而不是实际磁盘写入量。
4.2 配额的计算过程
假设 Namespace 中有两个 Pod:
Pod p1:
requests.cpu = 1
limits.cpu = 2
Pod p2:
requests.cpu = 2
limits.cpu = 3
则:
若配额为:
requests.cpu = 4
limits.cpu = 8
再创建一个:
requests.cpu = 2
limits.cpu = 2
API Server 会计算预计值:
因此创建请求被拒绝。此时 Pod 不会进入 Pending;它通常根本不会被持久化,客户端会收到类似:
exceeded quota: tenant-a-quota, requested: requests.cpu=2,
used: requests.cpu=3, limited: requests.cpu=4
这是“准入拒绝”,不是调度失败。
对比而言,如果配额允许,但集群没有足够可调度节点,Pod 可能创建成功后处于 Pending。两者诊断路径不同:
kubectl get pod -n tenant-a
kubectl describe pod <pod-name> -n tenant-a
- 出现
exceeded quota:检查 ResourceQuota; - 出现
Insufficient cpu、Insufficient memory:检查调度和节点资源; - 出现镜像拉取失败:检查 Registry、凭据和网络。
4.3 配额不等于节点预留
如果租户配额为:
requests.cpu = 4
它表示该 Namespace 最多可以声明 4 个 CPU 的调度请求,不表示集群已经为它预留了 4 个 CPU。
因此:
- 配额控制的是允许提交的总量;
- requests 影响调度器的放置决策;
- 节点是否有可用容量仍由调度器决定;
- 多个 Namespace 的 requests 可能共同竞争同一批节点。
如果需要保证租户的最低资源或避免租户之间的节点级竞争,通常还要组合:
- 节点池或专用节点;
- taint/toleration;
- node affinity;
- PriorityClass;
- 集群级调度策略;
- 云平台或虚拟机隔离。
ResourceQuota 本身不会给租户提供 CPU 性能保证,也不会阻止节点内核资源争用。
4.4 LimitRange 的职责:默认值和单对象约束
LimitRange 作用于 Namespace 内的单个 Pod、容器或 PVC,可以设置默认 requests/limits,也可以设置最小和最大值:
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-a-limits
namespace: tenant-a
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
cpu: 500m
memory: 512Mi
min:
cpu: 10m
memory: 16Mi
max:
cpu: "2"
memory: 4Gi
对于一个没有资源字段的容器:
containers:
- name: app
image: example/app:1.0
如果 LimitRange 设置了默认值,准入阶段可能把它补成:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
于是它既能被调度器合理处理,也能满足基于 requests.* 或 limits.* 的配额统计。
两者的关系可以形式化为:
-
LimitRange 作用于单个对象:
-
ResourceQuota 作用于 Namespace 总量:
因此:
- LimitRange 防止单个容器过大或没有默认资源;
- ResourceQuota 防止整个 Namespace 累积过多资源;
- LimitRange 不能限制租户创建多少个 Pod;
- ResourceQuota 不能把单个容器限制在合理范围内。
4.5 ResourceQuota 和 LimitRange 的顺序影响错误表现
假设配额包含:
hard:
requests.cpu: "4"
但 Namespace 没有 LimitRange,用户创建:
containers:
- name: app
image: example/app:1.0
在需要 requests.cpu 才能纳入配额统计的场景中,请求可能被拒绝,错误通常要求显式声明 CPU request。配置 LimitRange 后,默认 request 被注入,之后才进行配额检查。
所以一个可运行的租户 Namespace 通常至少需要:
- ResourceQuota;
- LimitRange;
- 明确要求工作负载声明 requests 和 limits,或提供可靠默认值。
默认值不能随意设置。若默认 requests.memory 过大,小型服务会快速消耗配额;若默认值过小,调度、QoS 和 OOM 风险又会变差。
五、NetworkPolicy:基于 Pod 的 L3/L4 流量允许模型
5.1 NetworkPolicy 的核心对象
NetworkPolicy 使用 podSelector 选择被保护的 Pod,并分别描述:
policyTypes: [Ingress]:入站策略;policyTypes: [Egress]:出站策略;ingress:哪些来源可以连接到该 Pod;egress:该 Pod 可以连接到哪些目的地;ports:允许的协议和端口。
一个重要规则是:同一方向的多个 NetworkPolicy 是累加允许关系,不是最后一条覆盖前一条。
对某个 Pod :
Egress 也同理。NetworkPolicy 没有通用的显式 deny 规则;要实现默认拒绝,通常要创建选择所有 Pod、但没有 allow 条目的策略。
5.2 默认拒绝和同 Namespace 访问
以下策略拒绝 tenant-a 中所有 Pod 的入站和出站流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
podSelector: {} 表示选择该 Namespace 中的所有 Pod。
然后允许同一 Namespace 内的 Pod 互相访问:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-namespace
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {}
egress:
- to:
- podSelector: {}
这里必须分别写 ingress 和 egress。只允许入站并不会自动允许出站,反之亦然。
5.3 NamespaceSelector 与 PodSelector 的组合语义
下面的规则只允许来自 tenant-a 中标签为 role: frontend 的 Pod:
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant.example.com/id: tenant-a
podSelector:
matchLabels:
role: frontend
namespaceSelector 和 podSelector 写在同一个数组元素中,语义是逻辑 AND:
如果拆成两个元素:
from:
- namespaceSelector:
matchLabels:
tenant.example.com/id: tenant-a
- podSelector:
matchLabels:
role: frontend
语义变成逻辑 OR:
后者会放宽范围,可能允许其他 Namespace 中同样带有 role: frontend 的 Pod,或者允许当前 Namespace 中所有 Pod。这个缩进和列表层级是 NetworkPolicy 中最容易造成安全误配的地方之一。
5.4 一个可运行的前后端示例
假设 tenant-a 有:
frontendPod,标签app: frontend;apiPod,标签app: api;- CoreDNS 位于
kube-system,DNS Pod 标签假设为k8s-app: kube-dns。
允许 frontend 访问 api 的 8080 端口:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: tenant-a
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
允许 api 访问 DNS:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-dns
namespace: tenant-a
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
使用前必须检查实际 DNS 标签:
kubectl get pods -n kube-system --show-labels
kubectl get svc -n kube-system
不同发行版、云厂商或集群安装方式可能使用不同的 DNS 标签和 Namespace。NetworkPolicy 只能引用实际存在的标签,不能假设所有集群都使用相同标签。
验证:
kubectl run frontend \
-n tenant-a \
--image=curlimages/curl \
--labels=app=frontend \
-- sleep 3600
kubectl run outsider \
-n tenant-b \
--image=curlimages/curl \
-- sleep 3600
kubectl exec -n tenant-a frontend -- \
curl --connect-timeout 3 http://api.tenant-a.svc.cluster.local:8080
kubectl exec -n tenant-b outsider -- \
curl --connect-timeout 3 http://api.tenant-a.svc.cluster.local:8080
预期是前者允许、后者超时或被拒绝。但能否真正得到这个结果,取决于 CNI 是否实现 NetworkPolicy,以及 Service、DNS 和网络路径是否符合该实现。
5.5 NetworkPolicy 的前置条件和边界
Kubernetes API 定义 NetworkPolicy 对象,但实际流量拦截由网络插件实现。若 CNI 不支持 NetworkPolicy,策略对象可能能够创建、也能在 kubectl get networkpolicy 中看到,但不会产生预期的阻断效果。
因此不能用以下结果证明策略生效:
kubectl get networkpolicy -A
它只证明对象存在。必须进行实际连通性测试,并确认 CNI 的实现文档和状态。
NetworkPolicy 通常是基于 IP、协议和端口的 L3/L4 策略,不是完整的身份代理或 L7 防火墙。它一般不能直接表达:
- HTTP 方法;
- URL 路径;
- JWT 用户身份;
- SQL 语句;
- 应用层租户关系。
这些要求需要服务网格、反向代理、应用鉴权或专门的 L7 网络产品。
还存在以下边界:
hostNetwork: true的 Pod 使用节点网络命名空间,行为与普通 Pod 不同;- 访问节点本地服务、主机网络或云平台元数据的风险不能只靠普通 Pod 间策略解决;
- 某些 CNI 对 hostNetwork、外部 IP、NodePort、LoadBalancer、加密隧道等路径有实现差异;
- 网络策略不能撤销已经通过 Kubernetes API 获取的 Secret;
- 只限制 Egress 可能仍无法阻止通过允许的 DNS、HTTPS 或应用出口进行数据外传;
- DNS 被默认拒绝后,使用 Service 名称访问服务通常会失败,即使目标端口策略正确。
5.6 Service、Ingress 和 NetworkPolicy 的关系
Service 是稳定的虚拟访问入口,NetworkPolicy 通常最终仍然作用于后端 Pod。创建一个 Service 并不会自动允许网络访问。
例如:
apiVersion: v1
kind: Service
metadata:
name: api
namespace: tenant-a
spec:
selector:
app: api
ports:
- port: 8080
targetPort: 8080
客户端访问:
api.tenant-a.svc.cluster.local:8080
要成功,至少需要同时满足:
- DNS 查询被允许;
- 客户端 Pod 的 Egress 被允许;
- api Pod 的 Ingress 允许客户端;
- Service selector 确实选中了后端;
- 后端进程监听的是目标端口;
- CNI 正确执行策略。
因此“Service 存在但连接超时”不能直接归因于 Service。应按 DNS、Endpoint、客户端出站、服务端入站、应用监听顺序排查:
kubectl get svc,endpointslices -n tenant-a
kubectl describe networkpolicy -n tenant-a
kubectl exec -n tenant-a <client> -- nslookup api.tenant-a.svc.cluster.local
kubectl exec -n tenant-a <client> -- curl -v --connect-timeout 3 http://api:8080
六、一个租户基线的完整组合
下面把 Namespace、LimitRange、ResourceQuota、RBAC 和默认网络策略组合起来。它不是所有生产环境的最终模板,但可以作为可执行的基础实验。
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
labels:
tenant.example.com/id: tenant-a
---
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-a-container-defaults
namespace: tenant-a
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
cpu: 500m
memory: 512Mi
min:
cpu: 10m
memory: 16Mi
max:
cpu: "2"
memory: 4Gi
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-budget
namespace: tenant-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "50"
persistentvolumeclaims: "10"
requests.storage: 100Gi
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-a-app-editor
namespace: tenant-a
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-a-app-editor
namespace: tenant-a
subjects:
- kind: Group
name: tenant-a-developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-a-app-editor
apiGroup: rbac.authorization.k8s.io
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: tenant-a-default-deny
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: tenant-a-allow-same-namespace
namespace: tenant-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {}
egress:
- to:
- podSelector: {}
应用:
kubectl apply -f tenant-a-baseline.yaml
kubectl get ns,limitrange,resourcequota,role,rolebinding,networkpolicy -n tenant-a
这里有一个重要的生命周期问题:如果先应用 ResourceQuota,再创建没有资源规格的 Pod,Pod 可能被配额拒绝;如果 LimitRange 能在准入阶段注入默认值,Pod 才能进入后续配额检查。实际排错时应查看:
kubectl get events -n tenant-a --sort-by=.lastTimestamp
如果一个应用需要访问 DNS、镜像仓库、数据库或监控系统,还必须显式增加对应 Egress 规则。默认拒绝策略不能直接照搬到所有系统,否则应用会以“DNS 失败”“连接超时”或“探针失败”的形式中断。
七、Pod Security 是 Namespace 多租户中不可缺少的前置控制
只配置 RBAC、NetworkPolicy 和 Quota,仍然可能允许租户创建高风险 Pod。例如:
spec:
hostNetwork: true
hostPID: true
containers:
- name: privileged
image: example/debug
securityContext:
privileged: true
这类能力可能使 Pod 接触节点网络、进程或设备。即使租户无法读取 Node 对象,它仍可能通过工作负载间接影响节点。
Kubernetes 内置的 Pod Security Admission 使用 Namespace 标签选择基线,例如:
kubectl label namespace tenant-a \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted \
--overwrite
restricted 的具体要求随 Kubernetes 版本的 Pod Security Standards 定义变化,应以目标集群版本文档为准。它通常会限制:
- 特权容器;
- host namespace;
- 不安全的 capabilities;
- 不安全的卷和用户设置;
- 容器权限提升。
这不是 RBAC 的替代品:
- RBAC 决定谁能提交对象;
- Pod Security Admission 决定 Pod 安全上下文是否满足策略;
- NetworkPolicy 决定网络流量;
- Quota 决定资源预算。
如果允许租户修改 Namespace 上的 Pod Security 标签,租户就可能修改自己的强制策略,所以这些标签同样应由平台侧管理。
八、隔离极限:Namespace 何时不够
8.1 共享 Namespace 适合什么场景
Namespace 多租户通常适合:
- 同一组织内部的团队;
- 开发、测试、预发布环境;
- 彼此具有一定信任关系的应用;
- 平台希望统一升级、监控和调度的工作负载。
它的优势是成本低、运维简单、资源共享充分。缺点是所有租户共享:
- API Server 和控制器;
- etcd;
- 节点内核;
- 容器运行时;
- CNI;
- 部分集群级控制面;
- 调度和节点资源。
8.2 不适合共享 Namespace 的场景
如果租户之间互不信任,或者租户有能力运行任意代码,应谨慎使用单集群 Namespace 作为唯一边界。以下需求通常需要更强的隔离:
- 对抗恶意租户;
- 处理高敏感数据;
- 必须满足严格合规边界;
- 允许自定义 CNI、CSI、Admission Webhook 或 CRD;
- 允许特权容器、hostPath、hostNetwork;
- 需要避免一个租户影响控制面可用性;
- 需要独立升级和故障域。
常见的增强路径从弱到强大致是:
- Namespace;
- Namespace + RBAC + Quota + NetworkPolicy + Pod Security;
- 专用节点池、taint、节点亲和性和独立存储策略;
- 虚拟集群或强隔离运行时;
- 独立 Kubernetes 集群;
- 独立云账号、项目、网络和基础设施。
不能把这个顺序理解成绝对等级。专用节点能降低资源和故障干扰,却不自动防止控制面越权;独立集群能减少控制面共享,却仍可能共享云账号、镜像仓库或网络。
8.3 “租户可以创建 Pod”是一个能力放大器
允许创建 Pod 往往意味着租户可以选择:
- 镜像;
- 环境变量;
- Volume;
- ServiceAccount;
- 网络模式;
- 安全上下文;
- 应用命令。
因此,RBAC 中的 create pods、create deployments 不应只按“对象写权限”理解,而应按“能运行什么代码”理解。
一个租户若同时拥有以下组合,风险会显著上升:
create pods
+ 使用高权限 ServiceAccount
+ 允许 hostPath 或 privileged
+ 能修改 RoleBinding
这可能演化成 Namespace 管理权限、节点访问甚至集群级权限。生产策略应同时审查 RBAC 和准入策略,不能只看 Role YAML。
九、常见误解与失败路径
误解一:Namespace 能阻止跨 Namespace 访问
错误。Namespace 主要划分 API 对象作用域,默认并不提供网络隔离。没有 NetworkPolicy 时,Pod 之间通常可以通过 Pod IP 或 Service 互相通信,具体还取决于网络插件和集群配置。
误解二:NetworkPolicy 能阻止所有越权
错误。它主要控制网络连接,不能阻止:
- 通过 RBAC 允许的 API 请求;
- Secret 被合法挂载到 Pod 后的读取;
- 容器逃逸;
- 节点级访问;
- 应用层认证缺陷;
- 允许的出口协议中的数据外传。
误解三:配额到了,节点就安全了
错误。ResourceQuota 只限制被配额统计的对象和资源使用量,不保证节点性能、不限制所有内核资源,也不自动保证公平调度。
误解四:限制了 CPU limit 就不会抢 CPU
错误。CPU limit 通常由 cgroups 执行,但实际性能还受 requests、节点超卖、CPU 管理策略、内核和运行时影响。CPU limit 不是独占 CPU。
误解五:看到 NetworkPolicy 对象就说明网络已经隔离
错误。必须确认:
kubectl get pods -A -o wide
kubectl get networkpolicy -A
kubectl get events -A
并使用测试 Pod 验证允许和拒绝路径。还要确认 CNI 插件支持并启用了 NetworkPolicy。Kubernetes API Server 不会替网络插件完成数据包过滤。
误解六:删除 Namespace 就一定能清理外部资源
错误。云盘、负载均衡器、DNS 记录和外部数据库可能由控制器通过 finalizer 管理。控制器不可用、凭据失效或 finalizer 异常时,Namespace 可能卡住,或者强制删除后留下外部资源。
十、生产诊断顺序
10.1 权限被拒绝
先确认请求身份:
kubectl auth whoami
再检查授权:
kubectl auth can-i get pods -n tenant-a
kubectl auth can-i create deployments -n tenant-b
kubectl auth can-i '*' '*' --all-namespaces
若 can-i 返回 no,检查:
kubectl get role,rolebinding -n tenant-a
kubectl get clusterrole,clusterrolebinding
kubectl describe rolebinding <name> -n tenant-a
重点确认:
- RoleBinding 的 Namespace;
- subject 的用户名和组名是否与认证系统一致;
roleRef是否指向预期角色;- 是否误用了 ClusterRoleBinding;
- 请求的 API group 和资源名称是否正确;
- 子资源是否单独授权,例如
pods/log、deployments/scale。
10.2 Pod 创建失败
kubectl describe pod <pod> -n tenant-a
kubectl get events -n tenant-a --sort-by=.lastTimestamp
kubectl describe resourcequota -n tenant-a
kubectl describe limitrange -n tenant-a
按错误类型区分:
exceeded quota:配额总量或对象数量;must specify requests...:缺少资源声明,且没有默认值;must be less than or equal to max:超过 LimitRange 单对象上限;Insufficient cpu/memory:调度容量;violates PodSecurity:安全准入策略;forbidden:RBAC 或其他准入拒绝。
10.3 网络连接失败
先验证名称解析:
kubectl exec -n tenant-a <pod> -- nslookup api.tenant-a.svc.cluster.local
再验证 Service 和后端:
kubectl get svc api -n tenant-a
kubectl get endpointslice -n tenant-a \
-l kubernetes.io/service-name=api
最后检查策略选择器:
kubectl get networkpolicy -n tenant-a -o yaml
kubectl get pod -n tenant-a --show-labels
kubectl get namespace --show-labels
要特别检查:
- 目标 Pod 是否被
podSelector选中; - 来源 Namespace 是否有正确标签;
ingress和egress是否都配置;- 端口是 Service port 还是容器 targetPort;
- DNS 的 Namespace 和 Pod 标签是否正确;
- CNI 是否真的执行 NetworkPolicy;
- 是否使用了 hostNetwork、NodePort 或外部出口等特殊路径。
十一、如何验证租户边界,而不是只检查 YAML
多租户安全需要做负向测试。假设:
- 用户
alice属于tenant-a-developers; - 用户
alice不应访问tenant-b; tenant-a中的 frontend 可以访问 api;tenant-b中的 Pod 不可以访问 tenant-a 的 api。
可以建立测试矩阵:
| 测试 | 预期 |
|---|---|
| Alice 在 tenant-a 创建 Deployment | 允许 |
| Alice 在 tenant-b 删除 Pod | 拒绝 |
| Alice 读取 tenant-a Secret | 按设计决定,通常最小化 |
| tenant-a frontend -> tenant-a api:8080 | 允许 |
| tenant-b Pod -> tenant-a api:8080 | 拒绝 |
| tenant-a Pod -> 未允许的外部地址 | 拒绝或按出口策略 |
| tenant-a 创建超过配额的 Pod | 拒绝 |
| tenant-a 创建超过 LimitRange 最大值的容器 | 拒绝 |
| tenant-a 创建 privileged Pod | 按 Pod Security 策略拒绝 |
其中,RBAC 的负向测试可以用 kubectl auth can-i,Quota 和 Admission 必须实际提交对象,NetworkPolicy 必须从测试 Pod 发起真实连接。测试结果还应记录集群版本、CNI、运行时和云厂商网络实现,因为这些组件会影响行为。
十二、最终边界:四个机制各自保证什么
可以把这四个核心机制的责任边界总结为:
Namespace
-> “对象归属在哪里”
RBAC
-> “谁可以通过 Kubernetes API 做什么”
NetworkPolicy
-> “哪些被选中的 Pod 之间允许哪些网络流量”
ResourceQuota / LimitRange
-> “Namespace 总共能申请多少,单个对象允许多大,缺省值是什么”
它们不会自动提供以下能力:
- Namespace 之间的硬件隔离;
- 防止节点内核漏洞;
- 防止容器逃逸;
- 应用层身份认证和授权;
- 自动保证资源性能;
- 自动发现所有数据外传路径;
- 自动清理外部云资源;
- 自动保护集群级资源;
- 自动阻止租户利用高权限 ServiceAccount 提权。
一个合理的共享集群租户基线,通常是:
如果租户之间需要对抗性隔离,则应进一步引入专用节点、隔离运行时、独立控制面或独立集群。Namespace 是 Kubernetes 多租户的组织基础,但它的隔离极限取决于整个控制面、准入、网络、节点和基础设施边界,而不是 Namespace 对象本身。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 供应链安全:镜像签名、SBOM、扫描、准入和来源验证
- 下一篇:Kubernetes 审计日志:Policy、Stage、Backend、性能和合规留存
- 延伸:Kubernetes Namespace:作用域、资源配额、权限、多租户和删除
- 延伸:ResourceQuota 与 LimitRange:租户预算、默认值、对象数量和治理
- 延伸:Kubernetes NetworkPolicy:Selector、Ingress、Egress、默认拒绝和验证
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论