嘿,朋友,你是不是也经历过这种崩溃时刻?
周一早上刚上班,运维群里炸锅了——“为什么我的Pod起不来?”“为什么我访问不了这个Service?”“谁把我命名空间里的配置改掉啦?!”你登录K8s集群一看,整个人都懵了:Namespace之间网络竟然通了?权限乱得像一团毛线球?有些开发说没权限,有些又说有权限但操作报错。
别慌,今天咱们就一起来把这个坑填平。我会用最接地气的方式,带你从零开始搞定K8s多租户隔离,保证你看完就能上手,不再被这些破事儿折磨。
一、先搞明白:为啥K8s天生不是“多租户友好”的?
很多公司一开始用K8s,觉得它是个好东西,资源弹性、部署方便。但用着用着就发现问题了:K8s原生设计其实是“单租户”逻辑。
举个简单例子:
默认情况下,K8s集群里所有Namespace都能互相Ping通(除非你主动配置NetworkPolicy)。所有Namespace共享同一个API Server,同一个etcd。这意味着什么?
- 张三的团队在
devnamespace里部署了一个Pod,李四的团队在testnamespace也能通过ClusterIP访问到张三只Pod的Service - 王五写了一个Deployment,不小心覆盖了李四的ConfigMap
- 某个实习生误删了整个
prodnamespace里的Pod
这就是典型的“权限混乱”。
所以,多租户的核心就三个东西:
- 命名空间隔离(Namespace)
- 网络隔离(NetworkPolicy)
- 权限控制(RBAC)
咱们一个一个来拆解。
二、Namespace:多租户的第一道防线
2.1 Namespace到底是什么?
Namespace是K8s提供的逻辑隔离单元。你可以把它理解成“不同的公司租用同一栋办公楼,每家公司有自己的楼层”。
默认情况下,K8s有几个内置Namespace:
default:如果不指定Namespace,Pod就创建在这里kube-system:系统组件(如coredns、kube-proxy)所在kube-public:公开信息
2.2 创建专属Namespace给每个团队
假设你们公司有两个团队:前端组和后端组。
# 创建前端组Namespace
kubectl create namespace frontend-team
# 创建后端组Namespace
kubectl create namespace backend-team
# 查看Namespace列表
kubectl get namespaces
输出大概长这样:
NAME STATUS AGE
default Active 10d
frontend-team Active 1m
backend-team Active 1m
kube-system Active 10d
kube-public Active 10d
2.3 在Namespace里创建资源
现在,前端组只能在frontend-team里操作:
# 切换到frontend-team Namespace
kubectl config set-context --current --namespace=frontend-team
# 创建Deployment
kubectl create deployment nginx --image=nginx:1.21
# 创建Service
kubectl expose deployment nginx --port=80 --target-port=80 --type=ClusterIP
# 查看Pod
kubectl get pods
这样,前端组和后端组完全隔离了。他们看不到对方的Pod、Service、ConfigMap。
关键点:资源默认只在当前Namespace有效。如果你想跨Namespace访问,必须显式指定Namespace,比如:
# 查看backend-team namespace里的Pod
kubectl get pods -n backend-team
三、NetworkPolicy:网络隔离的核心
3.1 为什么需要NetworkPolicy?
前面说了,K8s默认是“网络畅通”的。也就是说,frontend-team的Pod可以访问backend-team的Service。这显然不安全。
NetworkPolicy就是来解决这个问题的。它允许你定义“谁可以访问谁”的规则。
3.2 前提条件:你的CNI必须支持NetworkPolicy
不是所有K8s集群都支持NetworkPolicy。你需要检查你的CNI(Container Network Interface):
- ✅ Calico:支持NetworkPolicy
- ✅ Cilium:支持NetworkPolicy
- ✅ Flannel:默认不支持,需要额外配置
- ❌ Kubeadm默认:不支持
怎么检查?
kubectl get pods -n kube-system -l app=calico
kubectl get pods -n kube-system -l app=cilium
如果看到相关Pod在运行,说明你的CNI支持NetworkPolicy。
3.3 实战:限制前端组只能访问自己的Service
假设前端组部署了一个Nginx Service,我们只想让同Namespace内的Pod访问它。
# frontend-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-deny-all
namespace: frontend-team
spec:
podSelector: {} # 选择所有Pod
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {} # 只允许同Namespace内的Pod访问
egress:
- to:
- podSelector: {} # 只允许同Namespace内的Pod出站
应用这个策略:
kubectl apply -f frontend-policy.yaml
现在,frontend-team namespace里的Pod:
- 入站:只能被同Namespace内的Pod访问
- 出站:只能访问同Namespace内的Pod
backend-team的Pod就访问不了frontend-team的Nginx了。
3.4 进阶:允许前端组访问后端组
有时候,业务确实需要跨Namespace通信。比如前端组需要调用后端组的API。
我们可以在backend-team namespace创建一个NetworkPolicy,只允许frontend-team的Pod访问:
# backend-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow-frontend
namespace: backend-team
spec:
podSelector:
matchLabels:
app: api-server # 只作用于api-server这个Deployment
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend-team # 只允许frontend-team namespace的Pod访问
podSelector: {}
ports:
- protocol: TCP
port: 8080
同时,给frontend-team namespace打一个标签:
kubectl label namespace frontend-team name=frontend-team
kubectl label namespace backend-team name=backend-team
这样,只有frontend-team的Pod能访问backend-team的api-server:8080,其他Namespace都访问不了。
注意:NetworkPolicy是“白名单”机制。默认情况下,如果不创建NetworkPolicy,所有流量都是允许的。一旦你创建了NetworkPolicy,未匹配的流量默认被拒绝。所以,一定要记得给所有Namespace都配置合适的NetworkPolicy,否则可能会把正常流量也拦住。
3.5 常见坑:NetworkPolicy不生效
如果你配置了NetworkPolicy,但发现流量还是通的,检查以下几点:
- CNI是否支持NetworkPolicy?前面说过,Flannel默认不支持。
- Namespace标签是否正确?
namespaceSelector依赖Namespace标签,如果标签没打上,规则不生效。 - Pod标签是否正确?
podSelector依赖Pod标签,如果Pod没有对应标签,规则不匹配。 - 是否有默认拒绝策略?有些CNI(如Calico)默认是“全局拒绝”,有些是“全局允许”。你需要确认你的CNI行为。
查看NetworkPolicy是否生效:
kubectl describe networkpolicy -n frontend-team
四、RBAC:权限管理的终极武器
4.1 RBAC是什么?
RBAC(Role-Based Access Control,基于角色的访问控制)是K8s的权限管理模型。它的核心思想是:权限 = 角色 + 用户/组。
- Role:定义“能对哪些资源做哪些操作”
- ClusterRole:跨Namespace的角色定义
- RoleBinding:把Role绑定到用户/组
- ClusterRoleBinding:跨Namespace的角色绑定
4.2 创建一个简单的Role
假设前端组需要在frontend-team namespace里读写Deployment和Service,但不能删除Namespace。
# frontend-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: frontend-team
name: frontend-developer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
应用这个Role:
kubectl apply -f frontend-role.yaml
4.3 把Role绑定到用户
假设前端组的开发人员用户名是zhangsan,我们需要把他绑定到frontend-developer这个角色。
# frontend-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: zhangsan-frontend
namespace: frontend-team
subjects:
- kind: User
name: zhangsan
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: frontend-developer
apiGroup: rbac.authorization.k8s.io
应用这个Binding:
kubectl apply -f frontend-binding.yaml
现在,zhangsan在frontend-team namespace里,可以:
- 创建、更新Deployment
- 创建、更新Service
- 查看Pod
但他不能:
- 删除Namespace
- 访问其他Namespace的资源
- 创建或删除Role
4.4 常见权限报错怎么处理?
报错1:Forbidden: users "zhangsan" cannot list resource "pods"
这说明zhangsan没有权限列出Pod。检查他的RoleBinding,确认他是否被绑定到了包含pods权限的Role。
报错2:Unauthorized
这说明API Server不认识这个用户。你需要先创建Certificate Signing Request(CSR)或者使用kubeconfig文件来认证。
报错3:user "zhangsan" cannot access namespace "backend-team"
这说明zhangsan只有frontend-team的权限,没有backend-team的权限。如果需要跨Namespace访问,需要额外创建RoleBinding。
4.5 使用ServiceAccount简化权限管理
在生产环境中,我们通常不会给每个开发人员单独创建用户,而是使用ServiceAccount(服务账户)。
ServiceAccount是K8s自动为每个Pod创建的服务账户,默认情况下,Pod会使用默认ServiceAccount。
我们可以为前端组创建一个专属ServiceAccount:
# frontend-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: frontend-sa
namespace: frontend-team
然后在Deployment里指定使用这个ServiceAccount:
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: frontend-team
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
serviceAccountName: frontend-sa # 使用专属ServiceAccount
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
这样,这个Deployment里的Pod就使用frontend-sa这个ServiceAccount,而不是默认的default。
然后,我们把Role绑定到frontend-sa:
# frontend-binding-sa.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: frontend-sa-frontend
namespace: frontend-team
subjects:
- kind: ServiceAccount
name: frontend-sa
namespace: frontend-team
roleRef:
kind: Role
name: frontend-developer
apiGroup: rbac.authorization.k8s.io
4.6 ClusterRole:跨Namespace的统一权限
有时候,我们希望某个用户或ServiceAccount在所有Namespace里都有相同权限。这时候就要用ClusterRole和ClusterRoleBinding。
比如,创建一个“只读观察者”角色,可以在所有Namespace里查看资源:
# cluster-reader-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-reader
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
然后绑定到某个用户:
# cluster-reader-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: zhangsan-cluster-reader
subjects:
- kind: User
name: zhangsan
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-reader
apiGroup: rbac.authorization.k8s.io
现在,zhangsan可以在所有Namespace里查看Pod、Service、ConfigMap、Deployment等资源,但不能修改或删除。
五、实战演练:完整的多租户隔离方案
假设你们公司有三个团队:前端、后端、运维。我们需要实现:
- 每个团队有自己的Namespace
- 团队之间网络隔离,但允许特定跨团队通信
- 每个团队只能管理自己的Namespace,不能访问其他Namespace
- 运维团队可以监控所有Namespace,但不能修改任何资源
5.1 创建Namespace并打标签
# 创建Namespace
kubectl create namespace frontend-team
kubectl create namespace backend-team
kubectl create namespace ops-team
# 打标签,方便NetworkPolicy使用
kubectl label namespace frontend-team name=frontend-team
kubectl label namespace backend-team name=backend-team
kubectl label namespace ops-team name=ops-team
5.2 创建NetworkPolicy
前端团队:只允许内部通信
# frontend-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: frontend-team
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {}
egress:
- to:
- podSelector: {}
- to:
- namespaceSelector:
matchLabels:
name: backend-team
podSelector:
matchLabels:
app: api-server
ports:
- protocol: TCP
port: 8080
后端团队:只允许前端团队访问,拒绝其他团队
# backend-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: backend-team
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend-team
podSelector: {}
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector: {}
- to:
- namespaceSelector:
matchLabels:
name: ops-team
ports:
- protocol: TCP
port: 9090
运维团队:允许访问所有Namespace
# ops-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ops-policy
namespace: ops-team
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress:
- to:
- namespaceSelector: {} # 允许访问所有Namespace
应用这些NetworkPolicy:
kubectl apply -f frontend-netpol.yaml
kubectl apply -f backend-netpol.yaml
kubectl apply -f ops-netpol.yaml
5.3 创建RBAC权限
前端团队:只能管理自己的Namespace
# frontend-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: frontend-team
name: frontend-admin
rules:
- apiGroups: ["", "apps", "extensions"]
resources: ["pods", "deployments", "services", "configmaps", "secrets"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "create", "update", "patch"]
# frontend-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: frontend-admin-binding
namespace: frontend-team
subjects:
- kind: Group
name: frontend-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: frontend-admin
apiGroup: rbac.authorization.k8s.io
后端团队:类似前端团队
”`yaml
backend-role.yaml
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: backend-team name: backend-admin rules: