适用场景
线上服务切换负载均衡、迁移 API 网关或故障切流后,权威 DNS 已显示新 IP,但仍有一部分客户端持续访问旧地址。常见表现包括:请求间歇超时、少量用户仍命中已下线机房、发布后错误率在一个较长窗口内缓慢回落。
本文以 api.example.com 从 203.0.113.10 切换到 203.0.113.20 为例,说明如何区分权威记录、递归解析器、本机缓存、应用连接池四层状态,并给出可回滚的切换流程。
现象与风险
运维同学通常先执行:
dig api.example.com A +short
若此命令返回新地址,很容易误以为 DNS 已完全生效。实际上,它默认经过本机配置的递归解析器;不同网络、不同运营商和不同进程可能仍持有旧值。更危险的是,旧 IP 在切换后立即回收,缓存尚未过期的客户端就会连接到无关服务。
排查时先明确两件事:
- 权威服务器现在发布什么记录;
- 失败请求实际连到了哪个 IP,以及该值来自哪一层缓存。
先确认权威记录与委派链路
不要只看默认 dig 输出。先找到域名的权威 NS,再直接向其中一台查询:
dig example.com NS +short
dig @ns1.dns-provider.example api.example.com A +norecurse
dig @ns1.dns-provider.example api.example.com SOA +norecurse
+norecurse 要求服务器不要代为递归查询。A 记录的 ANSWER SECTION 应是新地址;记录末尾的 TTL 是递归缓存最多可继续保存该答案的秒数。SOA 中的 serial 应随本次变更递增;若 serial 未变,往往是变更提交到错误 zone、CDN DNS 控制面尚未同步,或查看的是从节点旧数据。
还应检查是否存在 CNAME 链:
dig @ns1.dns-provider.example api.example.com CNAME +noall +answer
dig @ns1.dns-provider.example api-origin.example.net A +noall +answer
最终连接地址的有效期受整条链中 TTL 影响。只降低最外层 CNAME 的 TTL,不能让下游 A 记录缓存立即失效。
按层定位旧地址从哪里来
1. 比较多个递归解析器
选择本公司 DNS、公共 DNS 和故障客户端所在网络的 DNS 分别查询:
for resolver in 10.0.0.53 1.1.1.1 8.8.8.8; do
echo "== $resolver =="
dig @"$resolver" api.example.com A +noall +answer
done
如果某个解析器答复旧 IP,且 TTL 小于变更前 TTL,说明它正在自然过期;不要反复刷新查询来替代等待。若 TTL 每次都回到完整值,则可能有上游仍在发布旧记录,需沿该解析器的转发链继续检查。
2. 检查 Linux 本机缓存与 hosts 覆盖
/etc/hosts 的优先级通常高于 DNS。使用下面命令查看系统解析结果和覆盖项:
getent ahostsv4 api.example.com
grep -nE '(^|[[:space:]])api\.example\.com([[:space:]]|$)' /etc/hosts
resolvectl query api.example.com 2>/dev/null || true
getent 走的是 Name Service Switch,最接近大部分进程的实际解析路径。若 dig 是新 IP 而 getent 是旧 IP,优先检查 hosts、nscd、systemd-resolved 或企业安全客户端注入的 DNS 策略。
在确认问题仅存在于本机缓存后,才执行对应的刷新动作:
sudo resolvectl flush-caches
sudo systemctl restart nscd # 仅在 nscd 已部署时执行
不要把重启 DNS 服务当作全网修复手段;它只影响当前节点。
3. 识别应用进程自己的缓存和长连接
即使 DNS 已更新,进程也未必立刻重新解析。Java 的 networkaddress.cache.ttl、Go 的自定义 resolver、服务网格的 DNS 代理,以及 HTTP 客户端的 keep-alive 连接都会延迟切换。
在故障 Pod 或主机上同时记录解析和真实建连目标:
getent hosts api.example.com
curl -sv --connect-timeout 3 https://api.example.com/healthz -o /dev/null
ss -ntp | grep ':443'
curl -v 中的 Trying x.x.x.x 是本次连接使用的地址;ss 可发现进程仍复用到旧后端的已建立连接。若仅旧连接失败,应缩短客户端空闲连接时间、主动 drain 连接或滚动重启工作负载,而不是盲目改 DNS。
一次可观测的定位示例
某次切换后,权威服务器返回 203.0.113.20,公司 DNS 也返回新地址,但两台应用主机的 getent 仍为 203.0.113.10。检查发现 /etc/hosts 中遗留了一条应急映射。移除该行并刷新 systemd-resolved 后,新建连接恢复正常。
为了避免凭感觉判断,应在切换窗口记录下列字段:
时间、探针节点、查询服务器、A 记录、TTL、HTTP 建连 IP、状态码、错误类型
将这些字段写入探针日志或监控标签,可以快速判断问题是 DNS 传播、节点配置,还是后端服务自身失败。
安全切换方案
DNS 不适合做秒级、无损的强制流量迁移。建议按以下顺序操作:
- 提前至少一个旧 TTL 周期降低 TTL,并确认权威和主要递归解析器均已显示较小 TTL;
- 新旧后端同时提供服务,新后端先通过独立域名或探针验证健康;
- 修改记录后保留旧后端的监听和证书,覆盖一个“旧 TTL + 最大客户端缓存 TTL + 连接空闲超时”的窗口;
- 分地域、分运营商检查解析结果和实际 HTTP 建连地址;
- 指标稳定后再下线旧地址,并把 TTL 恢复到正常值。
若使用四层/七层负载均衡,优先在负载均衡层做 drain 和权重切换;DNS 只负责将客户端引导到稳定入口。这样即使少量客户端保留旧解析结果,也仍能由旧入口转发或返回可控响应。
预防措施
- 建立权威 DNS、多个递归 DNS、应用侧
getent和真实 HTTP 建连的四级合成探针。 - 将 hosts 临时映射纳入配置管理,并设置到期检查,禁止手工遗留。
- 在发布手册中写明 TTL 降低和恢复的时间点,禁止“改记录后立即下线旧机”。
- 为客户端设置合理的 DNS 缓存上限和连接空闲超时;长连接协议要支持连接排空。
- 将 DNS 变更、后端扩缩容和证书更新纳入同一回滚预案,确保旧入口在窗口内可恢复。
总结
DNS 变更后仍访问旧地址,并不必然是 DNS 服务商“没生效”。先用直接查询确认权威答案,再逐层检查递归缓存、本机覆盖和应用连接复用,最后通过保留旧入口与分层探针完成平滑切换。把 DNS 当作最终一致性的流量引导机制,才能避免一次记录变更演变为线上故障。
Discussion
评论