K8s Pod 里 DNS 解析偶发 5 秒超时?从 ndots 到 conntrack 的排查实战

举报
yd_243555368 发表于 2026/11/07 01:01:15 2026/11/07
【摘要】 接口耗时精确卡在 5 秒、同 Pod curl IP 却正常,多数是 DNS 问题。本文用 resolv.conf、dig 对比、tcpdump 抓包、conntrack -S 四步定性,讲清 ndots:5 如何把一次解析放大成 5 次查询,以及 CoreDNS 过载、UDP conntrack 五元组冲突两类丢包根因,最后给出 dnsConfig 收敛、NodeLocal DNSCache、客

现象:偶发 5 秒,重试就好

线上 Go 服务在 K8s 里跑得好好的,但每天总有几十次请求耗时精确卡在 5.00s,日志里躺着 dial tcp: lookup mysql-prod.internal on 10.96.0.10:53: read udp ...: i/o timeout 或 Python 的 getaddrinfo EAI_AGAIN。诡异的是:同一个 Pod 里 curl 10.20.3.11:3306 一秒通,业务高峰期问题明显,低峰期几乎不出现。第一次遇到很容易往"数据库慢"或"网络丢包"上猜,其实八成的锅在 DNS。

5 秒不是玄学。glibc 的 resolver 默认 timeout:5 attempts:2(容器里 /etc/resolv.conf 常常就这一行),一次 UDP 查询没响应就等 5 秒才重发,所以任何"只丢了第一个包"的场景,表现都是整齐的 5 秒。

第一步:先把 resolv.conf 看清楚

kubectl exec -it deploy/api -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search api.svc.cluster.local svc.cluster.local cluster.local corp.internal
# options ndots:5

重点是 ndots:5 这一行。它表示:只要域名里的点少于 5 个,就先把这个域名挨个拼上 search 后缀去查,全部失败才查原名。所以查一个 mysql-prod.internal(1 个点)实际会发出 5 次 查询:

mysql-prod.internal.api.svc.cluster.local  -> NXDOMAIN
mysql-prod.internal.svc.cluster.local      -> NXDOMAIN
mysql-prod.internal.cluster.local          -> NXDOMAIN
mysql-prod.internal.corp.internal          -> NXDOMAIN
mysql-prod.internal                        -> 命中

连接池里的长连接断了重连、每次 gRPC 调用都新建 http.Client,这些 NXDOMAIN 就在几毫秒内砸向 CoreDNS。CoreDNS 单副本扛不住时排队超时,或者上游 corp.internal 转发慢,第一个 UDP 包被丢掉,glibc 就干等 5 秒。

第二步:区分是"慢"还是"丢"

在 Pod 里直接对比三种查法的耗时,能立刻定性:

# 带 search 展开(默认行为),看总耗时
time nslookup mysql-prod.internal

# 关掉 search,直查全名,排除 ndots 影响
time dig +search +ndots=2 mysql-prod.internal @10.96.0.10 | grep -E "Query time|status"

# 重复 50 次统计,找出长尾
for i in $(seq 50); do dig +tries=1 +time=2 api.internal @10.96.0.10 | grep "Query time"; done \
  | awk '{print $4}' | sort -n | tail -5

如果 +ndots=2 之后耗时骤降、长尾消失,问题就在 ndots 放大;如果单次直查也有 5s 长尾,往 CoreDNS 或 conntrack 上查。

第三步:抓包看丢在哪一跳

# Pod 内抓 DNS,看有没有发出、有没有回包
kubectl exec -it deploy/api -- sh -c \
  'tcpdump -i any -nn "udp port 53" -c 40 -w /tmp/dns.pcap'
kubectl cp default/<pod>:/tmp/dns.pcap ./dns.pcap
tshark -r dns.pcap -Y "dns" -T fields -e frame.time_relative -e ip.src -e dns.qry.name -e dns.flags.rcode

看到"请求出了、回包没回来",基本都是 UDP conntrack 五元组冲突:短连接高频发 DNS,内核 NAT 表里同源端口复用了同一个五元组,回包被 DNAT 到别的 Pod。宿主机上验证:

conntrack -S | grep insert_failed
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

insert_failed 持续增长、conntrack_count 逼近 max,就是它了。

第四步:CoreDNS 侧有没有过载

kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system top pod -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200 | grep -iE "error|timeout|refused"

打开 Corefile 的 log、errors、cache 30 之后,日志里出现大量 plugin/forward: no upstream host 或 i/o timeout,说明上游 forward . /etc/resolv.conf 指向的节点 DNS 不稳。这时把上游换成两个可用的解析器,并给 cache 加上拒绝缓存:

kubectl -n kube-system edit cm coredns
#  .:53 {
#      errors
#      health
#      ready
#      kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure; fallthrough in-addr.arpa ip6.arpa }
#      cache 30
#      forward . 100.100.2.136 100.100.2.138 { max_concurrent 1000 }
#      reload
#  }

五、治理方案:从"能查"到"快查"

1)收敛 ndots。内部短名用 svc.cluster.local 全名,其余场景把 ndots 降到 2,查询量直接砍掉 4/5:

spec:
  template:
    spec:
      dnsConfig:
        options:
          - name: ndots
            value: "2"
          - name: timeout
            value: "2"
          - name: attempts
            value: "2"

2)上 NodeLocal DNSCache。每个节点跑一个 DNS 缓存,走本机 169.254.20.10,把跨节点 UDP 变成 loopback,顺手绕开 conntrack 竞争:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/admin/dns/nodelocaldns.yaml
kubectl -n kube-system get ds nodelocaldns
# 之后新建的 Pod resolv.conf 里 nameserver 应变成 169.254.20.10

3)应用层别每次请求都解析。Go 里复用一个 http.Client,或用 net.Resolver 配 PreferGo: true 并设置 Dial 超时;数据库客户端设 connectTimeout=2s 而不是默认无穷等。

4)容量。集群规模超过 1000 Pod 或 QPS 上 10 万,CoreDNS 至少 3 副本并打反亲和,--max-concurrent 调高,避免单点排队。

效果验证

# 改造前 CoreDNS 每秒查询数
kubectl -n kube-system logs -l k8s-app=kube-dns --since=1m | grep -c "A IN"
# 改造后再采一次,正常应下降 60%~80%
# 业务侧看 P99 与超时计数
kubectl exec -it deploy/api -- sh -c 'grep -c "lookup .* i/o timeout" /var/log/api.log'

我们在一个 800 节点的集群上做过这组操作:ndots 5→2 加 NodeLocal DNSCache,CoreDNS 峰值 QPS 从 4.2 万降到 9 千,接口 5 秒超时从每天 60+ 次降到 0。DNS 这类问题看着玄,其实只要抓住 resolv.conf、抓包、conntrack 三个入口,半小时内必能定位。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。