一次外部调用 P99 突然固定在 5 秒的故障,根因不是上游超时,而是 Pod 的 ndots 与遗留 search 域共同放大的解析等待。
某业务集群的订单服务调用外部支付网关时出现明显抖动。监控上,外部调用的 P99 从数十毫秒抬升到约 5 秒,超时错误集中在 pay-gateway.example.com 这条链路上;同一时间,访问集群内 redis.<namespace>.svc.cluster.local 的请求完全正常。故障并非持续存在:新启动的 Pod 第一次调用几乎必然慢 5 秒,运行一段时间后又可能恢复,因此最初被当成偶发的网络抖动。
应用日志里大量出现 context deadline exceeded 和连接建立超时,重试次数随之上抬。奇怪的是,从同集群另一个命名空间的 Pod 手工执行 curl,同样的外部域名却能快速返回;从故障 Pod 执行 getent hosts,则稳定卡住约 5 秒。参与排障的同事一度认为问题出在“某些节点到出口网关的链路”,但跨节点对比后无法复现固定规律。
先把解析行为和服务调用分开观察,是这类故障的分水岭:
kubectl -n <ns> exec <pod> -- cat /etc/resolv.conf
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.dnsConfig}'
kubectl -n <ns> exec <pod> -- sh -c 'time getent hosts pay-gateway.example.com'
kubectl -n <ns> exec <pod> -- sh -c 'time getent hosts redis.<ns>.svc.cluster.local'
故障 Pod 的 /etc/resolv.conf 里除了集群默认的三个 search 域,还多出一条 legacy.internal,并且显式保留了 options ndots:5。这两个细节在正常 Pod 上都不存在。
getent hosts 复现,确认每次首次解析稳定等待约 5.0 秒。legacy.internal 后缀的名字,等待约 5 秒后才发送真正的绝对域名查询。resolv.conf 与 dnsConfig,确认故障命名空间额外配置了遗留 search 域,且 ndots 为 5。legacy.internal 仍被转发到一个已下线的 DNS 地址。故障持续约 40 分钟,订单创建链路的峰值错误率约 12%,P99 达到 5 秒。由于客户端普遍配置了重试,支付网关侧看到的请求量约为正常值的三倍,但幂等键保证了没有产生重复扣款;真正受损的是用户等待时间和下单成功率。少数请求在重试耗尽后返回 504,用户需要手动重试。
影响范围呈现明显的命名空间差异:使用旧部署模板的订单、履约两个命名空间受影响,使用新模板的库存、账号服务不受影响。集群内服务互调、CoreDNS 的整体 QPS 和集群 DNS 解析均正常,因此监控大盘上只有“外部调用延迟”一个指标异常,很容易把排查方向引向外部依赖。故障期间值班同时处理大量超时告警,真正用于定位的时间被压缩,这也是后续要补自动化探测的原因。
第一个误导来自错误信息的形状。应用报的是连接超时,支付网关的状态页又恰好显示正常,于是“对方慢”与“我们出口有问题”两种猜测反复拉扯。实际上应用拿到的是解析超时,连接根本没有建立;只看应用异常栈里的 dial tcp 很容易忽略它前面的域名解析阶段。
第二个误导来自 CoreDNS 的 NXDOMAIN 指标。故障期间 NXDOMAIN 数量明显上升,看起来像 DNS 被刷或配置错误。但拆开查询日志后可以看到,这些 NXDOMAIN 大多来自 search 域展开产生的无效后缀查询,属于 ndots 机制的正常副作用;真正的问题是其中一条遗留后缀没有快速失败,而是把解析器拖到了超时。
第三个误导来自 dig。在故障 Pod 里执行 dig pay-gateway.example.com 很快返回,于是有人断定“DNS 没问题”。但 dig 默认不套用 resolv.conf 的 search 列表,走的是绝对域名查询;应用使用的 getaddrinfo 才会先按 ndots 和 search 域逐个尝试。加上 +search 后,5 秒延迟立刻复现,这个对比推翻了此前的判断。
最终推翻“上游或 CoreDNS 整体故障”的证据有三条:一是节点抓包显示 DNS 查询发出后存在完整的 5 秒空窗;二是 getent hosts 与应用行为一致,而 dig 不一致;三是带遗留后缀的查询稳定返回 SERVFAIL,去掉后缀或改用 FQDN 后立即恢复。三条证据指向同一个方向:解析器在 search 域上等待,而不是网络或上游服务慢。
根因由两层配置叠加而成。第一层在客户端:旧部署模板给 Pod 注入了 dnsConfig.searches: [legacy.internal],并保留了 ndots:5。pay-gateway.example.com 只有两个点,少于 5,解析器会先把它拼到每个 search 域后面依次查询,最后才查绝对域名。集群默认的三个后缀都能快速返回 NXDOMAIN,唯独 legacy.internal 的查询被转发到一个已下线的 DNS 地址,直到超时才继续。
第二层在集群 DNS:CoreDNS 的配置里仍保留 legacy.internal 的 server/forward 段,指向一个已经停止服务的上游解析器。该上游没有返回 NXDOMAIN,也没有立即拒绝,而是静默丢包,于是 CoreDNS 只能等到解析器超时。两层配置单独看都不致命:没有遗留 search 域就不会去查它;没有遗留转发段,查询会快速失败。两者同时存在,才形成稳定的 5 秒惩罚。
应用侧的连接池和 DNS 缓存进一步掩盖了问题。首次解析慢 5 秒后,结果被缓存,后续请求走已有连接,指标看起来恢复;一旦连接空闲被回收或 Pod 重启,延迟再次出现。这种“时好时坏”的表现让排查者反复怀疑网络抖动,而不是配置。严格说,ndots 本身不是 bug,它只是让解析器按设计去尝试 search 域;真正的错误是把已下线的基础设施留在了 search 与转发配置里。
止血要同时处理客户端和集群 DNS,只改一边可能只是把 5 秒变成另一种失败。客户端侧先去掉遗留 search 域,把 ndots 调到 2,并让外部调用使用末尾带点的 FQDN;FQDN 会跳过 search 展开,即使配置暂时没清干净也能快速返回。修改后按批次滚动重启,避免一次性把所有 Pod 的解析缓存清空造成尖峰。
spec:
template:
spec:
dnsConfig:
options:
- name: ndots
value: "2"
kubectl -n <ns> rollout status deploy/<name>
kubectl -n <ns> exec <pod> -- cat /etc/resolv.conf
kubectl -n <ns> exec <pod> -- sh -c 'time getent hosts pay-gateway.example.com'
kubectl -n <ns> exec <pod> -- sh -c 'time getent hosts redis.<ns>.svc.cluster.local'
集群侧先确认没有其他工作负载还依赖 legacy.internal,再从 CoreDNS 配置中删除对应转发段并重载。不要直接删除整个 Corefile 或重启所有副本,先在一台副本上验证 legacy.internal 的查询会快速失败,再推广。若短期无法改 CoreDNS,至少要让该域返回明确的 NXDOMAIN,而不是静默丢弃。
恢复后要做三项验证:外部 FQDN 首次解析在毫秒级;集群内短服务名仍能按 search 域解析;CoreDNS 的转发错误和延迟按 zone 拆分后不再出现 5 秒尖峰。最后回滚旧模板入口,确认新建 Pod 的 dnsConfig 不再带遗留域。
把 DNS 配置纳入发布前检查。准入策略可以拒绝包含已下线 search 域的 dnsConfig,也可以要求外部依赖使用 FQDN;部署模板中的 ndots 应由平台统一管理,而不是各服务自行复制。基础设施下线流程要增加一步:先确认没有 search 域、Corefile、应用配置引用该域名,再关闭 DNS 服务,最后才回收地址。
监控不能只看 CoreDNS 的总 QPS。应按 zone 观察转发延迟、SERVFAIL 和超时,并对“解析耗时接近 5 秒”这种量化特征单独告警。可以在每个命名空间运行一个轻量探针,周期性解析一个外部域名和一个集群内短名,记录首次解析与缓存后解析的差异;这比只看应用 P99 更早发现 search 域问题。变更管理上,CoreDNS 的 forward 段和 workload 的 dnsConfig 应互相关联,任何一方删除都要检查另一方。
+search 对比解析路径合上页面,先回答,再展开对照。
dig pay-gateway.example.com 很快,应用解析却稳定慢 5 秒?ndots:5 对一个只有两个点的外部域名意味着什么?dnsConfig?dig 默认不套用 resolv.conf 的 search 列表,直接查询绝对域名;应用走 getaddrinfo,会先按 ndots 依次拼接 search 域。带遗留后缀的查询被静默丢弃,解析器等到 5 秒超时后才查绝对域名,所以只有应用侧慢。legacy.internal,全部失败后才查询 pay-gateway.example.com.。如果某个后缀的权威服务器不响应,就会产生固定的超时延迟。
小程序版
微信扫码,随时查命令