
DNS 这东西平时没人注意它一旦出问题整个集群就像断了线的风筝——服务明明活着就是互相找不到。我最早接触 coreDNS 是在一个自建的小集群里当时服务间调用时通时不通排查了大半天才发现是 DNS 解析超时导致的。从那以后我花了不少时间把 coreDNS 的配置、插件链、缓存机制和排错方法摸了一遍。这篇内容就是把这些年踩过的坑和验证过的配置整理出来适合正在用容器编排平台、需要自己维护集群内域名解析的运维和开发同学也适合刚接触 coreDNS、想搞明白它到底怎么运转的朋友。读完你至少能搞清楚三件事coreDNS 的请求是怎么一步步被处理的、Corefile 里那些插件到底谁先谁后、以及解析出问题时该从哪儿下手查。1. coreDNS 到底在集群里扮演什么角色很多人第一次看到 coreDNS是在集群里发现有个叫kube-dns的 Service后面挂着一组 coreDNS 的 Pod。名字叫 kube-dns实际跑的却是 coreDNS这个历史遗留的命名让不少人困惑。要理解 coreDNS 的定位得先搞清楚集群里的 DNS 解析到底解决什么问题。1.1 从一次服务调用失败说起假设你在集群里部署了两个服务服务 A 要调用服务 B代码里写的是http://service-b.default.svc.cluster.local。这个域名在集群外根本不存在只有集群内部的 DNS 才能解析。当 A 发起请求时它的 DNS 查询会先被节点的解析配置指向 coreDNS 的 ClusterIPcoreDNS 收到查询后根据配置的插件链去查对应的 Service 记录返回一个 ClusterIP 或者 Pod IPA 才能拿到地址发起真正的连接。这个过程听起来简单但里面有几个关键点第一coreDNS 本身也是跑在集群里的 Pod它怎么保证自己高可用第二它怎么知道某个 Service 对应哪个 IP第三外部域名比如访问公网 API的查询它又怎么处理。这三个问题分别对应了 coreDNS 的部署形态、Kubernetes 插件和 forward 插件。我见过不少团队在集群刚搭起来时没关注这些等到服务规模上来DNS 查询量一大问题就集中爆发了。提示集群里那个叫kube-dns的 Service 是给业务 Pod 用的解析入口它的 ClusterIP 会被写进每个 Pod 的/etc/resolv.conf所以别看到名字就以为是老组件实际后端是 coreDNS。1.2 coreDNS 和传统 DNS 服务的本质区别传统 DNS 服务比如 BIND配置是一堆 zone 文件改一条记录要动文件、reload 服务。coreDNS 完全换了个思路它把 DNS 服务拆成一条插件链每个插件负责一类查询的处理逻辑请求进来后沿着插件链依次经过谁匹配上谁处理。这种设计的好处是扩展性极强想加个自定义解析逻辑写个插件挂上去就行坏处是插件顺序错了行为就完全不对而且这种错误往往很隐蔽。举个直观的例子cache插件放在kubernetes插件前面和后面效果完全不同。放前面缓存的是最终解析结果放后面缓存的是 kubernetes 插件查出来的结果。大部分场景下我们希望缓存最终结果所以cache一般靠前。这种顺序即逻辑的设计是理解 coreDNS 的第一道门槛也是后面配置排错的核心线索。1.3 一个请求在 coreDNS 内部的完整旅程把一次 DNS 查询想象成进了一栋办事大厅。请求从Server块进来先经过最外层的插件比如errors负责记录错误、log负责打日志然后进入cache看有没有现成答案有就直接返回没有就继续往下走。接着到kubernetes插件它判断这个域名是不是集群内的服务域名通常是cluster.local后缀是的话就去 API 里查对应的 Service 或 Endpoint 记录不是的话就交给forward插件转发给上游 DNS 服务器。这个流程里每一步都可能出问题cache的 TTL 设太短导致查询量下不去kubernetes插件的pods模式配置不当导致解析到已销毁的 Podforward的上游地址写错导致外部域名全挂。我在实际维护中总结出一条经验coreDNS 的问题八成出在插件顺序和插件参数上剩下一成半是上游网络最后半成才是 coreDNS 自身 bug。所以排查时先看 Corefile再看网络最后才怀疑组件本身。2. Corefile 的写法与插件链的排序逻辑Corefile 是 coreDNS 的核心配置文件语法看起来像 nginx 的配置块但里面的门道不少。很多人照着模板抄一份能跑起来但一旦要加自定义逻辑就懵了根本原因是没搞懂 Server 块和插件链的关系。2.1 Server 块决定了谁来处理哪类请求Corefile 的基本结构是若干个 Server 块每个块以域名:端口开头大括号里是插件列表。比如.:53 { errors health ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } forward . /etc/resolv.conf cache 30 loop reload loadbalance }这个块的意思是监听 53 端口处理所有域名.表示根域的查询。里面的插件从上到下构成处理链。注意kubernetes插件后面跟的cluster.local in-addr.arpa ip6.arpa是它负责的域名后缀只有这些后缀的查询才会进这个插件其他的会fallthrough到后面的forward。这里有个容易踩的坑如果你有多个 Server 块coreDNS 会根据请求的域名和端口匹配最具体的那个块。比如你有一个.:53的块和一个cluster.local:53的块那么cluster.local后缀的查询会优先匹配后者。这个匹配规则和插件的fallthrough是两套机制混在一起理解很容易出错。我的建议是初期只用一个.:53块把所有逻辑放在里面等确实有性能隔离需求再拆块。2.2 插件顺序不是随便排的插件链的执行顺序就是它们在 Corefile 里出现的顺序但这个顺序不是完全自由的coreDNS 内部对某些插件有固定的排序约束。比如cache插件如果放在kubernetes后面它缓存的就是 kubernetes 的查询结果这在多数场景下没问题但如果放在forward后面那外部域名的查询结果也会被缓存这通常也是期望的行为。真正需要注意的是loop插件它必须放在靠前的位置用来检测转发环路放太后面可能检测不到。我整理了一份常见插件的推荐顺序供参考插件推荐位置作用放错位置的后果errors最前记录错误错误日志缺失health靠前健康检查端点探针失败ready靠前就绪检查流量过早打入cachekubernetes 之前缓存解析结果缓存命中率低kubernetes中间集群内域名解析集群域名解析失败forward靠后转发外部查询外部域名解析失败loop靠前检测转发环路环路检测失效reload最后配置热加载改配置需重启这张表不是死规矩但按这个顺序排绝大多数场景不会出问题。我见过有人把forward放在kubernetes前面结果集群内域名全被转发到上游上游当然不认识cluster.local直接返回 NXDOMAIN服务间调用全挂。这种错误排查起来很费劲因为日志里只看到 NXDOMAIN看不出是顺序问题。2.3 fallthrough 与插件匹配的微妙关系fallthrough是 coreDNS 里一个高频出现但容易误解的指令。它的作用是当当前插件无法处理某个查询时把查询交给链上的下一个插件。听起来简单但实际行为取决于插件的实现。以kubernetes插件为例它只处理自己配置的后缀比如cluster.local对于其他后缀的查询默认行为是返回 NXDOMAIN 还是 fallthrough取决于有没有显式配置fallthrough。我在一个多租户集群里遇到过这样的问题不同租户用不同的子域名后缀比如tenant-a.cluster.local和tenant-b.cluster.local。如果kubernetes插件只配置了cluster.local那么tenant-a.cluster.local的查询会被它匹配到但它查不到对应记录默认返回 NXDOMAIN根本不会走到后面的forward。解决办法是在kubernetes插件里显式加上fallthrough让查不到的查询继续往下走。这个细节在官方文档里写得不显眼但实际多租户场景下几乎必踩。注意fallthrough后面可以跟具体的后缀列表表示只对这些后缀的查询做 fallthrough。不写后缀表示对所有查不到的查询都 fallthrough。后者更省事但可能把本该返回 NXDOMAIN 的查询也放过去需要根据实际场景权衡。3. kubernetes 插件集群内域名解析的核心kubernetes插件是 coreDNS 在容器编排平台里最重要的插件没有之一。它负责把集群里的 Service、Endpoint、Pod 信息转换成 DNS 记录。理解它的记录生成规则和几种查询模式是排查集群内解析问题的关键。3.1 Service 记录是怎么生成的当一个 Service 被创建时kubernetes插件会为它生成几条 DNS 记录。以default命名空间下名为my-svc的 Service 为例会生成my-svc.default.svc.cluster.local指向 Service 的 ClusterIPA 记录my-svc.default.svc.cluster.local的 SRV 记录包含端口信息my-svc.default.svc和my-svc.default等短域名形式这里有个细节短域名形式比如my-svc能不能解析取决于 Pod 的resolv.conf里的search域配置。默认情况下Pod 的search域会包含namespace.svc.cluster.local、svc.cluster.local、cluster.local这几项所以同命名空间内直接写my-svc就能解析。跨命名空间调用就得写全my-svc.other-ns.svc.cluster.local或者至少my-svc.other-ns。我见过有人抱怨同命名空间能解析跨命名空间不行排查半天以为是网络策略问题其实是域名写短了。这种问题在日志里表现为 NXDOMAIN但根因是 search 域没匹配上。判断方法很简单进到 Pod 里cat /etc/resolv.conf看search行有哪些后缀再对照你写的域名就知道短域名能不能用了。3.2 Endpoint 记录与 headless Service 的特殊行为普通 Service 的 A 记录返回的是 ClusterIP但 headless ServiceclusterIP: None不一样它的 A 记录返回的是所有后端 Pod 的 IP 列表。这个行为由kubernetes插件的endpoint相关配置控制。headless Service 常用于有状态服务比如数据库集群客户端需要拿到所有节点地址自己做负载均衡或选主。这里有个坑headless Service 的 A 记录返回的 Pod IP 列表顺序是不保证的而且会随 Pod 的增减动态变化。如果你的客户端代码假设第一个 IP 是主节点那迟早出问题。正确做法是通过 SRV 记录拿到带权重的列表或者干脆用专门的服务发现机制。另外headless Service 的解析结果默认不带 TTL 缓存优化查询量大时对 coreDNS 压力不小建议在kubernetes插件里配置合适的 TTL。3.3 pods 模式的取舍insecure 还是 verifiedkubernetes插件有个pods配置项控制 Pod 记录的生成方式。可选值有disabled、insecure、verified。disabled表示不生成 Pod 记录insecure表示生成 Pod 记录但不校验 Pod 是否真的在运行verified表示生成前会去 API 校验 Pod 状态。这个选择直接影响解析的准确性和性能。insecure模式下即使 Pod 已经销毁只要 API 里还有残留记录比如删除过程中的短暂窗口DNS 仍可能返回它的 IP导致请求打到不存在的地址。verified模式会多一次 API 查询准确性高但增加延迟和 API 压力。我的经验是对解析准确性要求高的场景比如金融类业务用verified对性能敏感、能容忍短暂不一致的场景用insecure而大多数普通业务其实用disabled就够了因为业务通常通过 Service 访问不需要直接解析 Pod 域名。提示pods模式只影响形如pod-ip.namespace.pod.cluster.local的记录不影响 Service 记录。如果你从来没在代码里直接写过 Pod 域名这个配置改不改影响不大。4. 缓存、转发与上游那些让解析变慢或变错的配置coreDNS 的性能和正确性很大程度上取决于cache和forward这两个插件的配置。它们看起来简单但参数背后的逻辑值得细说。4.1 cache 的 TTL 与 prefetch 怎么调cache插件的基本用法是cache 30表示缓存 30 秒。但实际可配的参数远不止 TTL还有success、denial、prefetch、serve_stale等。其中prefetch和serve_stale是最容易被忽略但最有价值的两个。prefetch的作用是当某个域名在 TTL 过期前被查询达到一定次数coreDNS 会提前异步刷新缓存避免过期瞬间的查询穿透。配置形式是prefetch 10 60s 10%意思是 60 秒内被查询 10 次以上且剩余 TTL 低于 10% 时触发预取。这个配置对高频访问的域名效果明显能显著降低解析延迟的抖动。serve_stale的作用是当上游 DNS 不可达时返回过期的缓存结果而不是直接报错。配置形式是serve_stale 1h表示最多返回 1 小时内的过期缓存。这个在跨机房或上游不稳定的场景下非常有用能避免上游抖动导致整个集群解析失败。我负责的一个集群曾经因为上游 DNS 短暂不可用导致所有外部 API 调用失败加上serve_stale后这类故障的影响面小了很多。参数典型值适用场景注意事项success30-300正常解析结果缓存太短增加上游压力denial30-60NXDOMAIN 缓存太长导致新域名解析延迟prefetch10 60s 10%高频域名阈值太低反而增加查询serve_stale1h上游不稳定过期数据可能不准确4.2 forward 的上游选择与策略forward插件负责把非集群域名的查询转发给上游。最常见的写法是forward . /etc/resolv.conf表示使用节点上的 resolv.conf 里的上游服务器。这个写法在大多数环境里能用但有个隐患节点的 resolv.conf 可能被其他配置修改导致上游地址变化。更稳妥的做法是显式写上游地址比如forward . 8.8.8.8 1.1.1.1或者指向内网的自建 DNS。forward还支持策略配置比如policy random、policy round_robin、policy sequential。默认是random在多上游场景下能分散压力。如果上游有主备关系用sequential更合适它会按顺序尝试主挂了才用备。另外health_check参数可以配置健康检查自动剔除不可用的上游这个在生产环境建议开启配置形式是health_check 5s。4.3 上游解析失败的典型表现与定位上游解析失败在业务侧的表现往往是偶发超时或部分域名解析不了。定位时先看 coreDNS 的日志如果开了log插件能看到每个查询的响应码。SERVFAIL通常表示上游不可达或超时NXDOMAIN表示域名不存在可能是真的不存在也可能是上游配置问题REFUSED表示上游拒绝查询常见于上游有访问控制。我排查这类问题的固定套路是先在 coreDNS Pod 里用dig 127.0.0.1 example.com直接查本地确认 coreDNS 自身是否正常再用dig 上游IP example.com直接查上游确认上游是否可达最后对比两者结果就能定位是 coreDNS 配置问题还是上游网络问题。这个三步法看起来笨但比盲目看日志高效得多。5. 解析出问题时的排查链路DNS 问题的特点是现象在业务根因在配置或网络排查时最忌讳一上来就改配置。我总结了一条从现象到根因的排查链路按这个顺序走大部分问题能在十分钟内定位。5.1 第一步确认 Pod 的 resolv.conf 是否正确所有解析问题的排查都应该从 Pod 内部开始。进到出问题的 Pod 里cat /etc/resolv.conf重点看三行nameserver是不是 coreDNS 的 ClusterIPsearch域是否包含预期的后缀options ndots是多少。ndots这个参数很关键它决定了域名在尝试绝对解析前会先拼接多少个 search 后缀。默认是 5意味着如果一个域名里点的数量少于 5会先尝试拼接 search 后缀这可能产生大量无效查询。比如你查api.example.com它有两个点小于 5那么 coreDNS 会先收到api.example.com.default.svc.cluster.local、api.example.com.svc.cluster.local等一串查询全部 NXDOMAIN 后才查api.example.com本身。这不仅增加延迟还让 coreDNS 日志里充满无效查询。解决办法是在 Pod 的dnsConfig里把ndots调小比如设为 2或者查询时在域名末尾加个点表示绝对域名。5.2 第二步在 coreDNS 侧验证解析结果确认 Pod 的 resolv.conf 没问题后进到 coreDNS Pod 里直接查。用dig 127.0.0.1 目标域名看返回结果是否符合预期。如果这里能解析但 Pod 里不能问题在 Pod 到 coreDNS 的网络链路上可能是网络策略、Service 的 Endpoint 异常或者 kube-proxy 规则问题。如果这里也不能解析问题在 coreDNS 配置或上游。这一步的关键是区分coreDNS 自身能不能解析和Pod 能不能通过 coreDNS 解析。前者查配置后者查网络。我见过不少案例是 coreDNS 配置完全正确但 Pod 的nameserver指向了一个已经失效的旧 ClusterIP导致所有解析超时。这种问题在集群重建或 coreDNS Service 重建后特别容易出现。5.3 第三步看插件链是否按预期执行如果 coreDNS 自身解析也不对就要看插件链了。最直接的方法是临时在 Corefile 里加log插件观察查询经过了哪些插件、最终由谁响应。log插件的输出格式里会带上响应码和处理的插件名能快速定位是kubernetes插件没匹配上还是forward转发失败。另一个技巧是用dig的trace参数虽然它主要用来追踪公网 DNS 解析路径但在 coreDNS 场景下也能看出查询在哪个环节断了。不过trace对 coreDNS 的插件链支持有限更可靠的方式还是看log输出。我一般会在排错时临时把log和errors都打开问题定位后再关掉避免日志量过大影响性能。5.4 第四步检查上游与网络连通性如果插件链执行正常但结果不对就要怀疑上游了。在 coreDNS Pod 里dig 上游IP 域名确认上游是否可达、是否返回正确结果。如果上游不可达检查网络策略、防火墙规则、路由表。如果上游可达但结果不对检查上游的配置或者确认是不是被中间设备劫持了查询。这里有个容易被忽略的点coreDNS 的forward插件默认会继承节点的 DNS 配置如果节点本身配置了多个上游coreDNS 会随机选一个。如果其中一个上游有问题就会出现时好时坏的现象。解决办法是显式指定可靠的上游或者开启health_check自动剔除故障上游。6. 生产环境下的稳定性配置经验把 coreDNS 跑起来不难难的是让它在大流量、多变更的环境下稳定运行。这一节分享几个我在生产环境里验证过的配置和做法。6.1 资源限制与副本数的权衡coreDNS 的资源消耗主要来自查询量和缓存大小。一个中等规模的集群几百个 Pod单个 coreDNS 实例给 100m CPU 和 70Mi 内存通常够用。但要注意coreDNS 是延迟敏感型服务CPU 限制太低会导致查询排队表现为解析延迟抖动。我的经验是 CPU 限制不要低于 100m内存不要低于 70Mi副本数至少 2 个并且配置反亲和性让它们分散到不同节点。副本数的选择还要考虑故障域。如果集群跨多个可用区coreDNS 副本应该每个区至少一个避免某个区网络隔离时该区的 Pod 全部解析失败。另外coreDNS 的 Service 默认是 ClusterIP 类型kube-proxy 会在每个节点上做负载均衡所以副本数不需要和节点数一一对应但副本太少会导致单点压力过大。6.2 监控指标里最该关注的几个coreDNS 暴露的 Prometheus 指标里有几个是必须配告警的。coredns_dns_request_duration_seconds的 P99 延迟超过 100ms 就说明有性能问题coredns_dns_responses_total按响应码分类SERVFAIL比例超过 1% 就要关注coredns_cache_hits_total和coredns_cache_misses_total的比值反映缓存效果命中率低于 80% 说明缓存配置可能有问题。还有一个容易被忽略的指标是coredns_forward_healthcheck_failures_total它反映上游健康检查的失败次数。这个指标持续增长说明上游不稳定即使当前解析还正常也应该提前处理。我一般会把这个指标和serve_stale配合使用上游失败时先靠过期缓存顶着同时告警通知人去修上游。6.3 配置变更的安全流程Corefile 的变更风险很高一个插件顺序调整就可能让整个集群解析异常。我的做法是所有 Corefile 变更先在一个测试集群验证验证通过后再走灰度。灰度的方法是先改一个 coreDNS 副本的 ConfigMap观察一段时间至少 10 分钟的解析成功率和延迟没问题再全量。coreDNS 的reload插件支持配置热加载改完 ConfigMap 后大约 30 秒生效不需要重启 Pod这给灰度提供了便利。另外Corefile 一定要纳入版本管理每次变更记录变更原因和验证结果。我经历过一次事故某次为了临时解决一个问题加了个插件事后忘了删几个月后这个插件和新加的配置冲突导致解析间歇性失败。从那以后我们规定所有临时配置必须带注释和过期时间定期清理。注意reload插件虽然方便但它只监控 Corefile 的变化不监控 Corefile 里引用的外部文件比如通过import引入的配置。如果用了import改被引入的文件不会触发 reload需要手动重启或 touch 一下 Corefile。7. 几个真实场景下的配置片段光讲原理不够直观这一节放几个我在实际环境里用过的配置片段每个都附上适用场景和注意事项可以直接参考。7.1 单集群标准配置这是最通用的配置适合大多数单集群场景.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } forward . /etc/resolv.conf { max_concurrent 1000 health_check 5s } cache 30 { success 60 denial 30 prefetch 10 60s 10% serve_stale 1h } loop reload loadbalance }几个关键点health的lameduck 5s让 Pod 在退出前继续服务 5 秒避免滚动更新时的解析中断forward的max_concurrent 1000限制并发转发数防止上游被打爆cache的serve_stale 1h在上游故障时兜底。这套配置我在多个集群用过稳定性不错。7.2 多租户场景的域名隔离多租户集群里不同租户的服务域名需要隔离避免互相解析。做法是用多个 Server 块每个租户一个后缀tenant-a.local:53 { errors kubernetes cluster.local { pods disabled fallthrough } forward . /etc/resolv.conf cache 30 } tenant-b.local:53 { errors kubernetes cluster.local { pods disabled fallthrough } forward . /etc/resolv.conf cache 30 }这里的关键是每个块只处理自己后缀的查询fallthrough让查不到的查询继续走forward。注意kubernetes插件里的cluster.local是它负责的域名后缀和 Server 块的后缀是两回事别搞混。这种配置下租户 A 查tenant-b.local的域名会被匹配到tenant-b.local块实现隔离。7.3 上游故障时的降级配置如果上游 DNS 不稳定可以用serve_stale加health_check做降级.:53 { errors health ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } forward . 10.0.0.10 10.0.0.11 { policy sequential health_check 3s max_fails 2 } cache 30 { serve_stale 2h prefetch 5 30s 20% } loop reload }policy sequential让 coreDNS 按顺序尝试上游主上游挂了才用备health_check 3s和max_fails 2让故障上游在 6 秒内被剔除serve_stale 2h让上游全挂时还能用 2 小时内的过期缓存顶着。这套配置适合上游质量一般但业务不能停的场景。8. 写在最后的几点个人体会coreDNS 这个组件配置看起来简单但真正吃透需要理解它背后的插件链模型和 DNS 协议本身。我刚开始维护时也走过弯路比如把cache放在kubernetes后面导致缓存效果差或者忘了配fallthrough导致多租户域名解析失败。这些坑踩过之后我形成了一个习惯每次改 Corefile 之前先在纸上画出插件链标出每个查询会经过哪些插件、在哪个插件被处理画完再动手改。这个笨办法帮我避免了很多低级错误。另外一个体会是DNS 问题的排查一定要有耐心按链路一步步走不要跳步。我见过太多人一上来就怀疑 coreDNS 有 bug结果查到最后发现是 Pod 的ndots配置或者网络策略问题。coreDNS 本身其实很稳定绝大多数问题都出在配置和网络环境上。把排查链路走熟比记住一堆配置参数更有用。最后说个实用的小技巧如果你不确定某个 Corefile 配置的行为可以用coredns -conf Corefile -dns.port 1053在本地起一个实例用dig 127.0.0.1 -p 1053测试完全不碰生产环境。这个本地验证的习惯能让你在改生产配置前心里有底。