
在一次 Kubernetes 集群小版本升级后,我遭遇了一个非常阴魂不散的 DNS 故障:业务 Pod 间歇性解析失败,kubectl exec 进入容器后 nslookup 时好时坏,且没有明显的网络丢包或者 iptables 异常。
这个错误很是让我无语了一阵子:越是排查,越像是 CoreDNS 自己在看不见的地方被扼住了咽喉。
最终的罪魁祸首,指向了 RBAC Forbidden —— kube-system 里的 coredns ServiceAccount 在升级后权限被落下了一截。
* Kubernetes:v1.26.x(由 v1.24 滚动升级而来)
* 核心系统组件:CoreDNS 1.10.x,kube-proxy iptables 模式
* CNI:Calico
* 节点系统:Ubuntu 22.04 LTS
* 集群鉴权:原生 RBAC,未接 OIDC
* 客户端工具:kubectl v1.26,client-go v0.26 相关栈在日志中可见
相关 client-go Reflector 在权限不足时会反复打出 failed to list/watch ... is forbidden 的报错,这类报错在社区问题单与 CoreDNS 相关的 Issue 中可以看到类似片段。
业务侧最直观的反馈就是域名解析随机失败。Pod 内执行
nslookup svc-a.namespace.svc.cluster.local偶发返回 SERVFAIL。与此同时,kube-system 命名空间里的 coredns 日志密集出现 Forbidden,并且定位到 client-go 的 reflector.go:169:
[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 Services、Endpoints、EndpointSlices、Namespaces 等,任何一个资源的 list/watch 被拒都会导致缓存不完整,从而出现间歇解析失败或延迟解析的问题。

排查并没有走偏,反而印证了 RBAC 授权不全的方向。
CoreDNS Deployment 上抓最近日志,重点关注 reflector.go 的 failed to list/watch: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 授权问题了。
kubectl auth can-i 直击权限矩阵kubectl auth can-i --as=system:serviceaccount:kube-system:coredns-coredns \
--list | egrep 'services|endpoints|endpointslices|namespaces'如果输出里这些资源的 Verbs 列为空,说明没有 list/watch,这就解释了 Reflector 为什么持续报错。类似的 Forbidden 与 RBAC 差异的语义,在云厂商文档与知识库里也有明确阐述:Unauthorized 是没认证,Forbidden 是已认证但没授权 (AWS Repost)。
ClusterRole 与 ClusterRoleBinding很多环境里 CoreDNS 使用 ClusterRole 而非单命名空间的 Role,因为它确实需要对集群范围的对象进行观察,比如 Namespaces。如果你把权限错误地绑成了 Role,在跨命名空间的场景会直接 Forbidden。这在问答里是常见坑点之一 (Stack Overflow)。
为了完整性,我保留了关键信息原样:
* CoreDNS 容器日志中的 client-go 反射器错误:
[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 client 的 io.kubernetes.client.openapi.ApiException,它会携带 Status、code 403、message ... 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 的更广泛使用与默认化,CoreDNS 的 kubernetes 插件会优先尝试 EndpointSlice 数据源。旧集群里我们自定义过 CoreDNS ClusterRole,但当时仅赋予了 services 与 endpoints 的 list/watch,并没有包含 endpointslices.discovery.k8s.io,且还缺了 namespaces 的 list。升级后,这个缺口被放大,导致 CoreDNS 的缓存无法完整同步,于是解析出现抖动。
更有意思的一点是,Kubernetes API 的 Forbidden 错误本身可能包含额外的 RBAC 信息,这在安全审视里也被提过,虽然不是根因,但说明 Forbidden 文案本身就足以给出定位方向s。
我将 CoreDNS 的 ClusterRole 与 ClusterRoleBinding 明确补齐,并保持与上游 CoreDNS Helm Chart 的权限项一致,包括 services、endpoints、endpointslices、namespaces 的 list/watch。以下清单可以直接 kubectl apply -f:
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如果你的环境中 CoreDNS 的 ServiceAccount 与上面命名不同,请相应替换。关于 RBAC 模型与字段含义,可以参考官方文档 Using RBAC Authorization,它清楚描述了 apiGroups、resources、verbs 与绑定关系的语义。
应用上述权限后,重启 CoreDNS:
kubectl -n kube-system rollout restart deploy/coredns
kubectl -n kube-system rollout status deploy/coredns随后再次核验:
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 即可运行:
#!/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这段脚本可以纳入你的集群日常体检项,升级后跑一遍,能快速暴露权限缺失的问题。
* 迁移或升级时别忘了核对 CoreDNS 的 RBAC,尤其是 EndpointSlice。升级越过多个小版本时,这一项最容易被遗漏。你可以参考 CoreDNS 社区的实际报错片段来对照自身环境日志,避免误判网络层问题。
* 区分 Role 与 ClusterRole。CoreDNS 需要跨命名空间观察对象,错误绑定为 Role 会在集群范围访问时触发 Forbidden,问答里这个坑出现过不止一次。
* 读懂 Forbidden 的语义。Forbidden 代表已认证但没权限,和 Unauthorized 的处理路径完全不同。在托管云上,还要同时检查云侧到集群侧的映射关系,例如 EKS 的 IAM 到 RBAC 的绑定是否完整。
* 对于依赖 client-go 的控制器或应用,Reflector 的 failed to list/watch 不是噪音,它就是能力缺口的报警器。看到 reflector.go:169 且伴随 is forbidden,直接往 RBAC 查,别把时间浪费在 CNI 或 kube-proxy 上。
* 官方 RBAC 文档别跳过,很多细节如 apiGroups 与资源名的对应、verbs 的必需组合、binding 的作用域等,能节省大量试错时间。
这次故障的价值,在于把 DNS 抖动这种表层体验与 RBAC 底层授权之间的关联打通了。一旦你见过 Reflector 因缺少 list/watch 被 Forbidden 卡住的样子,就很难再把锅甩到 CNI。升级路径再复杂,RBAC 的清单化核查与自动化体检,足以把这类问题扼杀在发布前。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。