这篇文章记录我工作过程中处理过的一个我认为比较典型的故障吧,希望对社区的朋友有用。
线上集群的业务偶发 name resolution
报错,开发同学在 Pod 里跑 nslookup
和 curl
会随机超时。典型现象和错误截图如下:
业务侧报错栈:
http: proxy error: dial tcp: lookup kubernetes.default.svc on 10.96.0.10:53: read udp 10.244.0.4:57589->10.96.0.10:53: i/o timeout
。
这类报错常见于应用在访问 kubernetes.default.svc
或外网域名时由 DNS 延迟引发的超时,我使用 Google 在社区里发现了同款报错记录,链接如下:
CoreDNS 日志可见批量 i/o timeout
与 SERVFAIL
:
plugin/errors: 2 elasticsearch. A: read udp 10.244.235.249:44960->132.120.200.49:53: i/o timeout
,以及对集群内部 Service 的 AAAA/A 记录解析超时。
详细讨论可以在这个 Github issue 里看见。
在部分发行版中,还记录到转发上游超时后返回 SERVFAIL
的行为描述: Red Hat Customer Portal
在使用 ndots:5
默认配置时,解析外部域名出现额外延迟,nslookup
/curl
间歇变慢甚至超时,社区有多处实证讨论:
这些现象混在一起时,很容易让人误以为是 CoreDNS 本身不稳定。事实上,链路里任何一个环节的小抖动,都可能在应用侧呈现为 UnknownHostException
或 i/o timeout
。
为了把问题钉在板上,我在一套 3 节点的 Kubernetes 集群中,用 busybox
连续做 DNS 解析与 HTTP 探测:
kubectl run dnstest --rm -it --image=busybox:1.36 --restart=Never -- sh -c '
echo "nameserver check:"; cat /etc/resolv.conf;
i=0; while [ $i -lt 200 ]; do
date
nslookup www.google.com || echo "nslookup fail"
wget -qO- http://example.com >/dev/null 2>&1 || echo "curl fail"
i=$((i+1)); sleep 1
done
'
在问题出现的时间窗口内,可以稳定复现间歇性 nslookup: write to ... connection timed out
或 wget: bad address
。将相同循环脚本换到宿主机运行,失败概率显著降低,提示问题更偏向集群内部的 DNS 转发链路与配置。
resolv.conf
与 ndots
行为在 Pod 里:
cat /etc/resolv.conf
# 常见输出:
# search default.svc.cluster.local svc.cluster.local cluster.local
# nameserver 10.96.0.10
# options ndots:5
ndots:5
的含义是:当查询名包含小于 5 个点时,优先将其视为非绝对域名,逐个追加 search
后缀去尝试。这会对例如 s3.amazonaws.com
之类外部域名触发多轮内部后缀尝试,带来额外 DNS 往返,延迟与超时风险被放大,这是社区里反复被证实的高频坑点。
kubectl -n kube-system logs deploy/coredns
可见大量 read udp ... i/o timeout
与 SERVFAIL
,表明 CoreDNS 转发至上游的链路存在丢包或上游拥塞。
CoreDNS 转发超时约 6 秒后返回 SERVFAIL
,若上游 dnsmasq
等存在排队或丢包,短时间内同名查询还会持续 SERVFAIL
。
conntrack
与 UDP 特性DNS 默认走 UDP。节点 conntrack
表容量不足或回收不及时,会造成间歇丢包,从而在 Pod 侧表现为超时。内核日志可见 nf_conntrack: table full, dropping packet
。社区多次讨论通过调大 nf_conntrack_max
、调整超时、或引入 NodeLocal DNS 缓存来缓解。
当 CNI 的 MTU 设置过大,跨节点流量被分片且上游防火墙丢弃分片,也会出现 DNS 超时的间歇性现象。此类问题通常伴随跨子网访问外部 DNS 上游更容易超时;将 MTU 调小到底层网络可承受值能显著改善。这个方向常与特定云厂商网络链路相关,可结合厂商排障文档定位。
NodeLocal DNSCache
(推荐)NodeLocal DNS 将每个节点本地监听 DNS,Pod 对 DNS 的 UDP 查询会先命中本地缓存,向上游到 CoreDNS 的连接可升级为 TCP,降低 UDP 丢包带来的长尾延迟与超时。
启用步骤(适配常见 iptables
模式):
# 取得 kube-dns ClusterIP
KUBEDNS=$(kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}')
# 选一个本地监听地址,常用链路本地网段,例如 169.254.20.10
LOCALDNS=169.254.20.10
DOMAIN=cluster.local
# 下载官方示例后替换变量(伪代码示意)
# sed -i "s/__PILLAR__DNS__SERVER__/$KUBEDNS/g; s/__PILLAR__LOCAL__DNS__/$LOCALDNS/g; s/__PILLAR__DNS__DOMAIN__/$DOMAIN/g" nodelocaldns.yaml
kubectl apply -f nodelocaldns.yaml
启用后,Pod 的 nameserver
会指向本地缓存 IP 或通过 iptables
重定向,缓存命中降低延迟;上游到 CoreDNS 的链路采用 TCP,conntrack
回收有保障,长尾明显收敛。
ndots
做精确控制(按需)对需要频繁访问外部域名的工作负载,可将 ndots
调整到更小值,例如 1
或依据域名结构设为 2
。这能减少 search
追加带来的反复查询。配置示例:
apiVersion: v1
kind: Pod
metadata:
name: web-ndots1
spec:
dnsConfig:
options:
- name: ndots
value: "1"
containers:
- name: curl
image: curlimages/curl:8.8.0
command: ["sh","-c","while true; do curl -s https://www.google.com >/dev/null || echo DNSFAIL; sleep 1; done"]
ndots
调整后可以显著降低外部域名解析时的超时概率。
conntrack
与优化 UDP 超时(集群维度)在节点层面增加连接跟踪表容量、缩短 UDP 超时,能降低突发负载下的丢包。可使用一个 DaemonSet
以声明式应用:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: sysctl-conntrack
namespace: kube-system
spec:
selector:
matchLabels: {app: sysctl-conntrack}
template:
metadata:
labels: {app: sysctl-conntrack}
spec:
hostPID: true
containers:
- name: sysctl
image: busybox:1.36
securityContext:
privileged: true
command: ["sh","-c","
sysctl -w net.netfilter.nf_conntrack_max=1048576 &&
sysctl -w net.netfilter.nf_conntrack_udp_timeout=15 &&
sysctl -w net.netfilter.nf_conntrack_udp_timeout_stream=15 &&
sleep 3600000
"]
tolerations:
- operator: Exists
nodeSelector: {}
确保 CoreDNS 的 Corefile
使用合理的 forward
与 cache
,并关注上游可达性:
kubectl -n kube-system get configmap coredns -o yaml
# 例子(来自常见配置):
# forward . /etc/resolv.conf {
# max_concurrent 1000
# }
# cache 30
i/o timeout
多见于上游不可达或丢包;当转发到云厂商内置 IP 时(如 AKS 的 168.63.129.16
),需确认节点网络的放行与连通。
为了让业务侧也能快速自证,我收集了几类常见调用栈与自检脚本:
dial tcp: lookup index.docker.io on 192.168.65.1:53: read udp ... i/o timeout
。这类报错往往就是 DNS 查询超时,和我们在 Pod 内看到的现象一致。io.netty.resolver.dns.DnsNameResolverTimeoutException: [/100.64.0.10:53] query timed out after 5000 milliseconds
,或 SearchDomainUnknownHostException
。该异常族在 Netty socket.gaierror
或 requests.exceptions.ConnectionError
包裹的 DNS 失败,可用 try/except 精确捕获。 通用自检脚本(Pod 内运行):
# DNS QPS 与错误可视化
apk add --no-cache bind-tools >/dev/null 2>&1 || true
i=0; ok=0; fail=0
while [ $i -lt 100 ]; do
if dig +time=2 +retry=1 www.google.com @$(awk '/nameserver/{print $2;exit}' /etc/resolv.conf) >/dev/null; then ok=$((ok+1)); else fail=$((fail+1)); fi
i=$((i+1)); sleep 0.3
done
echo "ok=$ok fail=$fail"
在启用 NodeLocal DNSCache
与为关键工作负载设置 ndots:1
之后,nslookup
与 curl
的长尾超时完全消失,CoreDNS 的 i/o timeout
日志也从每小时几十条降为个位数。关于 NodeLocal 的官方原理图与效果说明,可以对比上游描述:本地缓存接收 Pod 的 UDP 查询,而到 CoreDNS 的链路可升级为 TCP,超时与尾延迟显著降低。
ndots:5
搭配长 search
列表会放大外部域名解析的时延与超时概率。对访问外部域名密集的工作负载下调到 1
或 2
,收益立竿见影。 i/o timeout
时,先验证上游 DNS 可达、CNI/路由与安全组是否放行,再看 Corefile 配置。 conntrack
表满是 UDP 超时的隐形元凶。线上请把容量与 UDP 超时配到位,或用 NodeLocal 避免 UDP 在内核表里堆积。 168.63.129.16
)若被 ACL/路由挡住,会在 CoreDNS 中集中表现为 i/o timeout
。网络侧放行是硬性前置条件。 musl
)在 ndots:5
环境下更容易暴露性能与超时问题,必要时更换基础镜像或调低 ndots
。 prometheus
指标与日志采样,结合 kubectl exec
的循环探测脚本,能用数据定位毛刺高峰期与受影响命名空间。 NodeLocal DNSCache
,按官方步骤替换变量并 kubectl apply
。spec:
template:
spec:
dnsConfig:
options:
- { name: ndots, value: "1" }
DaemonSet
调优 conntrack
,并在 Prometheus
抓 CoreDNS 与 NodeLocal 的指标。 forward
指向稳定的企业 DNS,或检查云厂商的专用 DNS 目标是否可达。 这类 Pod
内的 nslookup
与 curl
间歇性超时,表象在应用,根因却经常在 ndots
、conntrack
、上游 DNS、以及 UDP 的不可靠特性上叠加共振。把请求就近落在节点本地缓存、把 ndots
调整到和业务域名结构匹配的阈值、把节点与上游的网络通路打通,这三板斧能解决绝大多数场景。余下的少量长尾,再通过指标与日志去精修——而不是盲目地重启 CoreDNS 或扩大副本。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。