Kubernetes多租户隔离实战从Namespace到RBAC到网络策略中小企业落地案例与踩坑经验
先说说背景吧。我们是一家做B端SaaS产品的公司,团队大概40来人,后端15个人左右。去年业务扩展快,内部项目从3个增加到8个,再加上各个项目组需要不同的环境,原来的K8s集群变得一团糟——A团队的测试环境能访问B团队的数据库,C团队的一个Pod把CPU吃满了,D团队抱怨自己的命名空间里资源总是被误删。
后来CTO决定做一次彻底的多租户隔离改造。这篇文章就是从那套改造方案里提炼出来的实战经验,踩过不少坑,也总结了不少好用的做法,希望能帮到正在或准备做类似事情的你们。
一、Namespace:多租户隔离的第一道门
Namespace是Kubernetes多租户的基础,但很多人对它的理解还停留在”分组隔离”这个层面。实际上要真正隔离,还需要搭配资源配额(ResourceQuota)和LimitRange一起用。
1.1 创建租户Namespace并设置配额
每个租户一个Namespace,然后在里面设置资源配额,防止某个团队滥用资源:
# ns-production.yaml - 生产环境租户
apiVersion: v1
kind: Namespace
metadata:
name: tenant-production
labels:
env: production
tenant-id: prod
managed-by: platform-team
---
# 资源配额:限制该租户的总资源使用
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: tenant-production
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
pods: "50"
services: "20"
persistentvolumeclaims: "10"
configmaps: "50"
secrets: "50"
---
# LimitRange:限制单个Pod/容器的资源范围
apiVersion: v1
kind: LimitRange
metadata:
name: production-limits
namespace: tenant-production
spec:
limits:
- default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "200m"
memory: 256Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: "100m"
memory: 128Mi
type: Container
注意几个细节:ResourceQuota控制的是Namespace级别的总量,LimitRange控制的是单个容器级别的上下限。这两个配合着用,才能真正防止资源被滥用。之前我们没设LimitRange,有个团队把单个Pod的内存限制设成了8Gi,结果节点OOM了,整个Node上的其他Pod都跟着遭殃。
1.2 命名空间命名规范
我们团队统一用 tenant-{业务线} 的格式,比如:
tenant-ecommerce(电商业务)tenant-finance(财务系统)tenant-platform(平台基础服务)
这样命名的好处是方便用标签做批量管理,后面RBAC配置也好写。
二、RBAC:权限控制的精细化工具
RBAC(Role-Based Access Control)是多租户隔离的核心。光有Namespace还不够,关键是不同的人(或团队)在Namespace里有什么权限。
2.1 租户管理员权限
每个租户需要有一个管理员,负责本团队资源的日常运维,但不能访问其他租户的资源:
# tenant-admin-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-admin
namespace: tenant-production
rules:
# Pod相关
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/exec"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 部署相关
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 服务发现
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 配置和密钥
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 持久化存储
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# Ingress
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# CronJob
- apiGroups: ["batch"]
resources: ["cronjobs", "jobs"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
# 允许查看监控指标(配合Prometheus)
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-admin-binding
namespace: tenant-production
subjects:
- kind: User
name: alice@ecommerce.com
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: group-ecommerce-admin
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-admin
apiGroup: rbac.authorization.k8s.io
这里的关键点是:Role的namespace限定为tenant-production,所以这个RoleBinding只在这个Namespace内生效。 alice作为电商团队的管理员,只能操作自己团队的资源,看不到其他团队的东西。
2.2 跨Namespace的集群级角色
有些角色需要跨多个Namespace操作,比如平台运维团队:
# platform-cluster-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-operator
rules:
# 可以查看所有Namespace的资源
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets", "persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
# 只能在特定Namespace进行写操作
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["create", "update", "patch", "delete"]
resourceNames: [] # 不做限制,由Namespace绑定的方式控制
# 管理集群级资源
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
# 注意:ClusterRoleBinding是集群级别的
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: platform-operator-binding
subjects:
- kind: Group
name: group-platform-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: platform-operator
apiGroup: rbac.authorization.k8s.io
这里有个坑:ClusterRoleBinding无法限制到特定Namespace。所以平台运维团队虽然有ClusterRole权限,但实际写操作还是要通过Namespace级别的Binding来控制。我们的做法是:平台团队只对tenant-platform和kube-system等公共Namespace有写权限,其他租户Namespace只有读权限。
2.3 开发者的最小权限角色
普通开发人员不需要所有权限,给他们一个只读+部分写权限的角色:
# developer-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tenant-developer
namespace: tenant-production
rules:
# 可以查看所有资源(只读)
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
# 可以创建和更新自己的部署(通过CI/CD)
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["create", "update", "patch"]
# 查看日志
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# 不能删除资源!
# 不能访问Secret!
# 不能执行Pod内的命令!
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tenant-developer-binding
namespace: tenant-production
subjects:
- kind: Group
name: group-ecommerce-dev
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: tenant-developer
apiGroup: rbac.authorization.k8s.io
注意这个角色故意没有delete权限,也没有secrets的访问权限,也没有pods/exec。这样即使是开发者的kubectl配置泄露了,攻击者能做的也有限。
三、网络策略:最后一道隔离屏障
很多团队做到RBAC就结束了,觉得Namespace隔离就够了。但实际上,Namespace层面的网络隔离在Kubernetes里默认是不生效的。也就是说,如果两个Pod在不同的Namespace里,它们默认还是可以互相通信的。这就是为什么要用Network Policy。
3.1 基础网络隔离策略
首先给每个租户Namespace设置一个默认拒绝所有流量的策略:
# default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: tenant-production
spec:
podSelector: {} # 选择所有Pod
policyTypes:
- Ingress
- Egress
---
# 允许租户内部的DNS解析(CoreDNS)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: tenant-production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
这里第一个NetworkPolicy用空的podSelector: {}选择该Namespace下所有Pod,然后policyTypes声明了Ingress和Egress都拒绝。这样就实现了”默认拒绝”的白名单模式。
第二个Policy专门放行DNS流量,因为Pod要解析域名必须能访问kube-system里的CoreDNS。如果不加这个,所有Pod都解析不了域名。
3.2 按服务级别的精细化策略
光有默认拒绝还不够,还需要根据业务需求开放特定的通信路径:
# api-to-db-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: tenant-production
spec:
podSelector:
matchLabels:
app: user-service
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 5432
---
# 允许API网关访问所有内部服务
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-gateway-egress
namespace: tenant-production
spec:
podSelector:
matchLabels:
app: api-gateway
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 8443
- to:
- podSelector:
matchLabels:
app: user-service
ports:
- protocol: TCP
port: 5432
这套策略的逻辑是:
user-service(数据库)只接受来自api-gateway的5432端口连接api-gateway可以向标记为tier: backend的Pod的8080/8443端口发送请求api-gateway可以向user-service发送5432端口的请求
这样即使某个Pod被入侵了,攻击者也不能随意横向移动到其他服务。
3.3 租户间网络隔离
这是最关键的一点——确保租户之间完全隔离:
# tenant-isolation-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-cross-tenant
namespace: tenant-production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
tenant-id: prod # 只允许访问同租户
# 允许访问kube-system(DNS、CoreDNS等)
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- to:
- ipBlock:
cidr: 10.0.0.0/8 # 允许访问内网IP段
ports:
- protocol: TCP
port: 443 # 只允许HTTPS
这里用namespaceSelector来限制出站流量只能去往相同租户的Namespace,或者kube-system。如果Kubernetes集群里的Namespace都有tenant-id标签,这个策略就能很好地工作。
四、中小企业落地案例:电商平台的真实场景
说完了理论,来聊聊我们实际落地时的完整方案。
4.1 集群架构设计
我们采用的是单集群多租户方案(成本考虑),整体架构如下:
集群:k8s-prod-01(3个Master + 6个Worker节点)
├── kube-system(系统组件)
├── tenant-ecommerce(电商业务)
│ ├── ns-quota: 8 CPU, 16Gi内存, 50 Pod
│ └── network-policy: 默认拒绝,按需开放
├── tenant-finance(财务系统)
│ ├── ns-quota: 4 CPU, 8Gi内存, 30 Pod
│ └── network-policy: 默认拒绝,按需开放
├── tenant-data(数据服务)
│ ├── ns-quota: 16 CPU, 32Gi内存, 80 Pod
│ └── network-policy: 严格隔离
└── tenant-dev(开发测试)
├── ns-quota: 16 CPU, 32Gi内存, 100 Pod
└── network-policy: 相对宽松
4.2 权限体系的完整设计
# 完整的RBAC体系设计
# 1. 集群级别的角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-viewer
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-viewers
subjects:
- kind: Group
name: group-all-engineers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-viewer
apiGroup: rbac.authorization.k8s.io
# 2. 租户级别的管理员角色(已在上面定义过)
# 3. 租户级别的开发者角色(已在上面定义过)
# 4. 特殊角色:数据库管理员
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: db-admin
namespace: tenant-data
rules:
- apiGroups: [""]
resources: ["pods", "services", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # 只能读,不能改
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"] # 可以进入Pod执行命令
subjects:
- kind: User
name: dba-team@company.com
apiGroup: rbac.authorization.k8s.io
4.3 实际部署流程
我们的CI/CD流水线在部署到K8s之前会做这些检查:
#!/bin/bash
# deploy-with-validation.sh
NAMESPACE=${1}
IMAGE=${2}
REPLICAS=${3}
echo "=== 部署前校验 ==="
# 1. 检查命名空间是否存在
if ! kubectl get namespace "$NAMESPACE" &>/dev/null; then
echo "❌ 命名空间 $NAMESPACE 不存在"
exit 1
fi
# 2. 检查当前用户是否有该命名空间的写权限
if ! kubectl auth can-i create deployments --namespace "$NAMESPACE" &>/dev/null; then
echo "❌ 当前用户无权在 $NAMESPACE 创建部署"
exit 1
fi
# 3. 检查资源配额
QUOTA_USED=$(kubectl top namespace "$NAMESPACE" --ignore-sysinfo 2>/dev/null || echo "0")
echo "📊 当前命名空间资源使用情况:"
kubectl describe resourcequota --namespace "$NAMESPACE"
# 4. 检查网络策略
echo "🔒 当前命名空间网络策略:"
kubectl get networkpolicy --namespace "$NAMESPACE"
# 5. 执行部署
echo "🚀 开始部署..."
kubectl set image deployment/app "$NAMESPACE" \
app=$IMAGE \
--namespace "$NAMESPACE"
echo "✅ 部署完成"
这套校验流程虽然简单,但能有效防止误操作。之前就有个新来的实习生差点把生产环境的配置删了,幸亏有权限校验。
五、踩坑经验:那些走过的弯路
5.1 坑一:NetworkPolicy不生效
这是最常见的问题。我们一开始设置了NetworkPolicy,但发现租户之间还是能互相访问。排查了很久才发现:
原因:我们的集群使用的是Calico作为CNI插件,但部署NetworkPolicy的时候忘记在Calico的配置里启用defaultEndpointToHostAction。更关键的是,有些节点上运行的Pod根本没有被打上正确的标签,导致podSelector匹配失败。
解决方案:
# 检查Calico是否生效
kubectl get pods -n calico-system
# 查看NetworkPolicy是否被正确应用到Pod
kubectl get networkpolicy -n tenant-production -o yaml
calicoctl get networkPolicy -n tenant-production
# 确保Pod有正确的标签
kubectl label pod <pod-name> app=<app-name> -n tenant-production
另外,有些云服务商的K8s服务对NetworkPolicy的支持不完整,需要提前确认。
5.2 坑二:RBAC配置写错了导致全员锁死
有一次调整RBAC的时候,我们误删了一个关键的ClusterRoleBinding,结果所有开发者都连不上K8s集群了。排查的时候发现:
原因:我们的kubeconfig里配置的context绑定了那个被删的ClusterRoleBinding,删除后所有人都是Forbidden。
解决方案:
# 紧急恢复步骤
# 1. 通过kube-apiserver的admin权限恢复
kubectl create clusterrolebinding emergency-access \
--clusterrole=cluster-admin \
--user=your-admin-account \
--dry-run=client -o yaml | kubectl apply -f -
# 2. 立即备份现有的RBAC配置
kubectl get clusterroles,clusterrolebindings,roles,rolebindings -o yaml > rbac-backup.yaml
# 3. 建立RBAC变更的审批流程
# 所有RBAC变更必须经过Code Review
从那以后,我们制定了严格的RBAC变更流程:所有RBAC配置必须提交PR,经过至少2个人Review才能合并,而且必须提前备份当前的RBAC配置。
5.3 坑三:LimitRange和ResourceQuota的冲突
刚开始我们给每个Namespace都设置了LimitRange和ResourceQuota,但发现有些服务总是创建失败:
原因:LimitRange设置了容器的默认limits,但有些服务(比如Sidecar模式)需要两个容器,第二个容器没有设置requests/limits,结果被LimitRange的default填入了值,但加起来超过了ResourceQuota的限制。
解决方案:
# 正确的做法:分别设置LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: limit-range
namespace: tenant-production
spec:
limits:
# Pod级别的限制
- type: Pod
max:
cpu: "8"
memory: 16Gi
min:
cpu: "100m"
memory: 128Mi
# Container级别的限制
- type: Container
default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "200m"
memory: 256Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: "100m"
memory: 128Mi
# Volume级别的限制
- type: PersistentVolumeClaim
max:
storage: 100Gi
min:
storage: 1Gi
5.4 坑四:ServiceAccount的权限扩散
Kubernetes里每个Pod默认会挂载一个ServiceAccount,这个SA会有对应的Token和权限。我们曾经发现一个租户的Pod可以访问到另一个租户的资源,排查后发现:
原因:某个租户的Deployment没有指定serviceAccountName,使用了默认的default SA,而这个SA被错误地绑定到了一个高权限的ClusterRole上。
解决方案:
# 1. 为每个Namespace创建专用的ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: tenant-app-sa
namespace: tenant-production
automountServiceAccountToken: false # 不自动挂载Token,需要时再挂载
---
# 2. 限制default SA的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: no-access
namespace: tenant-production
rules: [] # 空规则,没有任何权限
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: default-no-access
namespace: tenant-production
subjects:
- kind: ServiceAccount
name: default
namespace: tenant-production
roleRef:
kind: Role
name: no-access
apiGroup: rbac.authorization.k8s.io
---
# 3. 在Deployment中显式指定ServiceAccount
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: tenant-production
spec:
template:
spec:
serviceAccountName: tenant-app-sa # 显式指定
automountServiceAccountToken: false
5.5 坑五:多租户共享存储类的权限问题
我们想用StorageClass来做租户级别的存储隔离,但发现配置很复杂:
解决方案:
# 为不同租户创建不同的StorageClass(通过不同的PV后端)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: tenant-production-ssd
provisioner: ebs.csi.aws.com # 以AWS EBS为例
parameters:
type: gp3
FSxDNS: "false"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: topology.ebs.csi.aws.com/zone
values:
- us-east-1a
---
# 在租户的PVC中引用特定的StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: tenant-production
spec:
storageClassName: tenant-production-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
六、监控和审计:隔离之后的保障
做了那么多隔离措施,最后还需要监控和审计来保障。
6.1 审计日志
开启Kubernetes的审计日志,记录所有API请求:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有敏感资源的写操作
- level: Request
resources:
- group: ""
resources: ["pods", "services", "secrets", "configmaps"]
- group: "apps"
resources: ["deployments", "statefulsets"]
verbs: ["create", "update", "patch", "delete"]
# 记录所有RBAC相关操作
- level: Metadata
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# 记录所有命名空间的访问
- level: Metadata
resources:
- group: ""
resources: ["namespaces"]
verbs: ["get", "list"]
# 记录所有失败的请求
- level: Metadata
decision: Exclude
userGroups: ["system:masters"]
6.2 资源使用监控
用Prometheus + Grafana来监控每个租户的资源使用情况:
# 自定义Prometheus规则,按Namespace聚合
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-rules
namespace: monitoring
data:
tenant-rules.yaml: |
groups:
- name: tenant_quotas
rules:
- record: tenant:namespace:cpu_usage_ratio
expr: >
sum by (namespace) (
container_cpu_usage_seconds_total
) /
sum by (namespace) (
resourcequota_status_hard
{resource="requests.cpu"}
)
- record: tenant:namespace:memory_usage_ratio
expr: >
sum by (namespace) (
container_memory_working_set_bytes
) /
sum by (namespace) (
resourcequota_status_hard
{resource="requests.memory"}
)
七、总结:多租户隔离的核心原则
折腾了这一轮,总结下来有几个核心原则:
- 最小权限原则:每个租户、每个用户、每个服务账号,只给它们需要的最小权限。
- 纵深防御:Namespace + RBAC + NetworkPolicy + LimitRange + ResourceQuota,多层防护。
- 默认拒绝:网络策略默认拒绝所有流量,RBAC默认不给任何权限,然后按需开放。
- 审计和监控:隔离做再好,也需要有日志和监控来发现问题。
- 变更流程:所有RBAC、NetworkPolicy的变更都必须经过Review和测试,不能随便改。
多租户隔离不是一蹴而就的事情,需要根据业务发展和团队规模不断调整。我们现在的方案也不是完美的,但比起之前那个”谁都能删库”的状态,已经好太多了。
希望这篇文章能帮到正在做类似事情的你们。如果有具体问题,欢迎交流。