资讯动态

coreDNS 插件链与 Corefile 实战:Kubernetes 服务发现配置、排错与性能调优

发布时间:2026/10/27 8:13:18 来源:尧图企业网站定制
DNS 这东西平时没人注意一出问题就是全线告警。我做集群运维这些年处理过的域名解析故障没有一百也有八十而自从 Kubernetes 把服务发现的重担交给 coreDNS 之后这套东西就成了绕不开的基本功。coreDNS 是什么简单说它是一个用 Go 写的、插件化的 DNS 服务器在 Kubernetes 里默认承担集群内部的服务发现职责把 Service 名字翻译成 ClusterIP把 Headless Service 翻译成 Pod IP 列表。它能干什么能让你在 Pod 里直接curl my-svc.my-ns.svc.cluster.local就打到后端不用记 IP能通过插件链灵活扩展缓存、转发、重写、限流等能力。适合谁看适合正在搭集群、排查解析超时、想搞懂 Corefile 到底怎么写、或者准备把自建 DNS 迁移到 coreDNS 的运维和开发同学。下面我按自己踩坑的顺序把 coreDNS 从设计思路到实操细节完整拆一遍。1. coreDNS 整体设计与插件链思路拆解1.1 为什么是插件化架构而不是一个大而全的 DNS传统 DNS 服务器比如老牌的 BIND走的是配置文件驱动 功能内置的路子功能确实全但每加一个特性就要改配置语法、重启进程扩展性很差。coreDNS 换了个思路核心只做一件事——接收 DNS 请求、按顺序走插件链、把响应写回去。所有具体能力缓存、转发、Kubernetes 服务发现、日志、限流都是插件编译时可以选择性打包运行时通过 Corefile 编排顺序。这个设计的好处很直接。第一职责单一每个插件只关心自己那一段逻辑出问题容易定位日志里能清楚看到是哪个插件返回了 REFUSED 还是 NXDOMAIN。第二顺序即逻辑Corefile 里插件从上到下执行谁先匹配谁先处理这比 BIND 那种 zone 匹配规则直观得多。第三可裁剪你不需要 Kubernetes 插件就编译一个精简版二进制能小到十几 MB扔进容器里启动毫秒级。我个人的理解是coreDNS 把DNS 服务器这件事拆成了请求路由 插件处理两段本质上是一个带 DNS 协议头的中间件管道。你在 Corefile 里写的每一行都是在往这个管道里插一个处理环节。理解了这一点后面所有配置都不再是死记硬背。1.2 插件链的执行顺序与匹配逻辑coreDNS 处理一个请求的流程可以概括为监听端口收到报文 → 解析出查询名和类型 → 按 Corefile 中 server block 的顺序找到匹配的 zone → 在该 block 内按插件声明顺序依次调用 → 某个插件返回非 fallthrough 的结果就终止。这里有两个关键概念必须吃透。server block 匹配Corefile 里每个{ }块前面写的域名比如.、cluster.local、example.org决定了这个块负责哪些查询。.表示兜底所有域名cluster.local只处理集群内部域名。请求进来先按最长后缀匹配到对应的 block。fallthrough 语义默认情况下插件处理完就结束但如果某个插件显式写了fallthrough表示我处理不了或者不想处理时把请求交给链上的下一个插件。这个机制是 coreDNS 灵活性的核心也是最容易配错的地方。举个实际场景集群里既要解析cluster.local的内部服务又要能解析公网域名。典型 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 . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance }注意kubernetes插件里的fallthrough in-addr.arpa ip6.arpa——意思是反向解析的请求如果 Kubernetes 插件处理不了继续往下走给forward。如果不写这行反向查询可能直接返回 NXDOMAIN导致某些依赖反解的组件行为异常。这种细节文档里一句话带过但线上真能坑你半天。1.3 与 kube-dns 的取舍为什么默认换成了 coreDNS早期 Kubernetes 用的是 kube-dns底层是 dnsmasq 几个 sidecar架构上是多进程协作dnsmasq 负责缓存和转发kube-dns 容器负责监听 API Server 生成记录。这套东西的问题在于多进程意味着多份内存、多套日志、故障时排查链路长dnsmasq 的缓存策略和 Kubernetes 的 TTL 语义对不齐Service 变更后缓存过期不及时出现过服务已经删了但域名还能解析的经典问题。coreDNS 单进程、单二进制Kubernetes 插件直接 watch API Server 的 Service 和 Endpoint 变化记录更新是事件驱动的TTL 由插件参数统一控制上面配置里的ttl 30。我实测下来Service 创建后 coreDNS 基本在秒级内就能解析到删除后同样快速失效比当年 kube-dns 的体验好太多。当然代价是 coreDNS 对 Corefile 的理解门槛稍高但一旦掌握可控性强得多。2. 核心配置细节与实操要点解析2.1 Corefile 语法server block、插件与参数的三层结构Corefile 的结构就三层server block域名 端口 花括号、插件名、插件参数。看起来简单但有几个语法细节新手经常栽跟头。第一端口写法。.:53表示监听所有地址的 53 端口.:5353换端口如果要指定监听地址写bind 127.0.0.1插件而不是在 block 头里写 IP。第二同一域名多个 block是允许的coreDNS 会按顺序尝试但一般不建议容易混乱。第三环境变量可以用{$VAR}语法注入这在容器化部署时很有用比如forward . {$UPSTREAM_DNS}。一个容易忽略的点插件参数的顺序有时是有意义的。比如cache插件写cache 30是全局 TTL 30 秒但也可以写cache 30 { success 60 denial 10 }分别控制正缓存和负缓存。负缓存denial指的是 NXDOMAIN 这类查不到的结果缓存多久——这个值设太大服务刚创建时可能因为之前的负缓存而解析失败设太小又失去缓存意义。我的经验是 denial 设 5 到 10 秒比较稳妥。2.2 kubernetes 插件的关键参数怎么选kubernetes插件是集群场景的核心参数不少我挑几个真正影响行为的说。pods insecure和pods verified的区别前者允许解析 Pod IP 的反向记录时不校验 Pod 是否真实存在后者会去 API Server 核实。生产环境如果 Pod 数量大verified会带来额外的 API 压力一般用insecure配合合理的 TTL。endpoint_pod_names参数决定 Headless Service 的 Endpoint 记录是用 Pod 名还是 IP 命名开了之后 StatefulSet 场景下能直接按 Pod 名解析很实用。ttl参数控制记录返回的 TTL 值。设太小比如 5 秒客户端频繁回查coreDNS 压力大设太大比如 300 秒Service 变更后客户端缓存旧记录时间长。集群内部我一般用 30 秒是个平衡点。还有一个upstream参数可以指定解析外部域名时用的上游但通常交给forward插件处理更清晰。注意kubernetes插件必须能访问 API Server它靠 ServiceAccount 的 token 和 CA 做认证。如果你看到 coreDNS 日志里刷connection refused或403先查 RBAC 和网络策略别急着怀疑 DNS 配置。2.3 cache、forward、loop 三个高频插件的配合cache放的位置很讲究。它应该放在kubernetes之后、forward之前这样内部域名和外部域名的解析结果都能被缓存。如果放在kubernetes之前内部记录也会走缓存虽然也能工作但 Service 变更时的失效依赖 TTL不如让 kubernetes 插件直接应答来得及时。forward插件负责把非本地的查询转给上游。参数/etc/resolv.conf表示读取系统解析配置作为上游列表也可以直接写 IP比如forward . 8.8.8.8 1.1.1.1。max_concurrent限制并发转发数防止上游慢导致 coreDNS 线程被占满——这个值默认是 0不限生产环境建议设个上限比如 1000。loop插件是防呆设计如果 coreDNS 发现自己把查询转发给了自己配置错误导致的环路会检测到并在启动时报错退出。这个插件几乎不占资源但能救你一命。我见过有人把forward .指到了本机的另一个 DNS 实例结果两个实例互相转发CPU 打满加上loop就能在启动阶段直接暴露问题。3. 集群内部署与实操过程完整记录3.1 从零部署一套 coreDNS 的步骤拆解假设你有一个自建集群需要手动部署 coreDNS很多托管集群已经内置但自建场景要自己来。我按实际操作顺序走一遍。第一步准备 RBAC。coreDNS 需要 list/watch Service、Endpoint、Namespace、Pod 等资源。核心是一个 ClusterRole 绑定到 coreDNS 的 ServiceAccount。这一步的 YAML 比较长关键是resources列表要覆盖services、endpoints、pods、namespaces动词是list、watch。少一个资源对应类型的记录就解析不出来而且日志里不一定有明显报错排查起来很隐蔽。第二步写 ConfigMap 承载 Corefile。把上面那段 Corefile 内容放进 ConfigMap 的data.Corefile字段。注意 ConfigMap 更新后 coreDNS 不会自动重载除非你启用了reload插件默认有 30 秒的检测间隔。我一般保留reload改配置后等半分钟自动生效省得手动重启。第三步部署 Deployment。副本数建议至少 2 个配合反亲和性打散到不同节点避免单节点故障导致全集群解析中断。资源限制方面内存 128Mi 到 170Mi 是常见起点CPU 请求 100m。这里有个经验coreDNS 的内存占用和缓存条目数强相关如果集群 Service 特别多几千个内存要给到 256Mi 以上否则可能 OOM。第四步暴露 Service。集群内其他 Pod 通过 kubelet 配置的--cluster-dns指向 coreDNS 的 ClusterIP。这个 IP 通常在 Service 网段里挑一个固定值比如 10.96.0.10。Service 的端口映射是 53/UDP 和 53/TCP 都要开——UDP 用于常规查询TCP 用于大响应超过 512 字节和区域传送。第五步验证。起一个临时 Podnslookup kubernetes.default.svc.cluster.local看是否返回 ClusterIP再nslookup www.example.com看外部解析是否通。两个都通基本部署成功。3.2 参数计算副本数、资源与缓存容量怎么估副本数不是拍脑袋定的。粗略估算公式所需 QPS ÷ 单副本处理能力 副本数。单副本 coreDNS 在普通 4 核机器上处理简单查询能到几万 QPS但集群内查询往往伴随缓存未命中实际有效吞吐要看命中率。我的经验值是1000 个 Pod 以内的集群2 副本足够3000 Pod 以上考虑 3 到 4 副本并监控coredns_dns_request_duration_seconds的 P99。缓存容量方面cache插件默认最多缓存 9984 条记录可通过cache 30 { prefetch 5 10m 20% }之类调整预取。每条记录占几百字节一万条也就几 MB不是瓶颈。真正的瓶颈是并发连接数和上游延迟。如果forward的上游响应慢请求会堆积表现为 coreDNS 的 goroutine 数飙升。这时候要么加副本要么给forward设max_concurrent并配合health检查剔除慢上游。3.3 实操现场一次 Service 解析失败的完整排查记录说个真实场景细节做了脱敏。某天业务反馈新上线的服务在 Pod 里curl域名超时但直接curlClusterIP 正常。这说明网络通、Service 通问题在 DNS。我先在问题 Pod 里cat /etc/resolv.conf看到nameserver 10.96.0.10、search default.svc.cluster.local svc.cluster.local cluster.local、options ndots:5。ndots:5意味着查询名里点数少于 5 个时会先拼接 search 域逐个尝试。业务用的域名是my-svc一个点都没有于是解析器会依次尝试my-svc.default.svc.cluster.local、my-svc.svc.cluster.local、my-svc.cluster.local、my-svc.前几个失败才轮到最后一个。如果 coreDNS 对某个中间域名返回慢整体就超时。接着查 coreDNS 日志开了log插件后能看到每次查询。发现my-svc.default.svc.cluster.local的查询返回了 NXDOMAIN但 Service 明明存在。进一步查发现业务 Pod 和 Service 不在同一个 namespace而业务代码里写的是短名my-svcsearch 域第一个拼的是 Pod 自己的 namespace自然找不到。解决办法要么写全名my-svc.other-ns.svc.cluster.local要么在代码里配置完整的 namespace。这个坑的本质是search 域拼接顺序和 coreDNS 本身没关系但表现上就是DNS 解析不了很容易误判。提示排查 DNS 问题时先看/etc/resolv.conf的ndots和search再看 coreDNS 日志里实际收到的查询名。很多时候你以为在查 A 记录实际解析器在查一串拼接出来的名字。4. 常见问题速查与避坑经验实录4.1 解析超时、NXDOMAIN、SERVFAIL 的区分与定位这三种错误含义完全不同混为一谈会走弯路。超时通常是网络不通或上游无响应查 coreDNS 到上游的连通性、查forward配置。NXDOMAIN是这个域名确实不存在查名字拼写、namespace、search 域。SERVFAIL是服务器处理出错常见于上游返回异常、插件内部错误、或者递归查询被拒绝查 coreDNS 日志里的errors插件输出。我整理了一张速查表按现象对原因现象常见原因排查动作解析超时上游不可达、网络策略拦截、coreDNS 过载查 forward 上游连通性、看 coreDNS CPU/goroutineNXDOMAIN域名拼写错、namespace 不对、search 域拼接看 resolv.conf 的 search、用全名测试SERVFAIL上游返回异常、插件报错、递归被拒开 errors 插件看日志、检查上游健康间歇性失败缓存负缓存、副本不均衡、上游抖动调 denial TTL、查各副本负载启动即退出loop 检测到环路、端口被占、Corefile 语法错看启动日志、coredns -conf本地校验4.2 五个我踩过的坑与对应解法坑一负缓存导致新服务解析失败。服务刚创建但之前有人查过同名域名得到 NXDOMAIN负缓存还没过期新查询继续返回 NXDOMAIN。解法是把cache的denial设小5 到 10 秒或者对新服务用全名绕过缓存。坑二Corefile 改了没生效。忘了reload插件或者 ConfigMap 挂载有延迟。coreDNS 的reload默认 30 秒检测一次改完别急着测等一会儿。另外 ConfigMap 以 subPath 挂载时不会自动更新要用目录挂载。坑三TCP 查询被忽略。只开了 UDP 53大响应比如返回很多 Endpoint 的 Headless Service走 TCP 时失败。Service 定义里port和targetPort都要覆盖 TCP别只写 UDP。坑四forward 环路。上游 DNS 配置成了 coreDNS 自己形成环路。loop插件能在启动时检测到并报错但如果是运行中动态改配置引入的环路可能表现为 CPU 飙升。解法是确保forward指向真正的外部上游。坑五内存缓慢增长。长时间运行后 coreDNS 内存上涨多半是缓存条目或连接未释放。检查cache的容量上限确认forward的连接复用正常。升级到较新版本通常有改善老版本有过连接泄漏的修复。4.3 监控指标与日常巡检清单coreDNS 自带prometheus插件暴露在 9153 端口。我日常盯这几个指标coredns_dns_requests_total按 zone 和 type 分看查询量分布、coredns_dns_responses_total按 rcode 分NXDOMAIN 和 SERVFAIL 的比例是健康度晴雨表、coredns_dns_request_duration_secondsP99 延迟超过 100ms 要警惕、coredns_cache_hits_total和coredns_cache_misses_total算命中率低于 80% 说明缓存策略或 TTL 有问题。巡检清单我固定做四件事一看各副本 QPS 是否均衡不均衡说明负载或网络有问题二看 NXDOMAIN 比例是否突增可能是某个服务下线或配置错误三看缓存命中率趋势下降说明 TTL 太短或查询模式变了四看 goroutine 数和内存持续上涨是泄漏信号。这套清单帮我提前发现过好几次上游 DNS 抖动和配置漂移。5. 性能调优与扩展场景的实战取舍5.1 高并发场景下的调优参数组合集群规模上去之后coreDNS 的默认配置往往不够用。我总结了一套调优组合按优先级排。首先是forward的max_concurrent默认不限并发上游一慢请求就堆积。设成 1000 到 2000 之间配合health_check剔除不健康上游。其次是cache的prefetch对热点域名提前刷新减少缓存过期瞬间的穿透。写法是cache 30 { prefetch 5 10m 20% }意思是某域名 10 分钟内被查 5 次以上、且剩余 TTL 低于 20% 时提前预取。再就是loadbalance插件对返回多个 A 记录的响应做轮询打散避免客户端总打第一个 IP。这个在 Headless Service 场景下很有用。最后是副本数和反亲和性前面说过至少 2 副本打散到不同节点配合 PodDisruptionBudget 防止滚动更新时全部下线。5.2 多集群、多 zone 的 Corefile 组织方式当你有多个集群或者需要区分内外网解析时Corefile 的 server block 组织就变得重要。常见做法是按 zone 分块cluster.local:53 { kubernetes cluster.local { pods insecure fallthrough in-addr.arpa ip6.arpa } cache 30 } example.internal:53 { forward . 10.0.0.53 cache 60 } .:53 { forward . /etc/resolv.conf cache 30 loop reload }这样cluster.local走 Kubernetes 插件example.internal转发到内网 DNS其余走系统上游。每个 block 独立配置缓存和插件互不干扰。要注意的是多个 block 监听同一端口时coreDNS 按最长后缀匹配所以cluster.local会优先于.命中顺序不用刻意排。5.3 从自建 DNS 迁移到 coreDNS 的注意事项如果是从老的自建 DNS 迁过来有几件事要提前想清楚。第一记录兼容性老系统里可能有手写的静态 A 记录、CNAMEcoreDNS 用hosts插件或file插件承接迁移前把记录清单导出来逐条核对。第二TTL 策略老系统 TTL 可能设得很长迁移时统一收敛到合理值避免切换后旧记录残留。第三灰度切换先把部分节点的--cluster-dns指向新 coreDNS观察一段时间再全量别一刀切。第四回滚预案保留老 DNS 的配置和运行状态出问题能快速切回。我个人的体会是coreDNS 的学习曲线主要卡在插件链顺序和fallthrough 语义这两点上一旦理解配置起来比传统 DNS 清爽得多。真正难的不是写 Corefile而是排查问题时能快速定位到是解析器行为、网络策略、还是 coreDNS 配置的锅——这需要你对/etc/resolv.conf的 search 和 ndots 机制有清晰的认识。最后分享一个小技巧本地调试 Corefile 时用coredns -conf Corefile -dns.port 1053起一个非特权端口的实例配合dig 127.0.0.1 -p 1053测试不用动生产环境就能验证配置语法和插件行为改完再上线能省掉大量重启等待的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑