先讲一个真实经历。凌晨两点,开发在群里喊:"我的 Pod 起不来了,报 403!"你看了一眼他的 YAML------Role 配了、RoleBinding 也绑了,ServiceAccount 名字也对。kubectl describe 一看,Role 里写着 resources: ["pods"],他创建的是 Deployment------403 的原因是 deployments.apps 不在 "" API Group 里。
这条消息你花了 5 秒就判断出来了,但他卡了 3 个小时。问题不在他不懂 RBAC------在于 RBAC 出问题时,报错信息永远是干巴巴的 forbidden,没有任何线索告诉你"差了什么"。
这篇文章不讲"RBAC 是什么"------那是文档干的事。这篇文章讲的是"403 了怎么办"。
一、RBAC 的排障起点:先把模型画在脑子里
403 的错误信息通常长这样:
vbnet
Error from server (Forbidden): deployments.apps is forbidden:
User "system:serviceaccount:team-a:ci-deployer" cannot create resource
"deployments" in API group "apps" in the namespace "team-a"
这里面已经嵌入了完整的排障公式:
vbnet
谁(Subject)→ 做什么(Verb)→ 对什么资源(Resource/API Group)→ 在哪个范围内(Namespace/Cluster)
排障的每一步都是在这四个维度上排查哪个环节断了。把这四个维度拆开检查,比盯着 YAML 一行行读快十倍:
sql
维度 1: Subject --- 是 User 还是 ServiceAccount?是不是这个 SA 绑到了 Pod 上?
维度 2: Verb --- get? list? create? delete? watch? RBAC 的 verb 必须精确匹配
维度 3: Resource --- 资源名(pods)和 API Group("" 或 apps 或 rbac.authorization.k8s.io)
维度 4: Scope --- 是某 Namespace 内(RoleBinding)还是全集群(ClusterRoleBinding)
任何一个维度不匹配 → 403。
二、第一个命令:永远从 kubectl auth can-i 开始
说一个反直觉的事实:你给某个用户配了 RBAC 之后,不应该直接让他去操作然后看报不报错。你应该在绑定完之后立刻验证:
bash
# 验证某个用户能不能做某件事
kubectl auth can-i create deployments \
--as=system:serviceaccount:team-a:ci-deployer \
--namespace=team-a
# 验证能不能 list secrets(特别注意:不指定 -n 不意味着"所有 namespace")
kubectl auth can-i list secrets \
--as=system:serviceaccount:team-a:ci-deployer \
--namespace=team-a
# 验证集群级别权限(不加 -n)
kubectl auth can-i list nodes \
--as=system:serviceaccount:team-a:ci-deployer
kubectl auth can-i 返回 yes 或 no,不返回"为什么 no"。这个局限让它更适合做"快速验证"而不是"根因分析"。
当 can-i 返回 no 但你确定配对了的时候,往下走。
三、第二个命令:kubectl describe 出绑定链
RBAC 的绑定关系不是存在一个地方的------它是 Role/RoleBinding 两个对象的组合。你得找到绑定链:
bash
# 第一步:找出某个 SA 的所有 RoleBinding
kubectl get rolebinding -A -o json | \
jq '.items[] | select(.subjects[]?.name == "ci-deployer") | {namespace: .metadata.namespace, name: .metadata.name, role: .roleRef.name}'
# 第二步:找出某个 SA 的所有 ClusterRoleBinding
kubectl get clusterrolebinding -o json | \
jq '.items[] | select(.subjects[]?.name == "ci-deployer") | {name: .metadata.name, role: .roleRef.name}'
拿到 Role 名之后,用 kubectl describe role 或 kubectl describe clusterrole 看它到底给了什么权限。
但这里有一个坑------kubectl get role 和 kubectl get clusterrole 看到的是所有权限规则并在一起的结果,但每条 rule 的 API group 是独立的 。如果一条 rule 写了 resources: ["pods", "services"] 但没写 apiGroups,它默认只作用于 "" (core) API Group------这就是开头那个人踩的坑。他以为 resources: ["deployments"] 不用写 apiGroups,但 deployments 不在 core API Group 里,它在 apps 里。
四、最容易被漏掉的四个细节
1. API Group 不对。
这是我在生产上见过的 RBAC 403 中占比最高的根因------超过一半。
Kubernetes 资源分属不同的 API Group:
erlang
"" (core group): pods, services, configmaps, secrets, nodes, namespaces ...
apps: deployments, statefulsets, daemonsets, replicasets ...
networking.k8s.io: networkpolicies, ingresses ...
rbac.authorization.k8s.io: roles, rolebindings, clusterroles, clusterrolebindings ...
batch: jobs, cronjobs ...
如果你的 Role 规则写成这样:
yaml
rules:
- apiGroups: [""] # core API Group
resources: ["pods"]
verbs: ["get", "list"]
那用户只能操作 pods,不能操作 deployments。要操作 deployments,必须再加一条:
yaml
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create"]
或者用 '*' 通配所有 API Group(但不推荐在生产上这样用)。
2. 资源名拼写错误或用错了 subresource。
RBAC 对资源名称是严格匹配的。pod 是错的,必须是 pods。service 是错的,必须是 services。
另外 subresource 需要单独授权:
yaml
# 这个规则让用户可以 get pods,但不能 get pod logs
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
# logs 是 pods 的 subresource,必须单独授权
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
常见的 subresource 包括:
pods/log--- 查看 Pod 日志pods/exec--- exec 进 Podpods/portforward--- 端口转发pods/status--- 更新 Pod 状态deployments/scale--- 扩缩容
3. RoleBinding 绑错了 namespace。
RoleBinding 是命名空间级别的对象。如果你创建 RoleBinding 时没指定 namespace,它默认落在当前 context 的 namespace------不是那个 SA 所在的 namespace:
bash
# SA 在 team-a,但 RoleBinding 建在了 default ------ 403
kubectl create rolebinding ci-binding \
--role=deployer \
--serviceaccount=team-a:ci-deployer
# 不指定 -n 的话,RoleBinding 落在 default namespace
正确做法:
bash
kubectl create rolebinding ci-binding \
--role=deployer \
--serviceaccount=team-a:ci-deployer \
-n team-a # RoleBinding 必须跟目标资源在同一个 namespace
4. ServiceAccount 的 token 没挂进 Pod,或者 Pod 用了错的 SA。
从 Kubernetes 1.24 开始,ServiceAccount 不再自动创建长期有效的 Secret token。Token 通过 TokenRequest API 以 projected volume 的方式挂载:
yaml
spec:
serviceAccountName: ci-deployer
containers:
- name: app
volumeMounts:
- name: token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
如果 Pod 的 serviceAccountName 字段为空,Kubernetes 分配默认的 ServiceAccount(通常是 default),而 default SA 几乎没有权限。这是最隐蔽的 403------你花几个小时排查 CI 的 RoleBinding,最后发现 Pod 压根没用那个 SA。
五、OpenShift 对 RBAC 排障的增强:oc policy 和 groups
如果你用的是 OpenShift,排障体验比原生 Kubernetes 好一个档次。OpenShift 在 RBAC 上加了几个实用工具:
oc policy can-i --list:列出某个用户的所有权限
这是原生 kubectl auth can-i 做不到的:
bash
# 列出 SA 在某个 namespace 里的所有权限
oc policy can-i --list \
--as=system:serviceaccount:team-a:ci-deployer \
-n team-a
输出直接是一个表格------能做什么、对什么资源、什么 verb。你不再需要靠"猜"来排查权限缺失。
oc policy who-can:反向查询谁有某个权限
这个场景极其常见:pod/exec 权限过于宽,你想知道"到底哪些人能 exec 进 production namespace 里的 Pod":
bash
oc policy who-can create pod/exec -n production
输出列出所有有该权限的 Subject(User、Group、ServiceAccount),你可以逐条审查是不是合理的。
OpenShift 的默认 Groups
OpenShift 集群自带几个非常重要的 Group,理解它们对排查多用户场景的权限问题至关重要:
makefile
system:authenticated --- 所有已认证用户(任何能登录集群的人)
system:unauthenticated --- 未认证用户
system:cluster-admins --- 集群管理员(相当于 cluster-admin ClusterRoleBinding)
OpenShift 的很多默认 ClusterRoleBinding 使用了这些 Group。比如:
bash
# OpenShift 默认给所有登录用户 basic-user 角色
# 这意味着任何人登录集群至少能 get pods
oc get clusterrolebinding basic-users -o yaml
如果你发现"某个不该有权限的人居然能 list pods"------先检查他是不是通过 system:authenticated Group 拿到的默认权限。
六、一个可以复现的排障流程
下次有人跟你说"我的 RBAC 配了还是 403",按这个顺序走一遍,比看 YAML 快三倍:
sql
步骤 1: 确认 Subject 身份
→ 是 User?Group?ServiceAccount?
→ 如果是 SA,确认 Pod 的 serviceAccountName 属性对不对
步骤 2: 确认绑定链
→ kubectl get rolebinding -A | grep <subject-name>
→ kubectl get clusterrolebinding | grep <subject-name>
→ 如果都找不到 → Subject 根本没绑定任何角色
步骤 3: 确认权限范围
→ 细看 Role/ClusterRole 的 rules:
a) apiGroups 写对了没?(deployments 在 apps,不在 core)
b) resources 拼写对了没?(pods 不是 pod)
c) verbs 包含你需要的操作吗?(create 跟 update 是两回事)
d) 需要 subresource 吗?(logs/exec 需要单独声明)
步骤 4: 确认 Scope
→ RoleBinding → 只在该 namespace 生效
→ ClusterRoleBinding → 全集群生效
→ 你要操作的目标资源跟 RoleBinding 在同一个 namespace 吗?
步骤 5: 用 can-i 验证
→ kubectl auth can-i <verb> <resource> --as=<subject> -n <ns>
→ 如果返回 no,回到步骤 3 逐条比对 rules
RBAC 的 403 报错了不等于"我配错了权限"。绝大多数情况是"我以为我配了某条权限,但实际上配的是另一条"。报错只能告诉你"没有",不能告诉你"少了什么"。排障的本质不是看 YAML,是把 Subject → Verb → Resource → Scope 这四个维度拆开,逐个对清楚。对清楚了,答案一般都出现在你自己都不好意思说出来的地方。