跳转至

一次 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,叠加两点放大器——

  1. TTL 缓存掩盖:本地/公共递归解析器把故障前的旧 A 记录缓存住了,TTL 未到期前继续吃旧值;上游恢复后,解析器仍按旧 TTL 走负缓存,恢复信号被推迟。
  2. 单一权威依赖:NS 记录只挂了少量权威,没有按地理/网络多线冗余,单个权威抖动就影响全量。

关键判据(也是复盘里的分水岭):dig @8.8.8.8 域名 在故障期间直连能返回,而走默认解析器不行——说明问题在权威/递归链路上,不在你的 Web 站。

诊断里最容易踩的几个坑

这次复盘里,真正拖慢恢复的不是「查不到」,而是诊断顺序和缓存干扰。几个真实教训:

  1. 先 ping IP 再 ping 域名。我第一反应是 ping 域名,报 cannot resolve 后差点去重启 Web 服务。冷静下来先 ping <web_ip>——IP 通、域名不通,才锁定是解析层。顺序反了就会白忙半天。
  2. 别只信一个解析器。dig example.com 默认走系统解析器,它的结果可能是缓存的旧值。必须再用 dig @8.8.8.8、dig @1.1.1.1 直连公共 DNS 交叉验证,才能区分「本地解析器问题」和「上游权威问题」。
  3. 负缓存同样会骗人。上游故障时,递归解析器会缓存 SERVFAIL 或 NXDOMAIN 一段时间(由 SOA 的 negative TTL 控制)。上游修好了,这些「坏结果」还能继续被吃到 TTL 过期,这也是为什么光修上游不够,必须手动 flush。
  4. hosts 文件与代理劫持。如果 dig 直连公共 DNS 也解析不到,但浏览器个别页面能开,要怀疑 --hosts 文件条目、浏览器 DoH(加密 DNS)或系统代理在改写解析结果。getent hosts 能看出系统层是否被 hosts 劫持。
  5. 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

防再发

这次事故暴露的三件事,逐条上护栏:

  1. 多权威多线冗余:NS 至少 2 个权威、分布在不同网络/机房,避免单点。
  2. DNSSEC 与 SOA 巡检:定时 dig +dnssec +trace 校验签名链完整;SOA 的 refresh/retry 别太激进,防权威抖动被放大。
  3. 解析层监控:不只是 ping 站点,要监控解析本身——定时 dig 域名 +short 与期望 IP 比对,TTL 变化、SERVFAIL、解析耗时突增都告警;这才是「站点挂了」的第一层探测器。
  4. TTL 策略:关键记录 TTL 别设过长(如 300-600s),缩短故障恢复后的缓存残留窗口;配合 flush 脚本,故障恢复后主动清缓存。
  5. 故障演练:定期手动模拟「权威 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——这也是这次事故给我最大的收获:不是技术多难,而是把「该看的地方」看对。