先从一个我实际踩过的坑说起。某天排查一个偶发超时故障服务A调用服务B走的是集群内部域名明明两端Pod都活着可请求就是会随机卡上几秒甚至直接失败。折腾半天最后定位到CoreDNS的副本数是1且没接任何监控告警——节点一抖动整个集群的DNS解析就跟着遭殃。从那以后我才意识到很多K8s用户对CoreDNS的理解停留在“它就是个DNS服务器”但它在集群里的真实地位相当于整个服务发现链路的中枢神经。服务间能不能互相找到、找到之后走哪个IP、Pod里的域名解析规则是什么、请求外部站点时怎么兜底全都和CoreDNS直接相关。这篇文章不聊虚的就围绕CoreDNS在K8s里最核心的4个作用展开结合我实际运维和排障中的经验把原理、配置和坑一起讲清楚。看完你至少能回答三个问题CoreDNS到底干了什么活、它的配置怎么改才安全、出了问题第一步该查哪里。1. CoreDNS在K8s里的4大关键作用一句话概括CoreDNS在Kubernetes集群里承担了四件核心事集群内服务发现的DNS解析入口、Pod DNS配置的标准答案、外部域名的转发出口、以及基于插件生态的灵活扩展点。这四件事环环相扣缺少任何一项集群的网络模型都不完整。先看一张总览表心里先有个全貌后面再逐个展开。作用对应场景关键机制直接受益对象服务发现的DNS解析服务A访问服务B的ClusterIPService名到IP的自动映射、A/SRV记录集群内部微服务Pod DNS配置的标准每个Pod里的/etc/resolv.conf自动注入DNS配置、search域、ndots集群内所有Pod外部域名转发Pod访问外部站点forward插件转发到上游DNS需要出集群的请求插件化扩展能力自定义域、重写、缓存、日志Corefile插件链平台管理员、开发团队我第一次完整理解这四件事是某次给客户做集群网络方案评审。当时对方平台组提了一个需求让集群内的监控组件可以直接用内部域名访问某个自建系统但这个系统不在集群里还有独立的内部DNS。听完需求我就明白这其实就是在CoreDNS上做扩展——加一个Zone配置再通过forward插件把解析请求转出去。CoreDNS的定位就是这样你看着它只是个容器里的DNS进程实际上它替你回答了集群里所有关于“名字是什么”“这个IP是谁”的问题。之后再遇到Pod之间调用失败我的第一反应就是检查CoreDNS而不是直接去看应用日志。因为如果DNS挂了表面上表现为“服务器没响应”实际根因完全不在应用层。2. 服务发现的底层ClusterIP对应的域名是怎么来的服务发现是CoreDNS最基础也最核心的作用。整个K8s集群里Pod创建销毁频繁IP地址不稳定如果靠写死IP来通信根本没法维护。于是K8s抽象出了Service给一组Pod绑定一个稳定的ClusterIP再往这个IP上挂一个稳定的名字。别人只要记住名字就行了IP变了不重要只要Service在解析就不会断。2.1 标准域名结构和A记录集群内部的域名遵循一套固定格式服务名.命名空间.svc.cluster.local。比如在default命名空间下有一个服务叫web那它的完整域名就是web.default.svc.cluster.localCoreDNS会自动生成对应的A记录指向Web服务的ClusterIP。这套命名规则是根级的集群内部所有DNS解析都跑在cluster.local这个私有域里和外部互联网域名完全隔离不会互相干扰。我经常被问一个问题为什么K8s里很多服务间的调用地址并不写成完整域名只写web或者web.default就够了这就牵涉到search域机制后面会展开。简单说Pod里的解析器会按search域列表自动去拼完整域名所以就算是简写也能定位到目标Service。这也是为什么很多新手会在Pod里执行ping web、curl web能通但换到宿主机上就ping不通——宿主机根本不在这个域里。2.2 SRV记录在状态服务里的用处除了A记录CoreDNS还会为Service生成SRV记录。SRV记录解决的是“一个服务有哪些端口可以访问”的问题。K8s里一个Service可能会声明多个端口SRV记录会把这些端口信息给到支持SRV的客户端让客户端不用在配置里写死端口号。举个例子一个Service暴露了HTTP端口和gRPC端口SRV记录里就会分别记录这两个端口对应的主机名和端口号。像Consul、Eureka这类服务发现体系也有类似机制K8s只是把这块工作交给了DNS。凡是支持DNS SRV的服务比如部分微服务框架就可以直接利用这个能力做动态端口探测减少配置项。2.3 无头服务Headless Service的特殊行为无头服务值得单独拿出来讲因为它的DNS行为跟普通Service完全不一样。普通Service会解析出ClusterIP无头服务则直接解析出后端的Pod IP列表。这就等于说服务名后面挂的不再是一个VIP而是一组真实的Pod地址。这个设计在中间件场景里很有用。比如集群里部署了多副本的有状态数据库客户端希望直接和每个副本建连而不是通过负载均衡VIP绕一层。把Service设为无头模式后客户端解析服务名就能拿到所有副本的IP自己来决定连哪台、怎么做重试自由度极高。对应到CoreDNS它就是返回多条A记录而不是返回一个ClusterIP。实际配置中识别无头服务很简单看YAML里是不是有clusterIP: None。之前帮一个团队排查数据库连接抖动问题发现他配的是普通Service连上VIP以后连接被随机转发导致会话状态丢失。改成无头服务后客户端按SRV记录直连具体Pod问题直接消失。这就是CoreDNS在路由行为上带来的连锁反应理解和配置之间往往只差一个“原来如此”。2.4 CoreDNS如何感知Service变化这里需要说一个隐藏的关键点CoreDNS并不是靠扫描或配置文件来维护记录的它是通过监听Kubernetes的API Server来实时感知Service和Pod变化的。集群里只要创建、删除、修改ServiceAPI Server都会把变更事件推给CoreDNS内置的Kubernetes插件插件更新本地记录解析请求立刻生效。全程不需要人工介入也不用reload配置。正因为这个机制CoreDNS在正常工作状态下几乎不需要人为操作。做完一次服务发布新Service的域名马上就能解析。这也是我判断CoreDNS是否健康的一个重要参考维度新建一个Service后立刻解析超时或者失败都说明问题不小不只是配置层面的瑕疵而是连API Server的通信或缓存都出了状况。这个检测步骤完全可以写进集群的健康检查清单里成本极低但见效很快。3. Pod内部DNS配置CoreDNS是每个容器的“默认网关”如果说服务发现解决的是“怎么找到别的服务”那DNS配置解决的就是“每个Pod自己怎么问路”。K8s之所以能做到开箱即用是因为每个Pod创建时kubelet会往容器的/etc/resolv.conf里写入一套标准DNS配置。这套配置的nameserver直指CoreDNSsearch域列表覆盖namespace.svc.cluster.local、svc.cluster.local和cluster.local这就是全网Pod都能用短域名互调的真正原因。3.1 解析顺序和search域Pod内发起一个请求时解析器会按resolv.conf里的配置处理。假设Pod在default命名空间search域依次是default.svc.cluster.local、svc.cluster.local、cluster.local。当你在Pod里访问web时解析器会先尝试web.default.svc.cluster.local再尝试web.svc.cluster.local最后试web.cluster.local命中后返回结果。所以“为什么Pod里能用短域名”的解释其实是三层拼接nameserver指向CoreDNSsearch域补齐完整域名CoreDNS最终返回正确的IP。这个顺序很重要因为读配置和看日志的时候很多人看到“解析尝试了多个域名”会以为程序出bug了其实只是search域在按顺序探测。3.2 ndots参数对解析行为的影响/etc/resolv.conf里还有个参数叫ndots默认值是5。它控制的是“域名里包含几个点才先走DNS查询否则先走search域拼接”。说的更直白点当你访问web这种名字点的数量少于5解析器会先按search域拼接成完整域名后再查询当你访问api.example.com这种带点的域名点的数量有2个还是少于5解析器依然会先拼接search域导致产生一次额外查询。这个参数在日常开发里造成的最大误会就是“为什么我在Pod里访问公网域名总会先多等一次超时”。举个例子容器里访问www.example.com系统会先尝试www.example.com.default.svc.cluster.local这个域名不存在CoreDNS会立刻返回NXDOMAIN然后才去查真实的公网域名。如果访问的是不存在的内网域名就会被search域反复探测拉长耗时最坏情况能看到好几秒的延迟。如果你在做离线部署或自建镜像仓库建议在Pod的YAML里通过dnsConfig显式调整ndots比如改成ndots: 2或者ndots: 1减少无意义的search拼接。这个调整不会影响内网短域名的解析因为凑到那个点数的域名会直接走DNS查询而短域名不管是拼接还是直接查询最终都能解析。我实测下来把ndots改小后容器里访问外部域名至少省掉一次无谓查询在批量调用场景里优化效果相当可观。3.3 独立配置Pod的DNS策略K8s的Pod DNS策略分几种Default、ClusterFirst、ClusterFirstWithHostNet和None。默认是ClusterFirst表示容器优先使用集群的DNS服务也就是CoreDNS。Default表示继承宿主机配置None完全自定义。最容易被忽略的是ClusterFirstWithHostNet——当Pod使用宿主机网络模式时会遇到这种情况直接沿用ClusterFirst策略的解析行为可能受宿主机网络环境影响所以专门提供一个策略让DNS行为保持和常规Pod一致。这里有个我见过的经典翻车现场有人把Pod设成hostNetwork: true但没有同步调整dnsPolicy结果容器里的域名解析走到了宿主机配置上集群内Service名全解析不出来。原因就是hostNetwork模式下Pod共享宿主机的网络命名空间而ClusterFirst策略默认只对常规网络模式生效。解决办法很简单把dnsPolicy换成ClusterFirstWithHostNet让Pod用它自己的resolv.conf。4. 外部域名访问forward插件与上游DNS配置K8s集群不是孤岛。Pod里跑着的应用经常会请求外部站点、调用外部API、拉取镜像或上报监控数据。这些流量无论如何都要离开集群而CoreDNS在其中扮演的角色就是“出口代理”——它把集群内部的解析请求拆成两类集群内域名自己处理集群外域名转发给上游DNS也就是通常提到的公共DNS或私有的DNS服务器。这就是CoreDNS的第三个关键作用外部域名的转发策略和出口管理。4.1 Corefile里的解析出口CoreDNS的配置核心是Corefile默认挂在ConfigMap里。我们要看的就是这里。一个典型的Corefile大概长这样.:53 { kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } forward . /etc/resolv.conf cache 30 errors }逐行解释一下。.:53表示对所在的根域进行处理也就是所有不以cluster.local结尾的查询都走到这组规则里。kubernetes插件负责处理集群内部域名和反向解析请求。forward . /etc/resolv.conf则把剩余的请求转发到系统指定的上游DNS地址列表。最后的cache 30表示缓存查询结果30秒errors输出错误日志。很多人误以为forward .后面的/etc/resolv.conf是CoreDNS去读宿主机配置其实是CoreDNS容器内部默认生成的配置通常指向节点所在网络的上游DNS。在实际使用中我更倾向于在Corefile里显式写上上游DNS地址而不是用/etc/resolv.conf比如forward . 10.10.0.2 114.114.114.114。显式指定能让排查问题时少一层不确定性尤其是在多个节点DNS配置不一致的环境里踩过一次坑的人都能理解这种诉求。4.2 缓存与TTL的均衡策略请求转发到上游如果不做缓存每次都现场查公网域名性能上肯定会吃亏上游也容易被打爆。CoreDNS通过cache插件来做响应缓存。TTLTime To Live就是一个记录能被缓存的时间K8s集群内部的Service记录TTL默认比较短外部域名缓存则由管理员在Corefile里指定策略。缓存策略要结合业务权衡缓存太久域名解析变更后集群内应用可能感知延迟比如代码里没做重新解析还会一直访问旧的IP缓存太短查询压力大CoreDNS的负载相应提高。我给过一个平台的建议内部记录保持30秒外部公网记录保持30到60秒如果是访问量很大的静态站点可以拉到300秒。这个数值没有绝对标准但至少要在流量和新鲜度之间找到自己集群能接受的平衡区间。同时负缓存同样值得关注NXDOMAIN的缓存时间默认比较短配合ndots场景能显著减少无效查询的重复等待。4.3 多上游DNS与故障转移生产环境里只配一个上游DNS是有风险的。一旦上游DNS挂了整个集群对外域名解析全部瘫痪应用连外部站点全部报错。在这个方面CoreDNS的forward插件支持配置多个上游DNS它会自动做故障转移。比如配置forward . 10.10.0.2 114.114.114.114第一个不可达时会自动切换到第二个。我见过一个非常典型的案例集群的Pod访问外部API偶发失败排查了一圈最后发现上游配置了公司内部DNS和公共DNS但内部DNS只有一台且经常超时。由于CoreDNS的max_concurrent和并发限制策略没有调优一个超时请求拖累了整体命中率。后来把上游顺序调整为先公共DNS后内部DNS同时把超时时间调短并加了expire参数。改完之后偶发失败基本消失。这里有几个关键参数需要提一下max_concurrent控制同时发起的最大查询数超过的部分会排队如果上游慢队列会越堆越长CoreDNS的整体吞吐量跟着下降。建议根据集群规模和CoreDNS Pod数量合理设置这个参数比如按每个副本允许2000并发来估算太大了会让上游承受压力太小了又会形成瓶颈。5. 插件生态与自定义解析从“用它”到“改它”CoreDNS的第四个关键作用就是它不是一个封闭的DNS服务器而是一个可以自由组装的插件平台。所有解析逻辑都由插件链驱动你能想到的绝大多数需求都能通过组合插件实现。这也是CoreDNS能在K8s场景里取代旧版kube-dns的根本原因之一。5.1 Corefile插件就是积木CoreDNS插件的设计是链式的一个请求进来后会依次经过匹配的插件处理。常见的插件包括kubernetes负责集群内解析hosts负责读自定义hosts文件rewrite负责改查询名cache负责缓存forward负责转发log负责日志审计。插件就是处理请求的最小单元你按顺序配置它们就是在定义解析链路。我在给一个特定的内部系统做方案时遇到过一个场景一套旧系统已经迁移进集群但很多代码里写死了旧域名没有改。新系统通过Envoy网关已经能处理大部分流量但还有一个内部组件还是按就域名调用。改动代码太慢我就在Corefile里加了一条rewrite规则把旧域名解析成新服务的完整域名。拼上一行配置应用代码一动没动解析直接就通了。这种“不改代码只改解析”的灵活打法就是CoreDNS插件生态的价值。5.2 hosts插件与自定义域名映射hosts插件允许你直接在Corefile里定义域名映射关系效果等价于改/etc/hosts但作用范围是集群级的。比如你想把registry.internal映射到一个固定的内网IP不需要真的在内网DNS上建记录直接写进Corefile的hosts插件段集群内所有Pod都会拿到这个解析结果。.:53 { hosts { 192.168.1.100 registry.internal fallthrough } forward . 10.10.0.2 }这里可能需要注意的一个问题是hosts插件默认情况下如果查不到会直接返回NXDOMAIN而不再继续往下走所以如果业务里需要“先查hosts查不到再转发上游”一定要写fallthrough。这个细节很多细节都会漏结果是加了个自定义域名反而把正常公网域名解析搞挂了。5.3 rewrite插件与域名重写rewrite插件的使用场景比想象中更常见。除了刚才提到的旧域名迁移还有一个典型需求集群内Pod通过域名访问某个服务时希望CoreDNS把特定子域名的请求全部改写。比如配置rewrite name prefix api.old-example.com api.new-example.com匹配前缀后替换成新的域名再继续解析。这种重写在国内的微服务迁移场景里几乎就是标配尤其是新旧系统并存、逐步切换流量的阶段。你可以给不同的命名空间只改一条重写规则该转换的注意版本和顺序即可。rewrite插件放在配链条的前面否则可能已经被前面的插件处理掉规则就不会执行这个顺序性值得留意。5.4 扩展时对Corefile的版本管控配置里依然要提醒一件事在K8s里CoreDNS的配置通过ConfigMap挂载进来ConfigMap更迭后需要触发CoreDNS的滚动更新才会重新加载。你在本地测试时改了Corefile能生效不代表线上也能及时更新。这牵扯到一个易错点不少人手动kubectl editConfigMap后没有reload还疑惑配置为什么不生效。实际生产中的更稳妥做法是把Corefile变更走GitOps流程改代码仓库、走审查、自动应用到集群。如果集群里没上GitOps也至少要在本地保存一份Corefile的备份别只依赖集群里的ConfigMap。CoreDNS的配置管理属于“平时没人管出问题才想起来”的类型但它一旦出错受影响的可是整个集群的DNS所以格式化保存和变更记录还是很值得提前做好的。6. 常见问题与排查技巧实录CoreDNS相关的故障表象都是“网络不通”或“域名解析失败”但根因往往五花八门。我把自己踩过和帮人排查过的几类高频问题整理在这里按排查优先级排序一条条过一遍能省不少时间。6.1 现象集群内短域名解析失败但完整域名可以这个现象我在好几个环境里都遇到过。Pod里访问web报错但访问web.default.svc.cluster.local是通的。第一反应是search域的配置是否完整。进入Pod执行cat /etc/resolv.conf看看search域列表有没有包含当前命名空间的svc.cluster.local。如果真缺失大概率是Pod里的dnsPolicy被改动过或者容器内的resolv.conf被镜像里的内容覆盖了。有一个隐蔽的坑很多基础镜像会自己写/etc/resolv.conf导致Kubelet注入的配置被覆盖。解决办法是改Dockerfile别动resolv.conf或者用dnsConfig显式指定search域。6.2 现象解析外部域名特别慢偶尔超时这类问题十有八九出在ndots和上游DNS上。先用dig trace对比Pod内和宿主机上的解析耗时。如果Pod内明显更慢大概率是search域拼接带来的额外查询超时。临时验证的方法是在Pod里把域名后面加个点比如curl http://www.example.com./加点后绕过search域直接走FQDN查询响应明显变快就说明是ndots的坑。解决方法是调整dnsConfig里的ndots为2或1。不要一上来就大动Corefile逐层排除能更快锁定问题。我遇到过一个情况ndots调了也没用最后查出来是CoreDNS的单副本Pod被调度到了一台网络较差的节点上游DNS经常超时重试整体耗时被拖到了4秒以上。6.3 现象CoreDNS Pod重启频繁流量打满一个完整的症状群是CoreDNS Pod内存持续上涨、重启频发、请求延迟升高。这类问题通常是Query负载太高或缓存配置不合理。先看一下cache插件的配置和多副本数副本太少扛不住集群规模属于最常见问题。建议在集群规模超过50个节点或日请求量很大的情况下直接把CoreDNS副本数调到2个以上并给Pod设置合理的Requests和Limits。我这里给一个参考配置每个CoreDNS Pod分配0.25核和256Mi内存如果发现内存不够优先扩大内存而不是硬调副本数因为每个副本都有自己独立的缓存副本越多缓存命中率也会越分散。6.4 现象集群节点出现5秒超时这个问题在社区里讨论得很多现象是Pod内访问Service偶发卡5秒重试就恢复了。根因一般不在CoreDNS本身而在于节点的conntrack表满或DNAT冲突导致发往CoreDNS的UDP包被丢弃。检查方式是在节点上查看conntrack统计数据。如果确实有大量丢包解决路径通常是升级内核参数、调整net.netfilter.nf_conntrack_max或者更直接的方式在CoreDNS配置里改用TCP转发。有一个很实用的配置调整方案在Corefile的forward插件段加上prefer_udp或直接强制force_tcp可以明显降低UDP层丢包带来的解析超时。但是也要注意TCP连接带来的fd开销和新建连接频率也需关注。另外一个更彻底的做法是引入NodeLocal DNSCache在节点上缓存DNS解析结果绕过部分走iptables路径的重荷。6.5 排查步骤速查表排查目标操作核心观察点确认CoreDNS是否运行kubectl get pods -n kube-system -o wide副本数、State、就绪状态查看CoreDNS日志kubectl logs -n kube-system -l k8s-appkube-dns有没有报错、重试、超时确认Service暴露kubectl get svc -n kube-system kube-dnsClusterIP是否存在检查Pod里的resolv.confkubectl exec -it pod -- cat /etc/resolv.confnameserver是否指向CoreDNS用dig手动解析kubectl exec -it pod -- nslookup web.default.svc.cluster.local是否返回正确IP检查CoreDNS监控Prometheuscoredns_dns_requests_totalQPS、错误率、缓存命中率6.6 调优实操两个高价值版本的改法CoreDNS的可调项不少但据我观察绝大多数集群最受益的是这两项显式配置上游DNS和调大cache。核心片的调优模板如下.:53 { kubernetes cluster.local in-addr.arpa ip6.arpa { pods verified fallthrough in-addr.arpa ip6.arpa ttl 30 } rewrite name prefix api.old-example.com api.new-example.com cache 60 forward . 10.10.0.2 114.114.114.114 { max_concurrent 1000 } errors log }我把TTL从默认的5秒调成了30秒内部服务记录在DNS里被缓存更久大幅减少CoreDNS的查询压力。cache调到60秒时外部域名和内部域名都在一个缓存区间在大部分业务场景下足够安全。max_concurrent设置的1000也是基于二副本承载压力选的如果是单副本建议调低否则并发过高可能导致CPU和内存吃紧。这里所有参数的设置都不是拍脑袋要么跑过压测要么基于监控数据来调整。7. 最后关于要不要用NodeLocal DNACache很多集群在CoreDNS扛不住或解析延迟明显时会考虑NodeLocal DNACache。它相当于在每个节点上部署了一个DNS缓存代理Pod的DNS请求先到本机缓存缓存未命中才转发给CoreDNS。这个方案能有效降低CoreDNS的负载还能减少跨节点通信带来的延迟和丢包概率。但我不建议新手一上来就加法因为引入它相当于在Pod和CoreDNS中间多了一层转发故障点也相应增多。它适合把CoreDNS部署、缓存效果、资源压力这几个核心问题都排查过还是觉得不够的情况。如果一次故障都还没分析清楚就直接上很可能把问题从“CoreDNS超时”变成“NodeLocal缓存数据不一致”调起来更吃力。我个人的经验是先把CoreDNS本身做稳把副本数、缓存、上游DNS、ndots调整好再观察几天。如果延迟和负载指标都在可接受范围没必要急着上中间缓存层。凡是多一层组件撤下来的时候就多一份割裂。总结回到开头那次让服务间调用随机超时的故障。最后我做的事情其实就三件把CoreDNS副本从1扩到3、显式配置上游DNS、把Pod的ndots从默认调整为2。三件事做完故障消失集群的解析延迟也从偶发的秒级降到了稳定个位数毫秒。CoreDNS不是K8s里最炫的组件但它真出问题时牵一发动全身。你在群里看别人说什么“服务发现失败”“域名解析超时”最终的根因往往都落在它身上。希望这篇拆解能让你在下次碰到DNS问题时不再先从应用日志猜起而是能精准地一层层排查下去。