课程目录(第 12 章 / 共 33 章)
命名空间与 RBAC:多人共用一个集群
用一个集群服务多个团队:用 Namespace 做逻辑隔离,用 ServiceAccount 给 Pod 一个身份,用 RBAC 精确控制谁能对什么资源做什么。
学完这一章,你将能够
- ✓说清 Namespace 能隔离什么、不能隔离什么
- ✓会给 Pod 指定 ServiceAccount,并理解 Token 是怎么进到容器里的
- ✓能写出 Role 与 RoleBinding,用 kubectl auth can-i 验证权限是否生效
Namespace 解决什么问题
到目前为止我们一直在 default 命名空间里折腾。一个人学习无所谓,但如果一个集群要给三个团队共用,问题马上就来了:名字会撞车,资源会被抢光,出了事谁都能删别人的东西。
Namespace(命名空间)是 Kubernetes 里的逻辑隔离单元,它主要解决四件事:
- 分组:同一类东西放在一起,
kubectl get pods -n demo只看自己的。 - 名字复用:
demo和staging里可以各有一个叫web的 Deployment,互不冲突。 - 配额:
ResourceQuota和LimitRange是按命名空间生效的,可以限制某个团队最多用多少 CPU、能创建多少个 Pod。 - 权限边界:RoleBinding 授权只在某个命名空间内生效,这是 RBAC 的基础。
要特别注意它的边界:Namespace 不是网络隔离。默认情况下,不同命名空间的 Pod 可以直接互相访问,Service 的完整 DNS 名是 <service>.<namespace>.svc.cluster.local。真正的网络隔离要靠 NetworkPolicy,而且是否生效取决于你的 CNI 实现,细节见集群网络。
集群创建后自带四个命名空间:default(不放东西时用)、kube-system(控制平面组件)、kube-public(公开可读)、kube-node-lease(节点心跳租约)。`kube-system` 里的东西不要随便动。
哪些资源不属于命名空间
有些资源天然是集群级的,它们没有 namespace 字段,写进 YAML 里也会被忽略:
| 类型 | 例子 |
|---|---|
| 节点与集群 | Node、Namespace 自身、ComponentStatus |
| 存储 | PersistentVolume、StorageClass、CSIDriver |
| 权限 | ClusterRole、ClusterRoleBinding |
| 扩展与策略 | CustomResourceDefinition、APIService、PriorityClass、IngressClass |
想自己确认,可以用这条命令列出所有集群级资源:
kubectl api-resources --namespaced=false
kubectl api-resources --namespaced=true | head -20这也解释了一个新手常见困惑:为什么 PVC 是命名空间级的,而它绑定的 PV 却是集群级的——PVC 属于某个团队,PV 属于整个集群的存储池,这一点在第 10 章 存储卷与 PV/PVC里讲得更细。
动手:创建 demo 命名空间并切换默认上下文
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl get namespaces
kubectl config set-context --current --namespace=demo
kubectl config view --minify --output 'jsonpath={..namespace}{"\n"}'最后一条应该输出 demo。从这一刻起,你所有不带 -n 的命令都默认作用于 demo——这既是方便,也是风险,手一滑就可能在生产命名空间里执行了删除命令。建议在团队里养成显式写 -n demo 的习惯,或者给上下文起个一眼能认出来的名字。切回来和查看资源:
kubectl config set-context --current --namespace=default
kubectl get all -n demo
kubectl get all,configmap,secret,pvc -n demokubectl get all 只包含 Pod、Deployment、ReplicaSet、StatefulSet、DaemonSet、Job、CronJob、Service 这些常见资源,不包括 ConfigMap、Secret、PVC、Ingress、ServiceAccount。排查时别被它的名字骗了。
ServiceAccount:Pod 在集群里的身份
RBAC 里被授权的主体(subject)有三种:User、Group、ServiceAccount。前两种是「人」,由 kubeconfig 里的证书或 OIDC 提供;ServiceAccount(SA)是给 Pod 里的进程用的身份。
每个命名空间都会自动创建一个叫 default 的 SA。如果你不指定,Pod 就用它。
kubectl get serviceaccount -n demo
kubectl create serviceaccount app -n demo从 Kubernetes 1.24 起,创建 SA 不再自动生成长期有效的 Secret Token。需要 Token 时,用下面的命令按需签发一个有时效的:
kubectl create token app -n demo --duration=1hPod 里拿 Token 的方式也不同了:kubelet 会通过 projected volume 把一个短期 Token 挂到 /var/run/secrets/kubernetes.io/serviceaccount/token,同时挂上 ca.crt 和 namespace 两个文件。如果 Pod 根本不需要访问 API Server,在 spec 里加一行 automountServiceAccountToken: false 就能关掉挂载——进容器执行 ls /var/run/secrets/kubernetes.io/serviceaccount/ 会提示目录不存在,容器里拿不到任何凭据,也就无法访问 API Server。
RBAC 四要素
RBAC(Role-Based Access Control)回答一个问题:谁(subject)能对什么资源(resource)做什么操作(verb)。它由四个对象组合而成:
| 对象 | 作用域 | 作用 |
|---|---|---|
Role | 命名空间 | 定义一组规则,比如「可以 get/list/watch pods」 |
ClusterRole | 集群 | 同样定义规则,但可用于集群级资源(如 Node),也可被 RoleBinding 引用 |
RoleBinding | 命名空间 | 把 Role 或 ClusterRole 绑到主体,授权只在这个命名空间内生效 |
ClusterRoleBinding | 集群 | 把 ClusterRole 绑到主体,授权在所有命名空间生效 |
一次请求要走完四道关,RBAC 只负责其中的第二道:
kubectl 或 Pod 里的进程发起请求(带客户端证书或 Bearer Token)
│
▼
① 认证 Authentication:你是谁?
├─ 人:kubeconfig 里的证书 / OIDC
└─ Pod:ServiceAccount 的 Token(kubelet 挂到 /var/run/secrets/.../token)
│ 通过 → 身份形如 system:serviceaccount:demo:app
▼
② 鉴权 Authorization ← RBAC 在这一步生效
Role / ClusterRole:定义「能对什么资源做什么」,写在 rules 里
RoleBinding / ClusterRoleBinding:决定「把这份规则给谁」
有规则匹配 → 放行;一条都不匹配 → 403 Forbidden
│ 通过
▼
③ 准入控制 Admission:请求合法,但要不要补默认值 / 拦下来?
LimitRange、ResourceQuota、NetworkPolicy、各种 Webhook 都在这一步
│ 通过
▼
④ etcd:对象真正被写入(只有 kube-apiserver 能直接读写 etcd)所以 ServiceAccount 决定「你是谁」(第①步),Role 决定「能干什么」,RoleBinding 决定「把这份权限给谁」——后两者一起在第②步起作用。
常用的 verb 有 get、list、watch、create、update、patch、delete、deletecollection;子资源要写成 pods/log、pods/exec 这样的形式。注意 pods/exec 权限约等于「能进容器干任何事」,几乎等同于该节点上的 root,不要随手给。
一条经验法则:能用RoleBinding就不要用ClusterRoleBinding。前者影响一个命名空间,后者影响整个集群,出错的代价差一个数量级。
动手:给 SA app 一份最小权限
目标:让 demo 里的 app 这个 SA 只能看 Pod 和 Pod 日志,不能删、不能改、不能看别的命名空间。
apiVersion: v1
kind: ServiceAccount
metadata:
name: app
namespace: demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: demo
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-pod-reader
namespace: demo
subjects:
- kind: ServiceAccount
name: app
namespace: demo
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioapiGroups 里的空字符串 "" 表示核心 API 组,Pod、Service、ConfigMap 都在这一组里;Deployment 属于 apps 组。
kubectl apply -f sa-app.yaml
kubectl get serviceaccount,role,rolebinding -n demo三个对象都出现就说明创建成功。接下来把这个 SA 挂到 Pod 上,让容器真的去调一次 API:
apiVersion: v1
kind: Pod
metadata:
name: api-client
namespace: demo
spec:
serviceAccountName: app
restartPolicy: Never
containers:
- name: client
image: curlimages/curl:8.10.1
command:
- sh
- -c
- |
curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://kubernetes.default.svc/api/v1/namespaces/demo/podskubectl apply -f api-client.yaml
kubectl logs -n demo api-client如果输出一段包含 "items" 的 JSON,说明 Pod 用 SA 身份成功读到了 Pod 列表;如果把 serviceAccountName 换成不存在的名字,Pod 会直接卡在 ContainerCreating,事件里写着 SA 找不到。
验证权限:kubectl auth can-i
kubectl auth can-i 是最直接的权限自检工具,它只回答「能不能」,不真的执行操作:
kubectl auth can-i list pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i get pods/log -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i list pods -n default --as=system:serviceaccount:demo:app预期依次是 yes、no、yes、no。最后一条是关键:SA 的权限被 RoleBinding 限制在 demo 里,换个命名空间就不生效了。命令的退出码也跟着 yes/no 变化(yes 是 0,no 是 1),可以直接写进脚本做校验。
想看这个 SA 的完整权限清单:
kubectl auth can-i --list -n demo --as=system:serviceaccount:demo:app输出会分成 Resources、Non-Resource URLs、Resource Names 几列,* 表示通配符。除此之外还有几行是 RBAC 默认给所有已认证用户的权限(比如读 /healthz),不要误以为是你的 Role 给的。
动手练习:从建 SA 到收紧权限
- 用命令创建 SA、Role、RoleBinding(如果上面已经 apply 过
sa-app.yaml,这三条会提示AlreadyExists,跳过即可):
kubectl create serviceaccount app -n demo
kubectl create role pod-reader -n demo --verb=get,list,watch --resource=pods
kubectl create rolebinding app-pod-reader -n demo --role=pod-reader --serviceaccount=demo:app- 看这个 SA 的完整权限清单,注意区分「自己的 Role 给的」和「RBAC 默认给所有已认证用户的」:
kubectl auth can-i --list -n demo --as=system:serviceaccount:demo:app- 逐条验证边界——
list pods是yes,delete pods是no,换个命名空间也是no:
kubectl auth can-i list pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i list pods -n default --as=system:serviceaccount:demo:app- 再收紧一步:把 Role 改成只允许读指定的那个 Pod,用
resourceNames限定,然后确认读别的 Pod 会被拒绝:
kubectl create role pod-reader -n demo --verb=get --resource=pods \
--resource-name=api-client --dry-run=client -o yaml | kubectl apply -f -
kubectl auth can-i get pods/api-client -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i get pods/web -n demo --as=system:serviceaccount:demo:app预期第一条是 yes、第二条是 no。最后删掉绑定,确认权限立刻消失——RBAC 是逐请求判定的,改完不用重新签发 Token:
kubectl delete rolebinding app-pod-reader -n demo
kubectl auth can-i get pods/api-client -n demo --as=system:serviceaccount:demo:app最小权限原则的检查清单
权限一旦给多了很难收回来,所以每次写 Role 前对着这份清单过一遍:
- 每个应用用自己的 SA,不要复用
default,也不要把多个应用塞进一个 SA。 - 作用域优先
Role+RoleBinding;确实需要集群级权限(Node、PV、CRD)时才用ClusterRoleBinding。 - verb 只给需要的:只读就给
get、list、watch,不要图省事写*。 - 资源写具体:
pods而不是*;pods/exec约等于节点上的 root,能不给就不给。 - 需要精确到单个对象时用
resourceNames,例如只允许读某个 ConfigMap(配置本身见第 9 章 ConfigMap 与 Secret)。 - 定期用
kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa>回看,删掉没人用的 RoleBinding。 - 临时提权用短时效 Token(
kubectl create token --duration=1h),不要造长期凭据。
常见坑与排错
| 现象 | 原因 | 怎么确认 | 怎么办 |
|---|---|---|---|
应用调 API 返回 403 Forbidden | Role 里没有对应的 verb/resource,或 RoleBinding 的 subject 名字写错 | kubectl auth can-i <verb> <resource> -n <ns> --as=system:serviceaccount:<ns>:<sa>;kubectl describe rolebinding <名字> -n <ns> | 补规则,或逐字核对 subject 的 name 与 namespace |
Pod 卡在 ContainerCreating,事件说 SA 找不到 | serviceAccountName 拼错,或 SA 不在同一个命名空间 | kubectl get sa -n <ns>;kubectl describe pod | 改对名字;SA 必须与 Pod 同命名空间 |
kubectl auth can-i 报 cannot impersonate | 你自己的账号没有 impersonate 权限 | 报错信息里直接写着 | 换有权限的账号验证,或直接读 RoleBinding |
| 改完 Role 后权限似乎没变 | 改错了命名空间,或看的是另一个 SA | kubectl get role -n <ns> -o yaml 对照;kubectl auth can-i --list --as=... | RBAC 是逐请求判定的,改完立即生效,不需要重新签发 Token |
| 命名空间删不掉 | 里面还有资源或对象上挂着 finalizer | kubectl describe namespace <名字> 看 Conditions | 先清空资源,或按提示处理 finalizer |
四个容易踩的坑
- ClusterRoleBinding 给过头:把
cluster-admin绑到一个 SA 上,等于给这个 SA 所在的所有 Pod 发了万能通行证,而且授权是全集群的,将来想收回来得先找出谁在依赖它。生产环境优先用RoleBinding,确实需要集群级权限时才用ClusterRoleBinding,并写清用途。 - `--as` 验证时的误判:如果报
cannot impersonate resource ...之类的 Forbidden,那通常是你自己的账号没有impersonate权限,而不是目标 SA 没权限。另外can-i只检查规则,SA 不存在时它照样可能返回no,别把它当成「SA 存在性检查」。 - SA 名字拼错导致 Forbidden:
RoleBinding的subjects[].name写错(比如app写成apps)不会报错,只是权限静默不生效,表现就是应用调 API 时收到 403。用kubectl describe rolebinding app-pod-reader -n demo逐字核对。 - 默认 SA 被赋予高权限:很多教程会让你
kubectl create clusterrolebinding default-admin --clusterrole=cluster-admin --serviceaccount=default:default,这等于让这个命名空间里每一个没写serviceAccountName的 Pod 都拥有集群管理员权限。一旦某个 Pod 被利用,整个集群就失守了。
还有两个细节:Role 和 RoleBinding 的 namespace 必须和被授权对象在同一个命名空间,跨命名空间授权要靠 ClusterRoleBinding 或者给每个命名空间各建一份;删除命名空间会把它里面的所有资源一起删掉,这是不可撤销的,执行前一定用 kubectl get all -n <ns> 看一眼。
自测题
自测:为什么说 Namespace 不是网络隔离?(点击展开答案)
因为 Namespace 只是 API 对象上的一个分组标签,它影响的是「名字、配额、权限」,不改变网络平面。Pod 的 IP 由 CNI 分配,跨命名空间的路由默认是通的,所以 demo 里的 Pod 可以直接连 staging 里的 Service,甚至能用 <service>.<namespace>.svc.cluster.local 精确寻址。要拦网络流量必须用 NetworkPolicy,而且它是否真正生效取决于 CNI 插件是否实现了它。把 Namespace 当成「网络隔离」会导致一个危险假设:以为放了敏感服务在单独命名空间就安全了。
自测:为什么能用 RoleBinding 就不要用 ClusterRoleBinding?(点击展开答案)
因为两者的影响范围差一个数量级。RoleBinding 只在它所在的命名空间内授权,出错了影响面可控;ClusterRoleBinding 的授权覆盖所有命名空间,一次误绑就等于给这个主体开了全集群的门,而且事后要收回来还得先找出谁在依赖它。所以判断标准很简单:只在一个命名空间里干活就用 RoleBinding;只有真正操作集群级资源(Node、PV、CRD)或需要跨所有命名空间时才用 ClusterRoleBinding,并写清用途。
自测:Pod 默认挂载的 SA Token 为什么是安全风险?(点击展开答案)
因为每个命名空间都有一个 default SA,而如果不指定 serviceAccountName,Pod 就会自动用它并挂载 Token。一旦有人给这个 default SA 绑了高权限(很多教程会这么干),那么这个命名空间里所有没写 SA 的 Pod 都自动拥有那些权限——哪怕它们根本不需要访问 API Server。降低风险的做法是:给每个应用单独建 SA、只授最小权限,对不需要调 API 的 Pod 显式设置 automountServiceAccountToken: false。
小结
- Namespace 提供逻辑隔离、配额和权限边界,但不提供网络隔离;PV、Node、StorageClass 等资源是集群级的。
- ServiceAccount 是 Pod 的身份;1.24 之后不再自动生成长期 Secret,按需用
kubectl create token签发短期 Token。 - RBAC 四要素:Role/ClusterRole 定义规则,RoleBinding/ClusterRoleBinding 把规则绑到主体;能用 RoleBinding 就别用 ClusterRoleBinding。
kubectl auth can-i和kubectl auth can-i --list是验证权限的第一手段,注意区分「自己没权限验证」和「目标真的没权限」。- 最小权限原则落到操作上:只给需要的 verb、只给需要的资源、优先限定命名空间。
练习
- 给
demo里的 SAapp追加一条规则:允许get名为app-config的 ConfigMap(用resourceNames限定),然后用kubectl auth can-i get configmap/app-config -n demo --as=system:serviceaccount:demo:app验证。 - 创建一个
staging命名空间,把web用kubectl get deployment web -n demo -o yaml导出、改掉命名空间后 apply 进去,确认两个命名空间的同名资源互不影响。 - 用
kubectl get role,rolebinding -A -o wide看看你的集群里已经有哪些授权,找出其中有没有不该存在的 ClusterRoleBinding。
到目前为止我们的工作负载都是「一直跑着」的:Deployment 保证副本数,Service 负责流量。但现实里还有另一类任务——跑一次就结束,或者每天凌晨跑一次。下一章第 13 章 Job 与 CronJob讲这类任务。
相关章节:第 15 章 排障手册(Forbidden 与 ContainerCreating 的排查流程)、集群网络(NetworkPolicy 与 CNI 实现)。