企业多部门共用K8s集群如何防止越权访问 Kubernetes多租户Namespace隔离与资源配额落地方案
想象一下你们公司的办公大楼:整栋楼只有一套电力和网络基础设施,但研发、财务、运维、市场各有独立楼层。如果门禁卡不分等级,任何人拿张总卡就能推开财务室的门翻报表,那肯定乱套。Kubernetes 集群也是一样的道理。很多团队以为建了几个 Namespace 就实现了多租户,实际上默认情况下,Namespace 只是“文件夹不同”,并不是真正的墙。一个配错权限的 ServiceAccount、一条手滑的 ClusterRoleBinding,或者一个内存泄漏的 Java 进程,都足以让别的部门跟着遭殃。
多租户落地的核心其实就三句话:认对人、关好门、分好蛋糕。下面我把这套东西拆成能直接拿去用的方案,配置尽量贴近真实生产,遇到容易踩的坑也会一并标出来。
先解决“谁在操作”:身份边界与 RBAC 收紧
K8s 的权限模型是 RBAC(基于角色的访问控制)。多部门场景下,最忌讳的就是大家共用一个 kubeconfig,或者 CI/CD 流水线里塞着一个 cluster-admin 的 Token。
企业里人员流动快,直接给每个员工建 User 对象维护成本极高。更稳的做法是:统一认证源 + Group 绑定 + 最小权限 Role。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: marketplace-app-operator
namespace: marketplace
rules:
- apiGroups: ["", "apps"]
resources: ["deployments", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"] # 业务确实需要读密钥时才放开,否则直接移除
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: marketplace-team-binding
namespace: marketplace
subjects:
- kind: Group
name: marketplace-devs
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: marketplace-app-operator
apiGroup: rbac.authorization.k8s.io
这里用 Group 而不是 User。配合 LDAP/AD 或 Keycloak 做 OIDC 同步后,人入职自动进组,离职直接踢出组,RBAC 配置完全不用动。
一个容易被忽视的细节:K8s 默认的 Role 作用域是 Namespace,但很多平台团队为了“方便调试”,会顺手加一条宽泛的 ClusterRole。一旦放开,业务账号就能 kubectl get secret -A 扫遍全集群。可以用 admission webhook 强行拦住这类操作。以 Kyverno 为例:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-cluster-scoped-binding-for-teams
spec:
validationFailureAction: Enforce
rules:
- name: deny-team-clusterrolebinding
match:
any:
- resources:
kinds: ["ClusterRoleBinding"]
validate:
message: "业务团队禁止创建 ClusterRoleBinding,请使用 Namespace 级 RoleBinding。"
pattern:
subjects[0].kind = "Group"
配合这条策略,任何试图把权限拉到集群级别的变更都会被拒绝,审计日志里也会留下记录。
再解决“应用能不能串门”:网络隔离与 Pod 安全
RBAC 管的是“谁能调 API”,但 Pod 之间的网络通信默认是放开的。同一个 Namespace 里,A 服务可以直接通过 curl http://finance-db.svc.cluster.local 连到 B 部门的数据库;跨 Namespace 更是畅通无阻。
多租户必须上 NetworkPolicy。很多企业只写了一条 DenyAll 就觉得安全了,实际上业务流量也会被拦死。更合理的做法是:默认拒绝 + 按租户白名单放行。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: marketplace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-tenant-and-ingress
namespace: marketplace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: marketplace
- podSelector: {}
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
- to:
- namespaceSelector:
matchLabels:
tenant: marketplace
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 443 # 仅允许出公网 HTTPS
为了让策略能按部门自动匹配,每个 Namespace 都要打上统一的 Label:
kubectl label namespace marketplace tenant=marketplace
kubectl label namespace finance tenant=finance
如果你们用的是 Cilium 或 Calico,还可以进一步启用 Hubble 可观测性,实时看到跨 Namespace 的异常连接请求。发现某个测试 Pod 在深夜频繁扫描其他部门的端口,基本就是配置错误或者内部试探边界。
除了网络,容器本身的安全基线也不能漏。K8s 1.21+ 内置了 Pod Security Admission(PSA),直接在 Namespace 元数据里声明:
apiVersion: v1
kind: Namespace
metadata:
name: marketplace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: baseline
pod-security.kubernetes.io/audit: restricted
restricted 级别会禁止容器提权、禁止 hostNetwork、禁止挂载宿主机敏感路径。业务如果非要跑特权容器(比如网络调试工具),必须单独走审批,不能默认放行。
最后解决“资源别被吃光”:Quota 与 LimitRange
隔离做完,还要防“吵邻居”。A 部门的一个 Go 程序没设置内存上限,把整个 Node 的内存吃满,B 部门的生产应用直接 OOM。Namespace 级别的配额就是用来锁天花板的。
实际落地时,ResourceQuota 和 LimitRange 要成对出现:前者管 Namespace 总量,后者管单 Pod/单容器的上下限。
apiVersion: v1
kind: ResourceQuota
metadata:
name: marketplace-quota
namespace: marketplace
spec:
hard:
requests.cpu: "16"
requests.memory: 32Gi
limits.cpu: "32"
limits.memory: 64Gi
persistentvolumeclaims: "10"
services: "20"
secrets: "30"
pods: "100"
---
apiVersion: v1
kind: LimitRange
metadata:
name: marketplace-limits
namespace: marketplace
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "200m"
memory: 256Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: "50m"
memory: 64Mi
- type: Pod
max:
cpu: "8"
memory: 16Gi
这段配置的逻辑很直白:
- 每个部门总共只能申请 16 核请求、32 核上限、32Gi 内存请求、64Gi 内存上限。
- 单个容器默认给 500m CPU + 512Mi 内存,最多不能超过 4 核 + 8Gi。
- 单个 Pod 最多 8 核 + 16Gi。
业务方如果确实需要跑大规格任务(比如离线训练、批处理),不要让他们私下改配额。走公开的申请流程,平台团队评估节点余量后再调。第一版配额宁紧勿松,观察一周真实使用量,再按 80% 利用率的目标逐步放宽。
另外,Secret 和 ConfigMap 的数量也要限制。很多团队只配 CPU/内存,结果一个部门疯狂创建几千个 ConfigMap,把 etcd 拖慢,影响全集群。上面的配额里已经把 secrets: 30、configmaps: 50、pods: 100 写死了,这就是防这类场景的。
变更怎么管:GitOps + 审计日志
配置写好了,谁来维护?纯靠人工 kubectl apply 在多部门环境下一定会失控。推荐把所有 Namespace、RBAC、Quota、NetworkPolicy 的定义放进 Git 仓库,按部门划分目录,用 Argo CD 或 Flux 做持续同步:
platform-infra/
├── namespaces/
│ ├── marketplace.yaml
│ └── finance.yaml
├── rbac/
│ ├── marketplace/
│ │ ├── roles.yaml
│ │ └── bindings.yaml
│ └── finance/
├── quotas/
│ ├── marketplace-quota.yaml
│ └── finance-quota.yaml
└── policies/
├── network/
└── pod-security/
合并请求必须经过平台团队 Review。这样每次权限变更都有迹可循,审计的时候直接 git log 就能还原历史。
K8s 的 API Server Audit Log 一定要开,而且不能只存在本机磁盘上。建议实时投递到 Loki 或 ELK,配置粒度如下:
# kube-apiserver audit policy
rules:
- level: Metadata
resources: ["secrets", "configmaps", "serviceaccounts"]
verbs: ["get", "list", "watch", "delete"]
- level: RequestResponse
resources: ["pods", "deployments"]
verbs: ["create", "delete", "patch"]
- level: None
users: ["system:kube-proxy"]
配合 Prometheus 抓取这些信号做告警:
- 某用户短时间内高频访问
secrets - 跨 Namespace 的 API 调用突增
- 某 Namespace 的 Quota 使用率超过 80%
RoleBinding被修改的频率异常
发现异常不要急着封号。先拉原始审计日志看请求体,确认是配置错误、CI/CD 流水线漂移,还是内部人员试探边界。多租户治理最怕“一刀切”,留足排查窗口反而能建立业务团队的信任。
落地节奏与真实避坑
如果你正准备从 0 到 1 搭这套体系,按这个顺序走会稳很多:
- 统一认证源:OIDC/LDAP/AD 打通,导出 User 和 Group 到 K8s。
- 创建部门 Namespace:打
tenantLabel,开启 PSArestricted。 - 部署 CNI 插件策略:Calico/Cilium 配置 Default Deny + 租户白名单。
- 下发 Quota + LimitRange:按业务规模初版分配。
- 接入 GitOps:所有变更走 PR + Review,Argo CD 同步。
- 开启 Audit Log:配置告警,接入日志平台。
- 定期权限复核:用
kubectl auth can-i --list --as=system:group:marketplace-devs -n marketplace验证实际权限是否符合预期。 - 灰度迁移:挑一个风险最低的部门先跑,确认无误杀再推广。
几个真实环境里踩出来的坑:
- ServiceAccount 默认挂载 Token:很多 Pod 模板没显式关闭
automountServiceAccountToken,导致容器里自带高权限 Token。业务 Pod 一律加上:spec: automountServiceAccountToken: false - Ingress Controller 权限收拢:别让业务团队直接操作
ingress-nginx的 ConfigMap 或 Namespace。统一由平台团队托管入口网关。 - ExternalDNS / LoadBalancer 配额:如果业务能自己创建 Service 类型
LoadBalancer,很容易把云厂商的 IP 配额耗尽。建议在 Quota 里限制services.loadbalancers: 5。 - etcd 备份不能共享:多租户集群的 etcd 备份要单独加密存储,恢复演练至少每季度做一次。
多租户隔离不是一次性交付的项目,更像是在维护一栋大楼的安保体系:门禁要分级、走廊要有监控、每个办公室的面积要有限制、钥匙丢了要能立刻注销。把身份、网络、资源、审计这四层串起来,平台团队能管得住,业务团队也不用天天求着加权限。
如果你正在选型 CNI、Admission Controller 或者设计配额分配模型,可以把具体的集群规模、部门数量和合规要求抛出来,我再帮你把配置往细里调。实际环境里的坑往往比文档里写的更生动,提前踩一遍比上线后救火划算得多。