
简介面向需要为 Kubernetes v1.21.2 集群准备 DNS 组件的部署与运维人员CoreDNS v1.8.0 离线镜像包可用于内网、受限网络或版本固定场景帮助解决集群初始化与组件升级时镜像拉取不便的问题。压缩包共 8 个文件约 40.62MB包含 4 个 JSON 元数据与镜像清单文件、2 个 TAR 层文件及 2 个 VERSION 版本标记文件结构上覆盖了容器镜像运行所需的主要组成。已有 408 人学习或下载。包体可用于核对 CoreDNS 镜像版本、层完整性与清单信息也便于在离线环境中完成镜像归档与分发适合集群搭建、版本管理和故障排查时参考使用。1. 一个 tar.gz 引发的 CrashLoopBackOffk8s 里换 CoreDNS 没那么简单集群 DNS 出问题很多人的第一反应是换个版本试试。上周某公司的测试集群里kube-dns 解析 Service 偶发超时有人把 coredns_v1.8.0.tar.gz 编出来的二进制直接打成镜像替换 Deployment重启后 Pod 直接 CrashLoopBackOff日志只打了半行 Corefile 报错。k8s 里 DNS 是所有服务的入口它一挂Service 之间调不通、StatefulSet 起不来、探针全红排查起来就是黑匣子。coredns_v1.8.0 本身不背这个锅翻车点基本集中在四处Go 版本与包声明不匹配、编译参数把版本信息丢了、Corefile 插件写错、RBAC 少一条 list/watch。这套流程适合自建集群、离线环境和需要锁定 DNS 版本的运维下面照我实际走的顺序从拆包讲到验证。2. 版本与环境核对coredns_v1.8.0 该配哪个 Go、哪个 k8s动手之前先把两件事定下来用什么工具链编编出来给哪个集群用。这两个问题答错后面每一步都是白干。2.1 拆包先看三样东西go.mod、Makefile、vendor拿到 tar.gz 第一件事不是解压完直接 make而是先确认包里到底是什么结构。不同来源的包顶层目录名不一样有的带版本号有的直接是coredns/先列出来再决定解压到哪。# 确认包的完整性有官方摘要就对比一下 sha256sum coredns_v1.8.0.tar.gz # 只看前两层结构别一次性刷屏 tar -tzf coredns_v1.8.0.tar.gz | head -n 20 # 解压到固定目录方便后面复用 sudo mkdir -p /usr/local/src sudo tar -xzf coredns_v1.8.0.tar.gz -C /usr/local/src cd /usr/local/src/coredns*/ || exit 1 ls -1tar -tzf这一步的意义在于提前知道有没有顶层目录、有没有vendor/。如果目录结构跟你预期的不一样后面cd进去的所有命令都会落在错误的路径下编译出来的东西自然不对。接下来三样东西必须看# 1. go.mod 前两行确认 module 名和 go 指令版本 head -n 5 go.mod # 2. Makefile 里的 ldflags看版本号是怎么注入的 grep -n ldflags\|VERSION\|GITCOMMIT Makefile | head # 3. 有没有 vendor 目录决定离线编译策略 test -d vendor echo 有 vendor可离线编译 || echo 无 vendor需要先拉依赖go.mod里的go 1.15这类声明是硬性要求工具链低于它直接编不过。Makefile里的-ldflags决定了coredns -version能不能显示出版本号手工go build时如果不带这一段编译产物会显示(devel)上集群后你想确认跑的是不是 1.8.0 都没法确认。vendor目录则决定了完全离线的机器能不能编——有 vendor 就加-modvendor没有就只能先在能联网的机器上把模块缓存拉下来。提示这一步多花两分钟能省掉后面“为什么编出来的版本号是空的”“为什么离线机器一直卡在下载依赖”这两种最常见的返工。2.2 Go 工具链与 k8s 版本对应关系CoreDNS 的版本跟 k8s 版本是绑着走的选错组合不会立刻报错而是在某个边角场景里给你来一下。下面这张表是我自己整理的口径具体以发布说明为准CoreDNS 版本常配合的 k8s 版本go.mod 声明需要留意的点1.6.x1.16 – 1.17go 1.12 / 1.13还带着 proxy 插件的尾巴新集群别再用1.7.x1.17 – 1.19go 1.14 / 1.15proxy 插件已移除forward 是唯一选择1.8.01.19 – 1.21go 1.15ready 插件、endpoint_pod_names 的常规用法从表里能看出两件事。第一1.8.0 的编译工具链下限是 Go 1.15我一般装 1.15.x 或 1.16.x比go.mod声明高一个小版本最稳跳太高反而容易撞上依赖解析行为的差异。第二如果你的集群已经在 1.22 及以上1.8.0 不是不能跑但官方后续的 patch 版本对高版本 k8s 的适配更完整能升就升到 1.8 系列更新的小版本别死磕 1.8.0。工具链装好之后先自检go version # 期望 go1.15.x 或 go1.16.x go env GOPATH GOMODCACHE GOFLAGSGOMODCACHE告诉你依赖会落在哪个目录离线打包时要连这个目录一起搬。GOFLAGS如果被之前的项目污染成-modvendor而当前包没有 vendor编译会直接失败用go env -w GOFLAGS清掉再试。至于集群那一端登到 master 上核对一下版本和现有的 DNS 配置kubectl version --short kubectl -n kube-system get deploy coredns -o jsonpath{.spec.template.spec.containers[0].image} kubectl -n kube-system get svc kube-dns -o jsonpath{.spec.clusterIP}第二条命令告诉你现在跑的是哪个版本第三条告诉你集群 DNS 的固定 IP。这个 IP 后面必须原样保留kubelet 启动时用--cluster-dns写死了它你把 Service 重建一遍导致 IP 变了所有 Pod 的/etc/resolv.conf就全指到一个已经不存在的地址上那才是真正的大面积故障。3. 编译与打包把 tar.gz 变成能塞进节点的镜像工具链和版本口径确认完进入动手环节。这一章的目标很明确从 tar.gz 出发产出一个coredns -version能正确回显 1.8.0、架构跟节点对得上的二进制再包成镜像。3.1 解包、校验与目录约定上一章已经解压过了这里补一遍环境变量和目录约定。我习惯把源码、编译产物、镜像 tar 包分三个目录放避免docker build的上下文把整个源码树都传进去慢得离谱。# 统一工作目录 export BUILD_ROOT/opt/build/coredns mkdir -p ${BUILD_ROOT}/{src,bin,image} cd ${BUILD_ROOT}/src # 重新解压一份干净的避免上一次编译残留 tar -xzf /path/to/coredns_v1.8.0.tar.gz cd coredns*/ # 记录当前 commit 或版本标识后面注入到二进制里 git rev-parse --short HEAD 2/dev/null || echo no-git-dir把源码和产物分开好处是docker build时只COPY那个bin/coredns上下文小、构建快也不会因为源码目录里有.git之类的东西把镜像撑大。git rev-parse那一步在解压包上通常拿不到值没有 git 目录就跳过这不影响编译只是版本标识得靠-ldflags手动指定。如果这台机器完全离线先把依赖准备好# 有 vendor 目录时强制走 vendor go env -w GOFLAGS-modvendor # 没有 vendor 时在联网机器上执行下面两条然后把 GOMODCACHE 整个目录拷过来 go mod download go mod verifygo mod verify会校验缓存里模块的哈希输出all modules verified才能保证拷到离线机上不会因为半截文件报奇怪的错。这一步属于典型的“多做一次后悔药”离线环境里依赖损坏的报错往往极其隐晦能提前拦住就别省。3.2 make 编译与交叉编译编译有两种走法直接用仓库自带的make或者手工go build。前者省事版本号会自动注入后者可控适合需要裁剪或交叉编译的场景。# 走法一官方 Makefile产物默认在源码根目录 make ls -lh coredns ./coredns -versionmake内部会跑go generate、go build和一段-ldflags其中-X参数把版本号写进coremain包。所以./coredns -version应该输出类似linux/amd64, go1.15.x, v1.8.0的内容。如果显示(devel)说明 ldflags 没生效通常是手工改了 Makefile 或者环境变量把它覆盖了。交叉编译给集群节点用我用走法二# 目标linux/amd64静态编译去掉调试信息 CGO_ENABLED0 GOOSlinux GOARCHamd64 \ go build -modvendor \ -ldflags -s -w \ -o ${BUILD_ROOT}/bin/coredns . # 核对产物架构和依赖 file ${BUILD_ROOT}/bin/coredns ldd ${BUILD_ROOT}/bin/coredns 21 | head -n 3CGO_ENABLED0是关键CoreDNS 是纯 Go 实现关掉 cgo 之后产物是静态链接的扔进scratch或alpine都不会缺 libc。-s -w去掉符号表和调试信息体积能小一截代价是出问题时没法gdb回溯——生产镜像我一般保留调试镜像再另编一份。节点是 arm64 的话把GOARCH换成arm64重编一次。这一步错了的后果很直接镜像能拉下来Pod 一启动就exec format error而且报错信息跟 DNS 毫无关系第一次遇到能想半天。编完立刻验证插件清单# 确认 kubernetes、forward、cache、ready 这几个插件都编进去了 ${BUILD_ROOT}/bin/coredns -plugins | grep -E ^(dns\.)?(kubernetes|forward|cache|ready|health)$CoreDNS 的插件是编译期决定的不是运行期加载的。如果你下载的这份包被人裁剪过kubernetes插件可能根本不在列表里那 Corefile 写得再对也没用。这一步花十秒能确认后面所有配置有落脚点。3.3 打成镜像并离线分发二进制好了包成镜像。我不用官方 Dockerfile 直接改自己写一个更短的思路清楚也好排查# 基础镜像带 CA 证书forward 走 DoT 或访问外部 HTTPS 时用得上 FROM alpine:3.13 RUN apk add --no-cache ca-certificates \ adduser -D -u 10001 coredns # 只拷二进制源码不进镜像 COPY bin/coredns /coredns RUN chmod 755 /coredns USER 10001 EXPOSE 53 53/udp 9153 8080 8181 ENTRYPOINT [/coredns]EXPOSE里的四个端口对应四件事53 是 DNS 本体9153 给 prometheus 插件出指标8080 是 health 插件的探针口8181 是 ready 插件的就绪口。这几个端口在后面的 Deployment 里要一一对上漏一个探针就会一直失败。USER 10001是非 root 运行。CoreDNS 监听 53 端口需要特权但在容器里配合NET_BIND_SERVICE能力或者 k8s 的默认策略通常没问题如果你的集群开了很严的 PodSecurityPolicy记得把这条能力加上否则容器启动会报permission denied绑不上 53。构建并导出# 在 BUILD_ROOT 下执行注意上下文别带源码 docker build -t infra/coredns:v1.8.0 -f Dockerfile . docker save infra/coredns:v1.8.0 -o ${BUILD_ROOT}/image/coredns-v1.8.0.tar离线节点上导入时注意命名空间。containerd 环境下 k8s 用的是k8s.io这个 namespace导错地方kubectl会一直 ImagePullBackOff# containerd 节点 ctr -n k8s.io images import coredns-v1.8.0.tar # docker 节点 docker load -i coredns-v1.8.0.tar # 导入后确认镜像在标签别写错 ctr -n k8s.io images ls | grep coredns镜像标签建议带上完整版本v1.8.0别用latest。滚动更新的时候同一个标签对应不同内容是最容易出事的情况之一尤其是多个节点预拉镜像时间不一致时你根本分不清哪个 Pod 跑的是哪份二进制。4. Corefile 落地kubernetes 插件的参数怎么写才不出事镜像有了真正的重头戏在 Corefile。这套配置我见过太多版本从能跑但性能差到看起来一样却解析不通差别都在参数细节上。4.1 最小可用 Corefile 与插件执行顺序先给一份我在 1.8.0 上用着最稳的基线配置.:53 { # 错误日志排在前面才能捕获后面插件的问题 errors # 健康检查lameduck 让进程退出前先保持 5s 可用 health { lameduck 5s } # 就绪检查替代了早期用 health 兼做 readiness 的写法 ready # 集群域解析反解域一并声明 kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } # 指标出口端口别跟别的服务撞 prometheus :9153 # 集群外域名转发max_concurrent 防止上游被打爆 forward . /etc/resolv.conf { max_concurrent 1000 } # 缓存 30 秒跟上面的 ttl 保持一致 cache 30 # 转发环路检测宁可启动失败也别静默死循环 loop # 监听 Corefile 变更改配置不用重启 reload # 多后端时按轮询分摊 loadbalance }这里有个特别容易误解的点Corefile 里插件的书写顺序并不决定执行顺序。CoreDNS 的插件执行顺序是由编译时的plugin.cfg固定的errors永远在最外层cache在forward之前拦截请求kubernetes命中就返回、不命中才fallthrough到forward。所以你在 Corefile 里把forward挪到kubernetes上面执行顺序一点没变只是让人读起来更迷惑。理解这一点之后很多“配置改了没反应”的疑问就通了改的是文本顺序不是执行链。想确认某个插件到底在不在执行链里靠的是-plugins输出的顺序而不是 Corefile 的排版。lameduck 5s值得单独说。它让 CoreDNS 在收到退出信号后先 deregister 自己、再等 5 秒才真正关闭给 kube-proxy 更新转发规则留出时间。少了这段滚动更新期间会有几秒钟的解析失败表现为“升级完总有那么几个请求超时”非常玄学。4.2 kubernetes 插件与 cache、forward 的参数取舍kubernetes插件是整份配置里参数最多、也最容易配错的部分。逐个说。pods insecure和pods verified的区别前者解析 Pod IP 时只依赖 Endpoints 里已有的记录不额外查 Pod 对象性能好、权限要求低后者会去校验 Pod 是否存在需要pods的 list/watch 权限结果更准但多一次查询。绝大多数集群用insecure就够了只有当你的场景里存在“Endpoints 有记录但 Pod 已经没了”的脏数据、需要严格过滤时才换verified。ttl 30是返回给客户端的记录缓存时间cache 30是 CoreDNS 自己缓存上游结果的时间。两个 30 不是巧合保持一致能让客户端和 CoreDNS 的缓存同时过期减少“客户端拿到旧 IP、CoreDNS 已经换了新 IP”这种不一致窗口。调大能降负载调小能加快 Service 变更的生效速度30 秒是这两者之间比较舒服的平衡点。fallthrough in-addr.arpa ip6.arpa只在反解域生效含义是反解查不到就交给下一个插件。不加这一行集群内查一个不存在的反解地址会直接返回 NXDOMAIN而不是继续往上游问。有跨集群反解需求的场景必须加。forward . /etc/resolv.conf的写法在容器里要小心/etc/resolv.conf是容器的文件默认内容通常是nameserver 10.96.0.10也就是 CoreDNS 自己的 Service IP。这样写等于让 CoreDNS 把外部查询转发给自己loop插件会直接检测出来并让进程退出。所以容器化部署时要么把上游显式写死要么通过dnsConfig给 Pod 指定一个不一样的上游。# 显式指定上游比读 /etc/resolv.conf 可控 forward . 10.0.0.2 10.0.0.3 { max_concurrent 1000 # 上游健康检查挂了自动摘除 health_check 5s }max_concurrent限制同时进行的外部查询数默认就是 1000超过的请求会被拒绝。集群规模大、外部域名解析量高的时候这个值要按 QPS 估一下再调health_check让 forward 定期探测上游连续失败就摘掉避免一个坏掉的上游拖慢所有查询。cache插件除了cache 30还有几个可选参数值得一提cache 30 { success 9984 # 成功结果最多缓存多少条 denial 9984 # NXDOMAIN 结果最多缓存多少条 serve_stale 1h # 上游挂了先返回过期数据比直接失败强 }serve_stale是我在 1.8.0 上比较喜欢的一个开关上游 DNS 抖动时先返回过期的旧记录业务侧感知不到失败。代价是有可能返回已经变更的地址所以只适合那些“旧地址短时间还能用”的场景对一致性要求高的内部域名别开。4.3 与 k8s 清单对齐RBAC、ConfigMap、Deployment、ServiceCorefile 只是配置文件的正文真正让它跑起来还需要一整套清单配合。权限部分最容易漏因为 CoreDNS 的报错不会告诉你缺哪条权限只会一直返回 SERVFAIL。apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:coredns rules: # 解析 Service 和 Endpoints 的基础权限必须有 - apiGroups: [] resources: [endpoints, services, pods, namespaces] verbs: [list, watch] # 1.8.0 的部分用法需要读 Service 的扩展信息 - apiGroups: [discovery.k8s.io] resources: [endpointslices] verbs: [list, watch]endpoints、services是必须的少了任何一个kubernetes插件启动时就会在日志里刷failed to list *v1.Endpoints: forbidden然后所有集群内域名解析全部失败。pods是pods verified模式的前提用insecure也建议留着某些 patch 版本会用到。endpointslices是较新的 API集群版本低的话这条可以去掉先确认kubectl api-resources | grep endpointslices有没有输出。ConfigMap 就是把 Corefile 原文塞进去注意 key 名跟挂载路径要对应apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . 10.0.0.2 10.0.0.3 cache 30 loop reload loadbalance }Deployment 部分重点在参数、探针和端口三处spec: replicas: 2 template: spec: serviceAccountName: coredns containers: - name: coredns image: infra/coredns:v1.8.0 args: [-conf, /etc/coredns/Corefile] ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 readinessProbe: httpGet: path: /ready port: 8181 volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefileargs里的-conf指定配置文件路径必须跟mountPath加items.path拼出来的完整路径一致也就是/etc/coredns/Corefile。三处路径任何一处对不上容器启动就会报“找不到 Corefile”然后退出。livenessProbe打/health的 8080readinessProbe打/ready的 8181这两个端口分别来自health和ready插件。有人图省事两个探针都打 8080结果就绪判断跟健康判断混在一起CoreDNS 还在等上游响应的时候 Pod 已经被判定为 NotReady 踢出负载均衡反而放大了故障。Service 部分最关键的是保留原有的 clusterIPapiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system spec: clusterIP: 10.96.0.10 # 必须跟 kubelet 的 --cluster-dns 一致 selector: k8s-app: kube-dns ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153如果你是从旧集群迁移先kubectl -n kube-system get svc kube-dns -o yaml把原 IP 抄下来填进去。直接 apply 一份不写clusterIP的新 Servicek8s 会重新分配一个地址而所有节点的 kubelet 参数还指着老地址结果就是整个集群的 DNS 全部超时而且从 Pod 里看 CoreDNS 本身是健康的特别容易误判。5. 常见问题排查从 Pod 起不来 到 解析超时这一章是我这几年踩过的坑里挑出来的五条按“启动期”和“运行期”分开。每条按现象、原因、解决三段走。5.1 启动期故障Pod 起不来先查这三处现象一Pod 一直 CrashLoopBackOff日志只有半行plugin/...: unknown directive。原因基本是 Corefile 里写了当前版本不支持的指令。1.8.0 上写view、minimal这类较新版本才有的插件名或者把forward拼成proxy都会在启动阶段直接失败。CoreDNS 的解析器对未知指令零容忍一行不对整个进程就退出。解决办法是在本地先校验一遍不用反复往集群里推# 用新二进制加载 Corefile报错信息比容器日志完整 /opt/build/coredns/bin/coredns -conf ./Corefile -dns.port 1053把-dns.port改到非 53 端口避免跟你本机已有的 DNS 冲突。加载成功会正常监听失败会直接把出错的行号和指令打出来。我现在的习惯是 Corefile 每次改完先在本地跑一遍这条命令通过之后才 apply能省掉一大半的滚动重启。现象二Pod 事件里是exec format error镜像明明拉下来了。原因是二进制架构跟节点不匹配。在 x86 机器上编的包扔到 arm64 节点或者反过来表现就是这个。编译时的GOARCH决定了产物架构跟镜像标签没关系。# 看二进制本身是什么架构 file /opt/build/coredns/bin/coredns # 看节点是什么架构 kubectl get nodes -o custom-columnsNAME:.metadata.name,ARCH:.status.nodeInfo.architecture两边对不上就按节点的架构重新交叉编译一次再把镜像重新导入。多架构集群要么统一架构要么用 manifest list 打多架构镜像别指望一个二进制通吃。5.2 运行期故障能启动但解析不对现象三日志里刷plugin/loop: Loop (127.0.0.1:xxxxx - :53) detected进程反复重启。这是loop插件在保护你。出现的原因通常是forward的上游指向了 CoreDNS 自己容器里的/etc/resolv.conf默认就是集群 DNS 的 Service IP而 Corefile 里写forward . /etc/resolv.conf等于自己转发给自己形成闭环。解决办法有两个一是把上游改成显式 IP二是通过 Pod 的dnsPolicy: Default或dnsConfig让容器内的resolv.conf指向真实上游。我更倾向第一种配置里写死上游地址行为可预期也免得依赖节点的 DNS 配置。现象四集群内域名能解析外部域名全部超时CoreDNS 日志干干净净。这种“只有一半不通”的情况八成是forward的上游在容器网络里不可达。典型场景是上游写了127.0.0.1或者某个只在宿主机上监听的地址从 Pod 网络出去根本到不了。日志里不会有报错因为请求确实发出去了只是没人应答最后客户端超时。# 进 CoreDNS Pod 里直接测上游可达性 kubectl -n kube-system exec -it deploy/coredns -- \ sh -c echo 上游地址容器内没有 dig 就用 nc 测 53 端口镜像里没带 dig 的话最快的判断方式是临时起一个带网络工具的 Pod用nc -zvu 上游IP 53测一下。不通就换成集群网络里真实可达的上游别用回环地址。现象五Service 域名解析不到但 Pod 域名能解析。这种“一半对一半错”的表现说明kubernetes插件在工作但拿到的数据不全。先看日志有没有failed to list *v1.Endpoints: forbidden有就是 RBAC 缺权限没有的话检查 Service 的clusterIP是不是被重建过、跟 kubelet 的--cluster-dns是否一致。# 确认 RBAC 绑定生效 kubectl -n kube-system auth can-i list endpoints --assystem:serviceaccount:kube-system:coredns # 确认 Service IP 跟 kubelet 参数一致 kubectl -n kube-system get svc kube-dns -o jsonpath{.spec.clusterIP}第一条命令返回yes才算权限到位。返回no就把 ClusterRoleBinding 补上再kubectl rollout restart deploy/coredns。注意改 RBAC 后必须重启 Pod已建立的 informer 不会自动重新建连。6. 装完怎么验证三条 dig 打通解析链路装完不等于跑通。我验证的顺序是自下而上先确认进程和探针再确认集群内解析最后确认外部解析和指标。第一步看 Pod 状态和就绪情况。两个副本都应该Running且READY 1/1重启次数为 0kubectl -n kube-system get pod -l k8s-appkube-dns -o wide kubectl -n kube-system describe pod -l k8s-appkube-dns | grep -A3 Liveness\|Readiness如果READY是0/1但状态Running说明/ready探针没过通常是kubernetes插件还没完成首次同步等 30 秒再看一直不过就回去查 RBAC。第二步用临时 Pod 打三条查询覆盖三种解析路径# 1. 集群内 Service 域名走 kubernetes 插件 kubectl run -it --rm dnstest --imageinfra/dnsutils:latest --restartNever -- \ dig short kubernetes.default.svc.cluster.local # 2. 集群内反解验证 fallthrough 配置 kubectl run -it --rm dnstest2 --imageinfra/dnsutils:latest --restartNever -- \ dig short -x 上一步返回的 ClusterIP # 3. 外部域名验证 forward kubectl run -it --rm dnstest3 --imageinfra/dnsutils:latest --restartNever -- \ dig short www.example.com第一条返回的是kubernetesService 的 ClusterIP说明kubernetes插件和 RBAC 都正常。第二条返回 Pod 名或 Service 名说明反解域配置生效。第三条返回外部地址说明forward链路通了。三条里任何一条空着就直接对应到上一章的那五类问题里去找。第三步核对指标确认没有异常响应码堆积kubectl -n kube-system port-forward svc/kube-dns 9153:9153 curl -s localhost:9153/metrics | grep -E coredns_dns_responses_total|coredns_dns_request_duration_seconds_count重点看rcodeSERVFAIL和rcodeNXDOMAIN的比例。SERVFAIL 持续增长基本可以断定上游或后端同步有问题NXDOMAIN 高但业务正常通常是客户端在频繁查不存在的域名属于噪音。老版本里这个指标叫coredns_dns_response_rcode_count_total1.8 系列用新名字抓不到就两个都 grep 一遍。最后补一个容易被忽略的动作确认 Corefile 真的生效了。reload插件虽然会自动加载变更但有延迟改完配置最稳的做法是手动滚一次kubectl -n kube-system get cm coredns -o yaml | grep -A5 Corefile kubectl -n kube-system rollout restart deploy/coredns kubectl -n kube-system rollout status deploy/coredns把 ConfigMap 里的内容和本地那份对一遍确认没有被别人的改动覆盖再重启。我遇到过 ConfigMap 被另一份清单 apply 回旧版本的情况Pod 日志一切正常解析结果却始终是老的查了半天才发现配置根本没换。从那以后我每次升 CoreDNS都强制走一遍这套验证先本地用-conf加载 Corefile再核对 Service 的 clusterIP 没变最后用三条 dig 分别打内、反解、外。希望这套顺序也能帮到你少在 DNS 上熬几个夜。本文还有配套的精品资源点击获取