在一次 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: User
system\:serviceaccount\:default\:mockupcannot list resource
services... }
。
这次事故发生在集群从 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 删除。