一次 DNS 解析故障的完整复盘
这一篇复盘「站点突然全打不开」的一类典型故障。你遇到的可能是另一个域名、另一批服务器,但诊断链路和坑位是通用的:先分清是解析层还是 Web 层,再逐级往上追,最后别被 TTL 缓存骗了。
故障概述
早上开始,业务站点从部分区域访问超时,半小时后全站打不开。浏览器提示 ERR_NAME_NOT_RESOLVED,ping 域名直接报 cannot resolve。这不是 Web 服务器的问题——域名本身解析不出来了。
影响
- 所有对外访问域名全挂,不只是一两个子域名。
- 内部服务用 IP 直连的不受影响(这是第一个分界信号:能用 IP 的没事,用域名全挂)。
- 持续约 40 分钟,覆盖用户反馈到主动 flush 缓存恢复。
时间线
| 时间 | 事件 |
|---|---|
| T+0 | 首个区域反馈访问超时 |
| T+15 | 扩大为全站打不开,ping 报 cannot resolve |
| T+20 | 判定为解析层故障,开始 dig 逐级排查 |
| T+32 | 定位到上游权威 DNS 异常,开始修复上游 |
| T+38 | 上游恢复,dig 直连公共 DNS 验证通过 |
| T+40 | 主动 flush 本地缓存,全站恢复 |
根因
真正的触发点在上游权威 DNS:该域的权威 NS 之一在故障窗口内对部分查询返回 SERVFAIL,叠加两点放大器——
- TTL 缓存掩盖:本地/公共递归解析器把故障前的旧 A 记录缓存住了,TTL 未到期前继续吃旧值;上游恢复后,解析器仍按旧 TTL 走负缓存,恢复信号被推迟。
- 单一权威依赖:NS 记录只挂了少量权威,没有按地理/网络多线冗余,单个权威抖动就影响全量。
关键判据(也是复盘里的分水岭):dig @8.8.8.8 域名 在故障期间直连能返回,而走默认解析器不行——说明问题在权威/递归链路上,不在你的 Web 站。
诊断里最容易踩的几个坑
这次复盘里,真正拖慢恢复的不是「查不到」,而是诊断顺序和缓存干扰。几个真实教训:
- 先 ping IP 再 ping 域名。我第一反应是 ping 域名,报
cannot resolve后差点去重启 Web 服务。冷静下来先ping <web_ip>——IP 通、域名不通,才锁定是解析层。顺序反了就会白忙半天。 - 别只信一个解析器。
dig example.com默认走系统解析器,它的结果可能是缓存的旧值。必须再用dig @8.8.8.8、dig @1.1.1.1直连公共 DNS 交叉验证,才能区分「本地解析器问题」和「上游权威问题」。 - 负缓存同样会骗人。上游故障时,递归解析器会缓存 SERVFAIL 或 NXDOMAIN 一段时间(由 SOA 的 negative TTL 控制)。上游修好了,这些「坏结果」还能继续被吃到 TTL 过期,这也是为什么光修上游不够,必须手动 flush。
- hosts 文件与代理劫持。如果 dig 直连公共 DNS 也解析不到,但浏览器个别页面能开,要怀疑
--hosts文件条目、浏览器 DoH(加密 DNS)或系统代理在改写解析结果。getent hosts能看出系统层是否被 hosts 劫持。 - DNSSEC 失败会伪装成「解析慢」。开了 DNSSEC 的域如果签名链断,表现常是「偶尔超时、偶尔成功」,很容易被当成网络抖动。
dig +dnssec +noall +answer看ad标志是否置位,能快速排除。
修复
一步步定位,从最容易的排除法开始:
# 1) 先分清层:ping IP 通不通,域名能不能解析
ping -c2 <web_ip> # 通 → Web 层可能没问题
ping -c2 example.com # cannot resolve → 解析层
getent hosts example.com # 空 → 系统解析不到
# 2) 绕开本地解析器,直连公共 DNS
dig @8.8.8.8 example.com +short # 有 IP → 本地/上游递归问题
dig @1.1.1.1 example.com +short
# 3) 逐级追权威,哪一级断就在哪
dig +trace example.com | tail -20
# 观察最后一级权威:SERVFAIL / connection timed out → 权威 DNS 故障
# 4) 看 SOA/NS 与 DNSSEC 状态
dig example.com SOA +noall +answer
dig example.com NS +noall +answer
dig example.com +dnssec +noall +answer
临时止血:把本地解析器切到备用上游(/etc/resolv.conf 或 systemd-resolved),或直接改 hosts 临时指向已知 IP 让业务先恢复。
彻底解决:修上游权威(恢复 NS 可用性、修正 NS/SOA 配置、确认 DNSSEC 签名链完整)。修完必须主动 flush 缓存,别干等 TTL:
# systemd-resolved
sudo resolvectl flush-caches
# BIND/named
sudo rndc flush
# 验证
dig example.com +short
防再发
这次事故暴露的三件事,逐条上护栏:
- 多权威多线冗余:NS 至少 2 个权威、分布在不同网络/机房,避免单点。
- DNSSEC 与 SOA 巡检:定时
dig +dnssec +trace校验签名链完整;SOA 的 refresh/retry 别太激进,防权威抖动被放大。 - 解析层监控:不只是 ping 站点,要监控解析本身——定时 dig 域名 +short 与期望 IP 比对,TTL 变化、SERVFAIL、解析耗时突增都告警;这才是「站点挂了」的第一层探测器。
- TTL 策略:关键记录 TTL 别设过长(如 300-600s),缩短故障恢复后的缓存残留窗口;配合 flush 脚本,故障恢复后主动清缓存。
- 故障演练:定期手动模拟「权威 DNS 不可达」,验证监控能否在 T+5 内告警、切换流程是否真的生效。
给解析层加一个最朴素的监控——比「等站点挂了再报警」早一步:
# 每分钟解析一次,与期望 IP 比对,异常即告警
cat > /usr/local/bin/check-dns.sh <<'EOF'
#!/bin/bash
DOMAIN=example.com
EXPECT=203.0.113.10
GOT=$(dig +short @8.8.8.8 "$DOMAIN" | head -1)
if [ "$GOT" != "$EXPECT" ]; then
echo "DNS MISMATCH: $DOMAIN expected $EXPECT got ${GOT:-EMPTY}" | mail -s "DNS alert" [email protected]
fi
EOF
chmod +x /usr/local/bin/check-dns.sh
# crontab:*/1 * * * * /usr/local/bin/check-dns.sh
这个脚本的价值不在「复杂」,而在把解析本身变成监控对象。它抓到的是「域名解析结果不对」这一层,而这一层往往比 Web 探针早暴露上游故障。进阶版可以再加 dig +time=2 +tries=1 控制超时、把 TTL 变化也记下来做趋势。
复盘结论
「全站打不开」先分清解析层还是 Web 层——能用 IP、域名解析不了,就是 DNS。诊断顺序永远是:本机解析 → 公共 DNS 直连 → dig +trace 权威,哪一级先断,故障就在哪。而最容易被忽略的是 TTL 缓存:它既能在故障时掩盖真相,又能在修复后推迟恢复;所以修完主动 flush 缓存、日常把解析本身当监控目标,比等用户喊「又挂了」更靠谱。
复盘到最后,最值钱的一句话是:监控的对象错了,反应就永远慢半拍。 我们以前「监控站点」,它只在 Web 全挂时才报警;这次之后我们「监控解析结果」,它能在上游权威刚开始抖动时就把问题暴露出来。把监控前移一层,往往比多写几段告警规则更能缩短 MTTR——这也是这次事故给我最大的收获:不是技术多难,而是把「该看的地方」看对。