容器集群内DNS缓存如果TTL设置过长或缓存层级混乱,会把已经下线的Pod IP持续返回给调用方,直接导致服务发现错误。
Kubernetes DNS缓存为什么会导致服务发现失败?
微服务A通过Service名称访问微服务B,B滚动更新时,旧Pod被终止,新Pod上线,但A所在Pod的DNS解析结果可能还保留着旧Pod的Cluster IP,这个现象像小区门卫手里的旧通讯录,住户已经搬走,他还告诉访客去敲那扇门。
缓存藏在多个位置,多数情况下并不是Kubernetes本身出错,而是叠加的缓存层把“旧地址”捂得太久。
- Linux系统层:nscd或systemd-resolved会缓存getaddrinfo结果。
- 应用程序层:Java的JVM默认缓存DNS解析结果30秒,部分Python库也有类似行为。
- 集群层:CoreDNS的cache插件默认缓存30秒。
- 节点层:NodeLocal DNSCache会缓存响应,降低CoreDNS压力。
这些层级叠加后,从Pod下线到所有调用方拿到新地址,延迟可能被放大到分钟级,服务发现失败通常不是“解析不到”,而是“解析到了旧地址”,请求打到已终止的Pod上,表现为连接超时或503错误。
容器集群DNS缓存和服务发现延迟对比
不同缓存配置对服务发现的影响差别很大,用一个表格对比常见方案:
| 缓存层级 | 默认TTL | 对服务发现的影响 | 适用场景 |
|---|---|---|---|
| 直连CoreDNS(无额外缓存) | CoreDNS cache 30秒 | 更新滞后但可控 | 小规模测试集群 |
| CoreDNS默认缓存 | 30秒 | 解析快,更新滞后30秒内 | 普通生产集群 |
| NodeLocal DNSCache | 通常5-30秒 | 节点级缓存,延迟明显降低 | 大规模集群 |
| 应用程序JVM缓存 | 30秒或更长 | 最容易忽略,更新滞后明显 | Java微服务 |
行业共识认为,NodeLocal DNSCache在降低DNS查询延迟的同时,需要额外维护一个DaemonSet,对于小集群反而增加复杂度,国内云原生集群做DNS优化时,通常会优先调整CoreDNS的cache TTL,而不是直接上NodeLocal DNSCache。
微服务容器DNS解析超时排查:先看哪一层缓存
遇到Pod间通过Service名访问超时,别急着重启Pod,按下面顺序排查,多数情况能在几分钟内定位到缓存层。
-
进入Pod执行解析命令:
nslookup service-name.namespace.svc.cluster.local观察返回的IP是否与预期一致。
-
对比Service的Endpoints:
kubectl get endpoints service-name -n namespace如果nslookup返回的IP不在Endpoints列表里,说明缓存里有旧地址。
-
检查Pod的
/etc/resolv.conf。options ndots:5会让短名称查询尝试多个搜索域,但这属于查询放大,不是缓存问题,缓存问题要看是否配置了use-vc或本地缓存代理。 -
查看CoreDNS配置:
kubectl get configmap coredns -n kube-system -o yaml找到
cache插件段落,确认success和denial的TTL值,默认30秒,如果被改大过,就会放大服务发现滞后。 -
如果是Java应用,用
jcmd或启动参数检查JVM的DNS缓存TTL,默认30秒,如果设置成-Dnetworkaddress.cache.ttl=-1,会永久缓存,这是微服务容器DNS解析超时排查里常见的坑。 -
检查NodeLocal DNSCache的Pod日志,如果NodeLocal缓存了过期记录,重启对应节点的NodeLocal Pod可以立即生效。
DNS缓存扰动服务发现的典型场景
四个场景在真实集群中反复出现,而且往往不是单个缓存层的问题。
- 滚动更新期间旧Pod IP残留:Deployment更新时,旧Pod被终止,但调用方的DNS缓存还指向旧Pod的Cluster IP,由于Service的Cluster IP通常不变,问题出在Endpoints变化后,旧Pod IP被缓存在应用层或节点层。
- HPA扩容后新Pod未及时被解析:扩容后新Pod已经Ready,但调用方仍然只解析到旧的Endpoints列表,如果应用程序缓存了Service的IP列表,不会主动重新查询。
- 跨地域集群通过外部DNS解析:地域节点缓存不一致,导致部分节点指向已经下线的Pod,国内Kubernetes集群DNS优化场景下,跨可用区的NodeLocal DNSCache如果上游配置不统一,问题更隐蔽。
- 长连接客户端不重新解析:gRPC或HTTP/2长连接建立后不会重新做DNS解析,除非连接断开,这类场景下,DNS缓存的影响被连接复用进一步放大。
如何配置Kubernetes DNS缓存降低服务发现故障
调整缓存不能一刀切,要按层配置。
CoreDNS层
修改coredns ConfigMap,把cache插件的TTL改小:
cache 5
这表示成功响应的缓存时间为5秒,对于频繁变更的Service,可以降到2秒,但会增加CoreDNS查询量。
应用层
Java应用显式设置JVM参数:
-Dnetworkaddress.cache.ttl=5 -Dnetworkaddress.cache.negative.ttl=0
Go程序默认不会缓存DNS结果,但需要注意使用net.Resolver时的自定义行为,Python程序在requests或urllib调用前,确保没有启用第三方DNS缓存库。
NodeLocal DNSCache
部署NodeLocal DNSCache后,每个节点上的本地缓存Pod接管Pod发起的DNS请求,它的好处是降低跨节点查询延迟,云原生DNS缓存成本方面,自建NodeLocal DNSCache比商业DNS缓存服务价格低,但需要额外维护DaemonSet和镜像更新。
Service配置
使用Headless Service可以直接返回Pod IP列表,让客户端自行处理负载均衡,这样绕过了Cluster IP的缓存问题,但要求客户端支持多IP选择。
设置publishNotReadyAddresses: true时,未就绪Pod的IP也会被返回,可能放大服务发现错误,除非有特殊要求,否则保持默认值false。
容器集群DNS缓存和服务发现常见问题
容器集群DNS缓存怎么清理?
不同层的清理方法不一样,CoreDNS可以通过重启Pod或发送SIGUSR1信号触发缓存重载,NodeLocal DNSCache需要重启对应节点的Pod,JVM缓存只能通过调整TTL参数并重启应用来清理,Linux系统层的nscd可以执行nscd -i hosts刷新。
Kubernetes服务发现用DNS还是环境变量?
DNS是主流方案,环境变量在Pod启动时注入,后续Service变化不会更新,不适合动态扩缩容场景,DNS可以动态解析Service对应的Endpoints,但需要关注缓存层带来的滞后。
国内Kubernetes集群DNS缓存优化有什么特别注意?
国内地域访问CoreDNS上游时可能存在跨网延迟,建议使用NodeLocal DNSCache并配置国内可达的上游DNS地址,例如云厂商提供的VPC DNS,NodeLocal DNSCache镜像应优先从国内镜像仓库拉取,避免部署时因网络问题失败。
NodeLocal DNSCache在多数云原生集群中将DNS查询延迟降低到毫秒级,这是业内专家指出的公开实践结论。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/642041.html




