凌晨告警响起:核心站点从外部监控看”无法访问”,但服务器本身 CPU、内存、进程一切正常,直接用 IP:端口 去 curl 也能返回 200。问题被迅速从应用层拽回网络层——根因是 DNS 解析。本文复盘这次 DNS 解析故障排查,从 dig 到本地缓存与劫持定位,沉淀一套可复用的六步法,帮你下次少走弯路。
一、DNS 解析链路:请求从哪里来,到哪里去
一次域名解析并不只是”问一下 DNS 服务器”。从进程发起请求到拿到 IP,数据要穿过一长串环节:应用层 getaddrinfo → /etc/hosts → nsswitch 配置 → 本地缓存(nscd / systemd-resolved)→ 递归解析器(resolv.conf 里的 nameserver)→ 根/顶级域/权威服务器。任何一环出错,上层应用都只会看到一个笼统的”解析失败”。排查的第一步,就是把故障钉在某一环上。
# 绕过系统缓存,直接走系统解析栈看最终效果
getent hosts example.com
# 看本机解析顺序配置
cat /etc/nsswitch.conf | grep hosts
cat /etc/resolv.conf
二、第一步:用 dig 把故障钉在某一环
dig 是排查 DNS 的首选,因为它默认绕过系统缓存、直连指定解析器,还能打印每一级的耗时与路径。遇到”打不开”,先别急着改配置,先用 dig 看清楚到底卡在哪。
# 完整递归追踪,看卡在根/顶级域/权威哪一级
dig +trace example.com
# 指定公共解析器,排除本地 resolver 的干扰
dig @223.5.5.5 example.com +stats
# 只看答案与耗时,适合快速验证
dig example.com +noall +answer +stats
三、第二步:区分”解析失败”还是”解析慢”
监控常常把两者混为一谈,但它们是两套不同的根因。解析失败(SERVFAIL 或空回答)通常是配置、权威服务器或劫持问题;解析慢(单次超过 200ms 甚至超时)则指向上游延迟、网络抖动或本地缓存失效。dig 的 query time 是关键指标:本地缓存命中通常小于 1ms,正常递归 20–80ms,一旦超过 500ms 就该怀疑上游或网络链路。
| 现象 | 典型根因 | 优先动作 |
| 超时无响应 | 解析器不可达 / 防火墙拦 53 端口 | 换解析器、查网络与 服务器安全加固 规则 |
| 空回答(NOERROR 无记录) | 记录被误删 / CNAME 断裂 | 核对权威侧记录 |
| SERVFAIL | 递归服务器拒绝 / 权威异常 | 换公共 resolver 交叉验证 |
| 极慢(>500ms) | 上游链路抖动 / 缓存未命中 | 抓包看 53 端口、加本地缓存 |
四、第三步:本地缓存污染排查
最”诡异”的一类问题来自本地缓存:nscd、systemd-resolved,甚至浏览器都会缓存错误结果,导致”明明改了 DNS 配置却迟迟不生效”。先清缓存再下结论,能省掉大量无效改动。这部分排查套路,和我们之前在 连接池与端口耗尽排查 里强调的”先隔离变量再归因”是同一思路。
# 查看 systemd-resolved 状态与当前 DNS
resolvectl status
# 清空 systemd-resolved 缓存
sudo resolvectl flush-caches
# 查看 nscd 命中/未命中统计
nscd -g
# 临时停用 nscd 验证是否它的问题
sudo systemctl stop nscd
五、第四步:DNS 劫持与污染定位
当不同解析器返回了不同的 IP,就要怀疑劫持或污染。做法是多解析器交叉比对,并抓 53 端口明文流量看答案是否被篡改。对外的关键服务,优先切到 DoH(DNS over HTTPS)或 DoT(DNS over TLS)加密查询来规避明文污染。更底层的流量观察,可以配合 Wireshark 网络抓包 一起做。
# 同时查多个公共解析器对比答案
dig @8.8.8.8 example.com +short
dig @1.1.1.1 example.com +short
# 抓 DNS 明文包,看返回是否被改
sudo tcpdump -n -i any udp port 53
# 用加密解析器验证是否被污染
dig @https://dns.google/dns-query example.com +short
六、第五步:容器与 K8s 里的 DNS 陷阱
容器里解析失败,八成出在 /etc/resolv.conf:ndots 与 search 域名会把”短服务名”拼成错误的 FQDN,或 CoreDNS 本身异常。K8s 中 Service 名解析依赖 CoreDNS,它挂了整集群服务发现就崩。把 resolv.conf 与 CoreDNS 状态纳入常规巡检很重要,这也是 K8s Ingress 实战 之外另一处域名暴露的关键面。
# 容器内查看实际生效的解析配置
cat /etc/resolv.conf
# 查看 CoreDNS 是否健康
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
# CoreDNS 关键配置示例(ConfigMap)
# 放开日志便于定位,cache 超时按需调整
.:53 {
errors
log
cache 30
forward . /etc/resolv.conf
}
七、六步排查清单
把上面拆开的动作收敛成可照做的顺序,下次告警直接照着走:
| 步骤 | 动作 | 目的 |
| 1 | IP:端口直连 curl 验证 | 确认是 DNS 而非应用/网络 |
| 2 | dig 直连公共 resolver | 排除本地 resolver 干扰 |
| 3 | 多解析器交叉比对 | 判断是否劫持/污染 |
| 4 | flush-caches 清本地缓存 | 排除错误结果缓存 |
| 5 | tcpdump 抓 53 端口 | 看明文答案是否被篡改 |
| 6 | 检查容器 resolv.conf / CoreDNS | 覆盖容器场景陷阱 |
八、预防:别等故障再来翻
排查是亡羊补牢,预防才是正解。建议:用独立健康探测监控关键域名的解析延迟;对外域名配置多个 resolver 兜底,避免单点;对强依赖域名的服务,尽量用 IP 白名单加长 TTL 降低解析频率。若你的服务经由 Nginx 反向代理 的 upstream 域名,或挂了 Let’s Encrypt 证书,记得把 DNS 解析的健康度一起纳入巡检,否则证书续期或上游寻址失败会连锁拖垮整条链路。网络栈层面,Linux 内核参数调优 里的 TCP 与 backlog 设置也会间接影响解析超时表现,值得一并核对。
九、一个真实案例:被悄悄改坏的 resolv.conf
回到开头的那次告警。最终根因是某次排障时,有人在 resolv.conf 里把 nameserver 临时指向了一个已下线的内网解析器,事后忘记还原。外部监控走的是公网递归,能正常拿到 IP;而服务进程用的是本机 resolv.conf,全部返回 SERVFAIL,于是就出现了”IP 能通、域名打不开”的割裂现象。教训很朴素:任何解析器变更都要进配置管理,并用独立探测持续校验,而不是靠手改后凭记忆还原。
# 错误:指向已下线/不可达的解析器,进程解析全部失败
nameserver 10.0.0.53
# 正确:使用稳定可达的递归,并保留备用与超时参数
nameserver 223.5.5.5
nameserver 119.29.29.29
options timeout:2 attempts:3
还有一个容易踩的坑:多网卡或多容器环境下,不同进程可能读到不同的 resolv.conf。发布后一定要在”真实运行环境”里用 dig 复测,而不是只在跳板机上验证。把这次的六步法和这个案例结合起来,基本能覆盖八成以上的线上 DNS 解析故障。
DNS 故障表面是”打不开”,背后却横跨应用、系统、网络、容器四层。把六步法当成肌肉记忆,多数解析问题都能在十分钟内定位。你遇到过最离谱的一次 DNS 诡异现象是什么?欢迎在评论区聊聊。




