在一次应用容器化迁移后,监控面板显示部分服务调用P99延迟从原来的80ms升到400ms,而各服务自身处理时间没有明显变化。通过链路追踪定位到耗时发生在服务发现后的DNS解析阶段。进一步抓取CoreDNS Pod日志与请求指标,发现同一域名的解析请求数量远高于预期,且部分请求返回时间超过200ms。这类问题在中小规模集群中并不少见,根因通常是CoreDNS默认缓存配置偏保守,无法有效吸收查询流量。下面围绕解析慢的表现、CoreDNS缓存机制和参数优化展开。

一、如何判断解析慢来自CoreDNS缓存
排查DNS解析慢首先要确认耗时是否集中在CoreDNS处理阶段。可以在集群内启动一个临时Pod,使用dig命令多次解析同一个Service域名,观察每次返回时间。如果第一次解析耗时较高,后续解析耗时明显下降,说明缓存可能未命中或TTL过短。执行以下命令可以对同一域名连续发起100次解析请求,统计耗时分布:
for i in $(seq 1 100); do dig +short +time=2 svc.default.svc.cluster.local @10.96.0.10; done
除了手动测试,更应该查看CoreDNS暴露的Prometheus指标。coredns_dns_request_duration_seconds指标记录了请求耗时分布,coredns_cache_hits_total和coredns_cache_misses_total则直接反映缓存命中情况。如果命中率长期低于70%,且平均解析耗时较大,基本可以判断缓存配置需要调整。还可以通过kubectl -n kube-system logs -l k8s-app=kube-dns查看日志中是否有大量上游转发记录。
另一个典型信号是上游DNS服务器负载异常升高。在CoreDNS配置了forward . /etc/resolv.conf时,缓存未命中会触发向上游递归DNS转发。如果集群内服务频繁查询外部域名,而缓存容量小或TTL被限制得很短,上游DNS会承受大量重复请求,解析延迟随之不可控。这时问题不在网络,而在CoreDNS的缓存策略。
二、CoreDNS缓存插件的默认行为与局限
CoreDNS默认部署的Corefile中通常包含一行cache 30。这行配置表示成功缓存的最大TTL被限制为30秒,否定缓存也采用同样上限。对于Kubernetes内部Service的A记录,TTL本身就较短,30秒可能够用;但对于外部域名,很多A记录TTL可达300秒甚至更长,被强制截断到30秒后,缓存会在更短时间内失效,导致相同查询反复回源。
除了TTL上限,默认缓存容量也可能不足。缓存插件内部使用哈希表存储记录,当缓存条目超过容量时,会按LRU策略淘汰旧记录。业务高峰时段如果查询的域名集合很大,频繁淘汰会造成命中率下降。此外,默认配置没有开启预取和过期服务功能,一旦记录过期且上游响应慢,客户端会直接感受到延迟。
下面是一个典型的默认Corefile片段,可以看到缓存配置非常简单:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
这种配置在小规模、低查询频率的场景下可以正常工作,一旦服务实例数增加或外部API调用频繁,默认值就会成为瓶颈。cache 30虽然写起来简单,但无法区分成功响应和否定响应的TTL,也无法根据业务特点设置预取策略,灵活性明显不足。
三、缓存参数配置调优实例
为了提升缓存命中率并降低上游请求量,可以使用缓存插件的完整配置块,分别设置成功缓存、否定缓存和预取行为。以下配置将成功缓存容量扩展到10000条,最大TTL提升到600秒,最小TTL保留30秒;否定缓存容量设为5000条,最大TTL为60秒,避免域名不存在的结果被长时间缓存。预取策略设置为在记录过期前30秒内,如果同一记录被查询5次以上,CoreDNS会自动刷新缓存,避免过期瞬间的集中回源。
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache {
success 10000 600 30
denial 5000 60 5
prefetch 5 30s 20%
serve_stale 1h
}
loop
reload
loadbalance
}
success后面的三个参数分别是容量、最大TTL和最小TTL。最大TTL设为600秒,允许长TTL域名保留更长时间;最小TTL设为30秒,防止某些异常短TTL记录占用过多空间。denial用于缓存NXDOMAIN等否定应答,如果集群内访问不存在的域名较多,适当设置否定缓存可以有效减少上游查询。prefetch可以在热门记录过期前主动更新,serve_stale则允许在刷新失败时暂时返回过期记录,避免上游故障导致解析完全失败。
修改配置后需要重启CoreDNS使配置生效。执行以下命令完成ConfigMap编辑和Pod滚动更新:
kubectl -n kube-system edit configmap coredns kubectl -n kube-system rollout restart deployment coredns
重启后观察CoreDNS Pod状态正常,再用之前的dig测试脚本验证解析耗时是否下降。一般情况下,将成功缓存最大TTL从30秒提升到600秒后,针对外部域名的重复查询命中率会显著提升,平均解析耗时可以从几十毫秒级别下降到几毫秒级别。
四、验证优化效果与持续监控
优化完成后不能只凭感觉判断效果,需要结合监控数据持续跟踪。可以通过Prometheus查询CoreDNS的缓存命中率指标,例如sum(rate(coredns_cache_hits_total[5m])) / sum(rate(coredns_cache_requests_total[5m]))。如果命中率稳定在90%以上,说明缓存策略有效。同时关注coredns_dns_request_duration_seconds_bucket的P99分位数变化,确保解析延迟不再出现大幅抖动。
在测试环境中还可以做对比压测。使用相同数量的并发查询请求,分别测试默认cache 30和优化后配置的解析耗时分布。压测数据可以作为配置调整的参考依据,但生产环境差异较大,不能完全照搬。建议先在测试集群验证参数合理性,再灰度到生产集群。
还需要注意缓存配置不能过于激进。如果最大TTL设置过大,上游DNS记录发生变化后,集群内可能长时间使用旧记录,影响服务访问。对于依赖GSLB或频繁变更解析结果的域名,应适当缩短对应区域的TTL上限,或者使用fallthrough让特定域名走上游实时解析。缓存优化的核心在于平衡命中率与数据新鲜度,而不是单纯追求高命中率。
最后建议为CoreDNS设置资源请求和限制,避免缓存容量增大后占用过多内存。缓存条目增多会增加内存消耗,如果Pod内存限制过小,可能触发OOM。可以通过kubectl -n kube-system top pod -l k8s-app=kube-dns观察内存使用情况,并结合实际缓存命中率调整容量参数,找到最适合当前集群规模和业务特征的配置组合。