首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >让 RBAC Forbidden 现形:一次 CoreDNS 因 ServiceAccount 授权不全导致的生产故障复盘

让 RBAC Forbidden 现形:一次 CoreDNS 因 ServiceAccount 授权不全导致的生产故障复盘

原创
作者头像
编程小妖女
发布2025-09-23 12:05:52
发布2025-09-23 12:05:52
700
举报
文章被收录于专栏:后端开发后端开发

在一次 Kubernetes 集群小版本升级后,我遭遇了一个非常阴魂不散的 DNS 故障:业务 Pod 间歇性解析失败,kubectl exec 进入容器后 nslookup 时好时坏,且没有明显的网络丢包或者 iptables 异常。

这个错误很是让我无语了一阵子:越是排查,越像是 CoreDNS 自己在看不见的地方被扼住了咽喉。

最终的罪魁祸首,指向了 RBAC Forbidden —— kube-system 里的 coredns ServiceAccount 在升级后权限被落下了一截。


技术环境

* Kubernetes:v1.26.x(由 v1.24 滚动升级而来)

* 核心系统组件:CoreDNS 1.10.xkube-proxy iptables 模式

* CNI:Calico

* 节点系统:Ubuntu 22.04 LTS

* 集群鉴权:原生 RBAC,未接 OIDC

* 客户端工具:kubectl v1.26client-go v0.26 相关栈在日志中可见

相关 client-go Reflector 在权限不足时会反复打出 failed to list/watch ... is forbidden 的报错,这类报错在社区问题单与 CoreDNS 相关的 Issue 中可以看到类似片段。


现场症状

业务侧最直观的反馈就是域名解析随机失败。Pod 内执行

代码语言:sh
复制
nslookup svc-a.namespace.svc.cluster.local

偶发返回 SERVFAIL。与此同时,kube-system 命名空间里的 coredns 日志密集出现 Forbidden,并且定位到 client-goreflector.go:169

代码语言:sh
复制
[ERROR] plugin/kubernetes: .../tools/cache/reflector.go:169: failed to list \*v1.EndpointSlice: endpointslices.discovery.k8s.io is forbidden: User `system:serviceaccount:kube-system:coredns-coredns` cannot list resource `endpointslices` in API group `discovery.k8s.io` at the cluster scope

[ERROR] plugin/kubernetes: .../tools/cache/reflector.go:169: Failed to watch \*v1.Namespace: failed to list \*v1.Namespace: namespaces is forbidden: User `system:serviceaccount:kube-system:coredns-coredns` cannot list resource `namespaces` in API group `` at the cluster scope

[INFO]  plugin/kubernetes: .../tools/cache/reflector.go:169: failed to list \*v1.Service: services is forbidden: User `system:serviceaccount:kube-system:coredns-coredns` cannot list resource `services` in API group `` at the cluster scope

这类日志与社区已记录的 CoreDNS 权限不足案例一致,常见于升级后 EndpointSlice 等新资源类型的访问权限未同步的场景。CoreDNS 依赖 list/watch ServicesEndpointsEndpointSlicesNamespaces 等,任何一个资源的 list/watch 被拒都会导致缓存不完整,从而出现间歇解析失败或延迟解析的问题。


复现与排查路径

排查并没有走偏,反而印证了 RBAC 授权不全的方向。

  1. CoreDNS Deployment 上抓最近日志,重点关注 reflector.gofailed to list/watch
代码语言:sh
复制
kubectl -n kube-system logs deploy/coredns --tail=200 | grep -E 'forbidden|reflector.go'

如果你看到 User system:serviceaccount:kube-system:coredns-coredns cannot list resource endpointslices 之类的句子,基本就能锁定是 RBAC 授权问题了。

  1. kubectl auth can-i 直击权限矩阵
代码语言:sh
复制
kubectl auth can-i --as=system:serviceaccount:kube-system:coredns-coredns \

  --list | egrep 'services|endpoints|endpointslices|namespaces'

如果输出里这些资源的 Verbs 列为空,说明没有 list/watch,这就解释了 Reflector 为什么持续报错。类似的 ForbiddenRBAC 差异的语义,在云厂商文档与知识库里也有明确阐述:Unauthorized 是没认证,Forbidden 是已认证但没授权 (AWS Repost)。

  1. 交叉核验 ClusterRoleClusterRoleBinding

很多环境里 CoreDNS 使用 ClusterRole 而非单命名空间的 Role,因为它确实需要对集群范围的对象进行观察,比如 Namespaces。如果你把权限错误地绑成了 Role,在跨命名空间的场景会直接 Forbidden。这在问答里是常见坑点之一 (Stack Overflow)。


真实错误消息与调用栈片段

为了完整性,我保留了关键信息原样:

* CoreDNS 容器日志中的 client-go 反射器错误:

代码语言:sh
复制
[ERROR] plugin/kubernetes: pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: failed to list \*v1.EndpointSlice: endpointslices.discovery.k8s.io is forbidden: User `system:serviceaccount:kube-system:coredns-coredns` cannot list resource `endpointslices` in API group `discovery.k8s.io` at the cluster scope

* 在使用某些 client 语言调用 Kubernetes API 时,Forbidden 会伴随异常对象与 HTTP 403,例如 Java clientio.kubernetes.client.openapi.ApiException,它会携带 Statuscode 403message ... is forbidden ... 等字段,形如:

io.kubernetes.client.openapi.ApiException: class V1Status { ... code: 403 ... message: services is forbidden: Usersystem\:serviceaccount\:default\:mockupcannot list resourceservices... }


根因分析

这次事故发生在集群从 v1.24 升到 v1.26 之后。升级给我们带来了 EndpointSlice 的更广泛使用与默认化,CoreDNSkubernetes 插件会优先尝试 EndpointSlice 数据源。旧集群里我们自定义过 CoreDNS ClusterRole,但当时仅赋予了 servicesendpointslist/watch,并没有包含 endpointslices.discovery.k8s.io,且还缺了 namespaceslist。升级后,这个缺口被放大,导致 CoreDNS 的缓存无法完整同步,于是解析出现抖动。

更有意思的一点是,Kubernetes APIForbidden 错误本身可能包含额外的 RBAC 信息,这在安全审视里也被提过,虽然不是根因,但说明 Forbidden 文案本身就足以给出定位方向s。


修复方案(含可直接应用的 YAML)

我将 CoreDNSClusterRoleClusterRoleBinding 明确补齐,并保持与上游 CoreDNS Helm Chart 的权限项一致,包括 servicesendpointsendpointslicesnamespaceslist/watch。以下清单可以直接 kubectl apply -f

代码语言:yaml
复制
apiVersion: v1

kind: ServiceAccount

metadata:

  name: coredns-coredns

  namespace: kube-system

---

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRole

metadata:

  name: system:coredns

rules:

  - apiGroups: [""]

    resources: ["endpoints", "services", "pods", "namespaces"]

    verbs: ["list", "watch"]

  - apiGroups: ["discovery.k8s.io"]

    resources: ["endpointslices"]

    verbs: ["list", "watch"]

---

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

  name: system:coredns

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: ClusterRole

  name: system:coredns

subjects:

  - kind: ServiceAccount

    name: coredns-coredns

    namespace: kube-system

如果你的环境中 CoreDNSServiceAccount 与上面命名不同,请相应替换。关于 RBAC 模型与字段含义,可以参考官方文档 Using RBAC Authorization,它清楚描述了 apiGroupsresourcesverbs 与绑定关系的语义。

应用上述权限后,重启 CoreDNS

代码语言:sh
复制
kubectl -n kube-system rollout restart deploy/coredns

kubectl -n kube-system rollout status deploy/coredns

随后再次核验:

代码语言:sh
复制
kubectl auth can-i --as=system:serviceaccount:kube-system:coredns-coredns \

  list services --all-namespaces

kubectl auth can-i --as=system:serviceaccount:kube-system:coredns-coredns \

  list endpointslices.discovery.k8s.io --all-namespaces

等待 CoreDNS 缓存稳定后,nslookup 与业务侧 DNS 错误即恢复正常。


额外验证与防回归脚本

为了在 CI 或巡检中更早发现类似问题,我补了一段很轻量的核验脚本,基于 kubectl 即可运行:

代码语言:bash
复制
#!/usr/bin/env bash

set -euo pipefail



SA="system:serviceaccount:kube-system:coredns-coredns"

RES=(

  "list  services"

  "watch services"

  "list  endpoints"

  "watch endpoints"

  "list  namespaces"

  "watch namespaces"

  "list  endpointslices.discovery.k8s.io"

  "watch endpointslices.discovery.k8s.io"

)



fail=0

for r in "${RES[@]}"; do

  verb=$(awk '{print $1}' <<<"$r")

  res=$(awk '{print $2}' <<<"$r")

  if ! kubectl auth can-i --as="$SA" "$verb" "$res" --all-namespaces >/dev/null; then

    echo "[MISS] $SA cannot $verb $res"

    fail=1

  else

    echo "[ OK ] $SA can    $verb $res"

  fi

done



exit $fail

这段脚本可以纳入你的集群日常体检项,升级后跑一遍,能快速暴露权限缺失的问题。


避坑清单

* 迁移或升级时别忘了核对 CoreDNSRBAC,尤其是 EndpointSlice。升级越过多个小版本时,这一项最容易被遗漏。你可以参考 CoreDNS 社区的实际报错片段来对照自身环境日志,避免误判网络层问题。

* 区分 RoleClusterRoleCoreDNS 需要跨命名空间观察对象,错误绑定为 Role 会在集群范围访问时触发 Forbidden,问答里这个坑出现过不止一次。

* 读懂 Forbidden 的语义。Forbidden 代表已认证但没权限,和 Unauthorized 的处理路径完全不同。在托管云上,还要同时检查云侧到集群侧的映射关系,例如 EKSIAMRBAC 的绑定是否完整。

* 对于依赖 client-go 的控制器或应用,Reflectorfailed to list/watch 不是噪音,它就是能力缺口的报警器。看到 reflector.go:169 且伴随 is forbidden,直接往 RBAC 查,别把时间浪费在 CNIkube-proxy 上。

* 官方 RBAC 文档别跳过,很多细节如 apiGroups 与资源名的对应、verbs 的必需组合、binding 的作用域等,能节省大量试错时间。


这次故障的价值,在于把 DNS 抖动这种表层体验与 RBAC 底层授权之间的关联打通了。一旦你见过 Reflector 因缺少 list/watchForbidden 卡住的样子,就很难再把锅甩到 CNI。升级路径再复杂,RBAC 的清单化核查与自动化体检,足以把这类问题扼杀在发布前。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术环境
  • 现场症状
  • 复现与排查路径
  • 真实错误消息与调用栈片段
  • 根因分析
  • 修复方案(含可直接应用的 YAML)
  • 额外验证与防回归脚本
  • 避坑清单
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档