DNS 解析故障排查实战:超时、污染与本地缓存

当业务突然报「无法连接上游」「接口偶发超时」,第一反应往往是网络或代码问题,但根因经常藏在 DNS 解析故障 里。DNS 解析故障排查的第一步,是确认请求到底卡在「网络不通」还是「名字解析不出来」——两者表现相似,修复路径却完全不同。本文从本地解析链路、dig 逐层定位、超时/污染/缓存三大典型故障,到容器环境排障,给出一套可直接照做的实战清单。

一、先确认是不是 DNS 的问题

排查前先用一个对照实验区分两类故障:如果用 IP 直连正常、用域名就失败,基本锁定是解析层;如果 IP 也连不通,则先回到网络层(路由、防火墙、SELinux 导致的服务异常排查 等)。另一个快速信号是错误文案:Name or service not known、Temporary failure in name resolution 直接指向解析;Connection timed out 更偏网络。

确认方向后,再决定把时间花在解析链路上,而不是无谓地改应用代码——很多「偶发超时」其实是本地 stub resolver 串行重试多个不可达 DNS 服务器造成的,与业务毫无关系。

在真实业务里,这种故障的表现千奇百怪:命令行 curl 偶尔卡在「Trying …」数秒;浏览器刷新有时秒开、有时转圈;微服务网关报「upstream connect error」,但后端进程其实健康。它们共享同一个根因——域名在某个时刻没被及时解析出来。先把这一层摘出来,后面所有排障才有方向。

二、看清本地解析链路:resolv.conf 与 systemd-resolved

2.1 /etc/resolv.conf 的配置陷阱

Linux 默认通过 /etc/resolv.conf 拿到 DNS 服务器。常见坑有三个:options timeout: 与 attempts: 太小导致很快放弃;写了多个 server 但 glibc 默认串行查询,一个不可达就要等满超时;rotate 没开时永远优先第一个。一个稳健的配置示例:

# /etc/resolv.conf(稳健基线)
nameserver 223.5.5.5
nameserver 119.29.29.29
options timeout:2 attempts:2 rotate
options ndots:1

说明:timeout:2 attempts:2 把单次等待从默认 5 秒压到 2 秒、重试 2 次;rotate 让多个 server 轮流承担,避免单点拖垮全部请求。

2.2 systemd-resolved 与 nsswitch 的分工

较新的发行版由 systemd-resolved 充当 stub resolver,resolv.conf 里往往只有一行 nameserver 127.0.0.53。真正的上游写在 /etc/systemd/resolved.conf。应用能否用到它,还取决于 /etc/nsswitch.conf 里 hosts: 行是否包含 resolve 或 dns。

# 查看 stub resolver 状态与上游
resolvectl status
resolvectl query example.com
# nsswitch 中应包含 resolve/dns
grep hosts /etc/nsswitch.conf
# hosts: files resolve [!UNAVAIL=return] dns

三、用 dig / nslookup 逐层定位

3.1 指定 DNS 服务器做对比

怀疑本地链路有问题时,直接指定公共 DNS 对比结果,能立刻判断是「上游坏了」还是「本地配置错了」:

# 用系统默认链路解析
dig +short example.com
# 强制走某个公共 DNS 解析
dig @223.5.5.5 example.com
dig @8.8.8.8 example.com +stats

如果 @223.5.5.5 正常、默认链路失败,问题在本地 stub resolver 或 resolv.conf;如果所有服务器都失败,则是网络出口或域名本身的问题。

3.2 看耗时与 TTL

+stats 会打印 Query time,偶发高延迟往往就是应用层「偶发超时」的元凶;同时关注 ANSWER 区的 TTL,它决定了下游缓存能续命多久。

dig example.com +stats
;; Query time: 312 msec
;; SERVER: 223.5.5.5#53(223.5.5.5)
example.com.  300  IN  A  93.184.216.34

四、三大典型故障:超时、污染、缓存

4.1 解析超时(串行重试的隐形杀手)

最隐蔽的超时来自 glibc 对多 server 的串行查询:第一个不可达要等满 timeout*attempts,应用线程被长时间阻塞。修复就是前面给出的 timeout/attempts/rotate 基线,必要时在应用侧(如 Java 的 networkaddress.cache.ttl)做正解缓存,减少反复查询。

应用侧缓存是把双刃剑:缓存太久会让 DNS 变更迟迟不生效,缓存太短又抵消了防护效果。经验值是内网服务 30–60 秒、对公网依赖 10–30 秒,并配合滚动发布逐步切换,而不是一次性改解析。这样即便某次解析抖动,影响也被限制在缓存窗口之内。

4.2 DNS 污染与劫持

当不同网络下解析结果不一致、或返回明显错误的 IP,要怀疑污染。用 TCP 53 或 DoH/DoT 规避 UDP 被篡改,并对比多个独立递归服务器:

# 走 TCP 查询,规避 UDP 中间篡改
dig +tcp example.com @223.5.5.5
# 对比三个独立递归,结果不一致即可疑
for s in 223.5.5.5 119.29.29.29 1.1.1.1; do echo -n "$s: "; dig +short example.com @$s; done

4.3 本地缓存不刷新

改了 DNS 记录但站点迟迟不生效,多半是缓存作祟:可能是 systemd-resolved、nscd 或 dnsmasq 在兜底。清理命令因组件而异:

# systemd-resolved
resolvectl flush-caches
# nscd
sudo nscd -i hosts
# dnsmasq
sudo systemctl restart dnsmasq

五、容器环境里的 DNS(Docker / K8s)

容器 DNS 是最常见的「宿主机正常、容器内报错」场景。Docker 默认把宿主 resolv.conf 注入容器,但宿主若只有 127.0.0.53,容器内就无法递归。可在 /etc/docker/daemon.json 显式指定上游:

{
  "dns": ["223.5.5.5", "119.29.29.29"]
}

K8s 则通过 Pod 的 dnsConfig 覆盖默认 nameserver,集群内还依赖 CoreDNS。若 Pod 内解析失败,先 kubectl exec 进去 dig 复核(参见 Docker 实战系列 的网络模型)。

# Pod 内自定义 DNS
dnsConfig:
  nameservers: ["10.0.0.2"]
  options:
    - name: ndots
      value: "2"
  searches:
    - default.svc.cluster.local

六、实战排障清单

现象优先检查命令
域名解析失败resolv.conf / resolved 状态resolvectl status
偶发超时timeout/attempts/rotatedig +stats @公共DNS
结果被篡改污染/劫持dig +tcp 多服务器对比
改记录不生效本地缓存resolvectl flush-caches
容器内报错daemon.json / CoreDNSdocker inspect --format

七、根因归类与预防

故障层根因预防手段
客户端配置多 server 串行超时收紧 timeout/attempts + rotate
缓存层resolved/nscd 旧记录合理 TTL + 变更后 flush
传输层UDP 被污染DoT/DoH 或 +tcp
容器层stub 不可递归daemon.json 显式 dns

把上面这套链路固化成监控:对核心域名做 dig +stats 探活,把 Query time 与解析成功率打点到 Linux 服务器安全与基线加固 配套的监控体系里,DNS 解析故障就能从「半夜救火」变成「图表上一目了然」。

八、把解析健康纳入可观测性

DNS 解析故障最可怕的不是发生,而是「悄悄发生、偶发发作」。一个实用的做法是把解析探针做成定时任务:每分钟对核心域名发起 dig +stats,把 Query time 与解析成功率上报。当某个递归服务器开始偶发超时,图表会比用户投诉早数小时亮起红灯,运维得以在影响扩散前介入。

在微服务与网关场景里,解析抖动还会被放大成雪崩:一个被依赖的域名解析变慢,上游连接池被占满,进而拖垮整条调用链。因此在 Nginx 反向代理与网关 层,除了关注后端响应,也应该把「上游域名解析耗时」纳入告警阈值,而不是等到 5xx 暴涨才去翻日志。

最后提醒一句:改完配置务必验证。不要只看 systemctl restart 返回成功,而是用 dig 与 resolvectl query 实际打一发,确认链路真的通了——很多「我明明改过了」的工单,最后都卡在改了配置却没生效、或被更上层的配置覆盖。把「改完即验证」写成发布清单的一项,能省下大量互相指责的时间。

上一篇 Java Record 实战:用 record 关键字写出不可变数据类
下一篇 Gradio 实战:用 Python 快速搭建 AI 演示界面
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]