适用场景

集群中的应用偶发报出 lookup xxx on 10.96.0.10:53: i/o timeoutSERVFAIL 或连接外部服务失败;重试后又恢复。故障通常集中在业务高峰,Pod 重建、扩容或切换节点后更明显。本文适用于使用 CoreDNS 作为集群 DNS,且 CoreDNS 需要转发外部域名查询的 Kubernetes 集群。

现象与影响范围

一次典型事故中,支付服务调用第三方 HTTPS 接口时约 1% 请求失败。应用日志显示:

dial tcp: lookup api.partner.example on 10.96.0.10:53: read udp 10.244.2.18:46157->10.96.0.10:53: i/o timeout

同一 Pod 立刻执行 nslookup 有时成功、有时超时;查询 kubernetes.default.svc.cluster.local 始终正常。这是重要分界:集群服务域名正常而外部域名异常,优先检查 CoreDNS 的 forward 链路、节点到上游 DNS 的网络和缓存策略,而不是先修改业务重试次数。

排查顺序

1. 在故障 Pod 中区分内部与外部解析

kubectl -n payments exec deploy/pay-api -- sh -c '
  cat /etc/resolv.conf
  nslookup kubernetes.default.svc.cluster.local
  nslookup api.partner.example
'

nameserver 应指向集群 DNS Service IP。内部域名失败时,检查 CoreDNS Service、Endpoint 和 kube-proxy;仅外部域名失败时,继续检查转发链路。不要在业务 Pod 里临时替换 /etc/resolv.conf,这会掩盖集群问题并造成后续漂移。

2. 查看 CoreDNS 日志、负载和转发失败

kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system logs deploy/coredns --since=15m | \
  grep -E 'timeout|SERVFAIL|plugin/errors'
kubectl -n kube-system top pods -l k8s-app=kube-dns
kubectl -n kube-system get configmap coredns -o yaml

日志中的 plugin/errors: 2 ... i/o timeout 表示 CoreDNS 等待上游响应超时。观察 CoreDNS 的 CPU、内存和重启次数:CPU 持续接近限额会让 DNS 请求排队,即使上游 DNS 完全健康也会出现超时。

3. 从 CoreDNS Pod 验证上游 DNS 可达性

先读取 Corefile 中 forward . 的目标。若使用 /etc/resolv.conf,还需确认节点文件没有被错误地指向 127.0.0.53 等 Pod 网络无法访问的本地 stub resolver。

kubectl -n kube-system exec deploy/coredns -- sh -c '
  cat /etc/resolv.conf
  nslookup api.partner.example 10.0.0.53
'

10.0.0.53 替换为实际的上游 DNS。若 UDP 查询超时,再用 TCP 查询对比:

kubectl -n kube-system exec deploy/coredns -- sh -c \
  'nslookup -vc api.partner.example 10.0.0.53'

UDP 失败但 TCP 成功,常见于节点防火墙、云安全组、MTU 分片或上游对 UDP 的限流;两者均失败则检查路由、网络策略和上游 DNS 健康。

修复方案

本次问题是业务流量突增时大量 Pod 同时查询同一批外部域名,上游 DNS 的 UDP 查询出现尾延迟,而 CoreDNS 缓存命中率不足。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
    }
    forward . 10.0.0.53 10.0.0.54 {
        policy sequential
        max_concurrent 1000
        health_check 5s
    }
    cache 30 {
        denial 5
    }
    loop
    reload
    loadbalance
}

cache 30 将成功响应最多缓存 30 秒,能吸收重复查询;denial 5 缩短 NXDOMAIN 缓存,避免刚创建的记录长时间不可见。max_concurrent 应大于峰值并发查询量,但不能无限增大,否则会把过载传递给上游 DNS。上游地址应至少两台,并按实际网络选择 sequential 或默认并发策略。

修改后执行滚动重启并观察恢复:

kubectl -n kube-system rollout restart deployment/coredns
kubectl -n kube-system rollout status deployment/coredns --timeout=120s
kubectl -n kube-system get endpoints kube-dns -o wide

不要直接删除全部 CoreDNS Pod;滚动重启能维持 DNS Endpoint 可用。若 CPU 已接近限额,优先增加副本并为每个副本设置可观测的资源请求和限额,再评估是否需要 CoreDNS autoscaler。

预防与监控

  1. 监控 CoreDNS 的请求速率、SERVFAIL、响应码、P99 延迟、进程 CPU 和容器重启次数;将外部域名超时与上游 DNS 的延迟放在同一看板。
  2. 为关键外部域名设置合适 TTL,避免业务代码在每个请求路径中重复解析域名。
  3. 发布网络策略、防火墙或节点镜像前,在节点和 CoreDNS Pod 两个视角做 UDP/TCP 53 端口连通性验证。
  4. 压测或大规模扩容前,评估 DNS QPS 和上游 DNS 的容量;缓存命中率下降往往比平均延迟更早暴露风险。

总结

Kubernetes DNS 间歇失败不等同于 CoreDNS 服务异常。先用内部、外部域名对比确定故障边界,再从 CoreDNS 日志、资源、上游连通性和缓存命中率逐层取证。明确上游、合理缓存、滚动发布和可观测告警,能把偶发解析超时从依赖重试的隐患变成可定位、可治理的容量问题。