为什么你的 RBAC 配了还是 403?—— 一套可以复现的排障方法论

先讲一个真实经历。凌晨两点,开发在群里喊:"我的 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 返回 yesno,不返回"为什么 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 rolekubectl describe clusterrole 看它到底给了什么权限。

但这里有一个坑------kubectl get rolekubectl 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 是错的,必须是 podsservice 是错的,必须是 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 进 Pod
  • pods/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 这四个维度拆开,逐个对清楚。对清楚了,答案一般都出现在你自己都不好意思说出来的地方。

相关推荐
爱勇宝2 小时前
DeepSeek实习生日薪5500:真正恐怖的不是工资
人工智能·深度学习·面试
面试鸭2 小时前
面试官:“知识库搜到多少资料,你就全喂给 AI?”我:“信息越多越准。”她亮出错答案:“那它怎么被废话带跑了?”
后端·面试·求职
刘沅4 小时前
LeetCode 93.复原IP地址
算法·面试
用户40966601317514 小时前
实时数据大屏的 4 种推送方案:短轮询 → 长轮询 → SSE → WebSocket
后端·面试
不简说6 小时前
JS 代码技巧 vol.2 — 20 个数组/对象/字符串/正则的实用技巧
前端·javascript·面试
想要成为糕糕手8 小时前
NO.48 旋转图像 —— LeetCode 热题 100 面试导向深度解析
javascript·算法·面试
JieE2128 小时前
LeetCode 138 随机链表的复制|两种解法详解(哈希表 + 原地 O (1) 空间)
javascript·算法·面试
Zhu7588 小时前
在k8s环境部署Apache Kafka 集群架构,combined模式,无SSL配置
kafka·kubernetes·apache
贺国亚8 小时前
推理加速-Speculative-PrefixCache与Chunked-Prefill
面试