Kubernetes RBAC 实战:ServiceAccount 最小权限、Role/RoleBinding 与 Forbidden 403 排查 Kubernetes RBAC 实战:ServiceAccount 最小权限、Role/RoleBinding 与 Forbidden 403 排查你在 Pod 里跑了个运维脚本,调kubectl get pods时报了一行:Error from server (Forbidden): pods is forbidden: User system:serviceaccount:default:default cannot list resource pods in API group in the namespace default或者你给 CI 建了个专用账号,apply一半突然 403;又或者装了个 controller,启动日志一直刷cannot watch resource ...。这些都是同一个东西在拦你——RBAC。这篇把 RBAC 的四个对象讲清楚,再手把手给一个 Pod 配上「只能读本命名空间 Pod」的最小权限,最后给一套 403 的定位命令。先搞清楚谁在访问 API集群里发起 API 请求的「主体」分两类:User / Group:集群外的人,比如你 kubeconfig 里的证书身份。K8s 本身不存 User,靠证书、OIDC 等外部机制认证。ServiceAccount(SA):集群内的 Pod 用的身份。每个命名空间默认有一个叫default的 SA,你不指定 SA 时,Pod 就自动挂它。上面那条报错里的system:serviceaccount:default:default,拆开就是system:serviceaccount:namespace:sa名字。也就是说,你的 Pod 用的是 default 命名空间的 default SA,而这个 SA 默认几乎没有任何权限——这是好事,最小权限本该如此。RBAC 的四个对象RBAC 就是把「主体」和「权限」绑到一起,靠四个对象:对象作用生效范围Role定义一组权限(能对哪些资源做哪些动作)单个命名空间ClusterRole同上,但可跨命名空间 / 管集群级资源整个集群RoleBinding把 Role(或 ClusterRole)授予某主体单个命名空间ClusterRoleBinding把 ClusterRole 授予某主体整个集群记一句话:Role/ClusterRole 定义「能干什么」,Binding 定义「谁能干」。两者缺一不可——光有 Role 没 Binding,等于写了权限没发给任何人。手把手:给 Pod 配「只读本命名空间 Pod」目标:在default命名空间建一个 SA 叫pod-reader,让挂它的 Pod 只能get/list/watch本命名空间的 Pod,别的一律不行。第一步:建 ServiceAccount# sa.yamlapiVersion:v1kind:ServiceAccountmetadata:name:pod-readernamespace:default第二步:建 Role# role.yamlapiVersion:rbac.authorization.k8s.io/v1kind:Rolemetadata:name:pod-reader-rolenamespace:defaultrules:-apiGroups:[]# 空字符串代表核心组(core),Pod 就在这里resources:[pods]verbs:[get,list,watch]三个字段是重点:apiGroups:Pod、Service、ConfigMap 这些「核心资源」的 group 是空字符串,不是core。Deployment 在apps,Ingress 在networking.k8s.io,写错 group 是 403 高发原因。resources:资源的复数小写名,如pods、deployments。想控制子资源(如看日志)要写pods/log。verbs:动作,常见get list watch create update patch delete。list和watch是两个独立动作,很多 controller 要 watch,你只给了 list 照样 403。第三步:建 RoleBinding 把 Role 发给 SA# binding.yamlapiVersion:rbac.authorization.k8s.io/v1kind:RoleBindingmetadata:name:pod-reader-bindingnamespace:defaultsubjects:-kind:ServiceAccount# 主体是 SA,不是 Username:pod-readernamespace:defaultroleRef:kind:Rolename:pod-reader-role# 这里的名字必须和上面 Role 的 name 完全一致apiGroup:rbac.authorization.k8s.ioroleRef一旦创建不可修改,想换绑的 Role 只能删了重建 Binding。第四步:让 Pod 用这个 SA# pod.yamlapiVersion:v1kind:Podmetadata:name:reader-testnamespace:defaultspec:serviceAccountName:pod-reader# 不写就是 default SA,写了才生效containers:-name:kubectlimage:bitnami/kubectl:latestcommand:[sleep,3600]一把应用并进去验证:kubectl apply-fsa.yaml-frole.yaml-fbinding.yaml-fpod.yaml# 进 Pod 里试kubectlexec-itreader-test -- kubectl get pods# ✅ 能列出kubectlexec-itreader-test -- kubectl get svc# ❌ Forbidden,符合预期kubectlexec-itreader-test -- kubectl delete pod x# ❌ Forbidden,没给 delete能读 Pod、读不了 Service、删不了 Pod——最小权限就配好了。不用真跑 Pod 就能测权限:kubectl auth can-i排查 403 最快的工具是auth can-i,它直接问 API Server「这个身份能不能干这件事」,不用真去操作:# 问某个 SA 能不能在 default 里 list podskubectl auth can-i list pods\--assystem:serviceaccount:default:pod-reader-ndefault# 输出 yes / no# 问它能不能删 podkubectl auth can-i delete pods\--assystem:serviceaccount:default:pod-reader-ndefault# no# 一次性看这个 SA 在命名空间里能干的所有事kubectl auth can-i--list\--assystem:serviceaccount:default:pod-reader-ndefault--as是「模拟身份」(impersonation),你自己得有 impersonate 权限(集群管理员默认有)。这一招能把「到底是权限没配对,还是代码调错了资源」瞬间分开。403 排查清单碰到 Forbidden,按这个顺序查,基本一遍命中:看报错里的身份对不对。报错会明确写User system:serviceaccount:ns:name。如果这里是default:default,说明你 Pod 根本没挂上自定义 SA——检查spec.serviceAccountName拼写,或者 Pod 是不是改完没重建(SA 改动不会热更到运行中的 Pod)。看报错里的资源和 group。报错会写cannot list resource pods in API group 。把这里的 resource 和 apiGroup 跟你 Role 里的resources/apiGroups逐字对。核心资源 group 是,Deployment 得单独在apps里授权——只授 pods 是管不到 deployments 的。看动作 verb 全不全。controller 常要watch,你只写了get list就会在建立 watch 时才炸,现象是「一开始好好的,过一会儿刷错」。确认 Binding 真的绑对了。用auth can-i --list直接看有效权限;或者:# 看这个 RoleBinding 绑的是谁、绑的哪个 Rolekubectl describe rolebinding pod-reader-binding-ndefault# 反查某个 Role 被哪些 Binding 引用(靠 grep)kubectl get rolebinding-ndefault-owide常见低级错:roleRef.name和 Role 的name差一个字;或者subjects.namespace写错了 SA 所在命名空间。命名空间对不对。Role/RoleBinding 是命名空间级的,default里的 Role 管不到kube-system。要跨命名空间授权,得用 ClusterRole 在目标命名空间建 RoleBinding 引用它。ClusterRole 的一个高频用法想让一个 SA 能读所有命名空间的 Pod,不可能给每个命名空间都建 Role。正确做法是建 ClusterRole,再用 ClusterRoleBinding 授权:apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRolemetadata:name:pod-reader-cluster# 集群级对象,不带 namespacerules:-apiGroups:[]resources:[pods]verbs:[get,list,watch]---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRoleBindingmetadata:name:pod-reader-cluster-bindingsubjects:-kind:ServiceAccountname:pod-readernamespace:default# SA 仍在 default,但权限跨全集群roleRef:kind:ClusterRolename:pod-reader-clusterapiGroup:rbac.authorization.k8s.io一个小技巧:用 ClusterRole 定义「能干什么」,用普通 RoleBinding(而非 ClusterRoleBinding)引用它,就能把这份权限限定在单个命名空间生效——权限定义复用,作用范围收窄,很实用。小结认证解决「你是谁」,RBAC(授权)解决「你能干什么」,两码事;Pod 的身份就是 ServiceAccount。四件套记牢:Role/ClusterRole 定义权限,RoleBinding/ClusterRoleBinding 把权限发给主体,少一个都不生效。授权三要素别写错:核心资源的apiGroups是空字符串;list和watch是两个独立 verb;资源名用复数小写。排查 403 先读报错里的身份、资源、group三样,再用kubectl auth can-i --list --as...直接问 API Server,比猜快得多。一句话记忆点:403 不是玄学,报错里已经写清了「谁、对什么资源、做什么动作」被拒,照着这三样对 Role 就行。