生产环境偶尔出现的域名解析超时,排查到最后往往不是网络抖动,而是DNS配置在多年迭代中积累了大量技术债。记录散落在不同云厂商、自建解析平台和内部hosts文件里,没有人能说清某条CNAME为什么存在,也没有人敢直接删除。技术债清理推进到基础设施层时,DNS通常是绕不开的一环,但它的影响面又特别大,改错一条记录就能让服务雪崩。

要安全完成DNS重构,不能直接拿着旧记录批量修改。第一步是把隐藏的依赖关系挖出来,第二步是设计兼容性迁移方案,最后还要建立防回潮机制。下文会从这几个角度拆解。
一、先让DNS技术债显性化
很多团队开始清理DNS技术债时,第一反应是直接整理记录,但更合适的做法是先回答三个问题:哪些域名在真正提供服务?每一条记录对应哪个应用或环境?记录变更是否有审批和留痕?如果这三个问题没有答案,说明技术债不只是配置脏乱,还缺少最小可用的治理框架。
一个实用方法是导出所有DNS记录并建立台账。以主流云平台为例,可以使用CLI或SDK批量拉取Route 53、Cloud DNS或自建BIND的zone文件。导出后需要区分权威解析和本地覆盖,比如开发机上的hosts、内网DNS转发、Kubernetes的CoreDNS自定义配置。很多“幽灵依赖”来自本地覆盖,用户以为改的是权威DNS,实际流量仍被旧地址劫持。
以Windows主机为例,hosts文件位于C:\Windows\System32\drivers\etc\hosts,这个路径下的静态映射经常被临时修复手段写满。清理阶段建议把这些映射逐条登记,并判断是否应由DNS服务接管。可以用下面命令快速查看当前解析来源差异:
# Linux/macOS 查看hosts cat /etc/hosts # 对比权威解析 dig +short app.ipipp.com
这段命令能够帮助定位同一域名在不同环境下的解析不一致问题。先把所有记录归拢到表格中,标注使用方、负责人、最后变更时间和是否可删除,DNS重构才有可靠的输入。
二、设计兼容性迁移方案
DNS重构的目标通常不是简单把旧记录迁到新平台,而是同时改善命名规范、环境隔离和故障切换能力。直接在线上修改A记录风险极高,缓存和客户端长连接都可能让切换过程出现新旧地址并存。因此推荐采用新旧域名并行的策略:新服务优先使用新域名,旧域名通过CNAME或转发规则临时指向新域名,观察无流量后再逐步下线。
例如某支付网关原域名是pay.internal.ipipp.com,对应多个内网IP。重构时可以建立pay-gw.prod.ipipp.com作为权威入口,旧域名不直接删除,而是设置CNAME指向新域名,并把TTL降到60秒。这样即使有遗漏的调用方,也能在更改权威解析后快速生效。
# 检查旧域名和新域名的解析结果是否一致
import socket
old_addr = socket.gethostbyname("pay.internal.ipipp.com")
new_addr = socket.gethostbyname("pay-gw.prod.ipipp.com")
print(old_addr, new_addr)
assert old_addr == new_addr, "解析不一致,需要继续保留旧域名"
代码中的断言可以作为迁移验证脚本,放到CI或发布流程中执行。另一个重点是把硬编码IP改为域名调用。很多技术债来自应用配置里直接写了IP地址,尤其是内部服务。重构DNS时必须同步修改配置中心或环境变量,否则即使DNS层已统一,应用仍会绕过解析。
还需要考虑TTL与缓存影响。切换前至少提前一个TTL周期降低记录缓存时长,但也不要设置为0,一方面部分解析器不支持,另一方面会增加权威服务器压力。通常生产切换前24小时将TTL调整到60或120秒即可,切换完成并稳定后再逐步提高。
三、使用基础设施即代码实施变更
手工在云控制台改DNS记录的方式会继续制造技术债。DNS重构首先要引入版本管理和声明式配置。Terraform、Pulumi、DNSControl等工具都支持将DNS记录写成代码,提交到Git仓库,通过Pull Request评审后再应用。这样每条记录的创建、修改、删除都有上下文,不再依赖个人记忆。
以DNSControl为例,可以用DSL描述不同环境的解析策略。下面示例展示了如何将生产与测试域名拆开,并设置合理的TTL:
// dnsconfig.js
D("ipipp.com", REG_NAMESERVER, DnsProvider("route53"),
A("api", "10.0.2.10", TTL(60)),
CNAME("app", "api.ipipp.com.", TTL(300))
);
D("test.ipipp.com", REG_NAMESERVER, DnsProvider("route53"),
A("api", "10.0.9.20", TTL(300))
);
DSL代码经过本地dnscontrol preview命令预览差异,再执行dnscontrol push发布。这个流程和修改应用代码一致,能让DNS变更进入评审体系。Terraform用户则可以把Route 53 zone和record统一管理,在同一个state文件中绑定DNS与关联资源,避免DNS记录成为游离对象。
resource "aws_route53_record" "api" {
zone_id = aws_route53_zone.main.zone_id
name = "api.ipipp.com"
type = "A"
ttl = "60"
records = ["10.0.2.10"]
}
当DNS配置进入版本库后,还可以使用pre-commit或者CI流水线做静态校验,比如检测记录是否符合命名规范、是否跨环境引用、TTL是否超出阈值。把治理规则自动化,才能真正防止技术债在重构完成后快速反弹。
四、灰度切换与回滚机制
DNS切换不能一步到位。尤其是权威DNS修改后,缓存仍会保留旧值,不同递归解析器的刷新时间也不同。更稳妥的方式是引入流量灰度:先在权威解析中新增一条权重记录,将少量流量指向新地址,同时保持旧地址可用。但要注意,并不是所有DNS服务都原生支持权重解析,部分云厂商需要结合健康检查和路由策略。
如果无法使用权重解析,可以采用双域名方案:新客户端访问新域名,旧客户端继续访问旧域名,监控一段时间后再通过CNAME统一切换。切换前必须明确回滚条件,例如新地址错误率超过0.5%、解析失败率上升或核心接口P99延迟明显恶化。回滚时优先把权威记录改回旧地址,并利用低TTL快速生效。
监控是重构的安全网。除了传统DNS服务监控外,还需要从应用侧观察解析结果是否与预期一致。可以使用dig +short或nslookup定期拨测,记录每次解析到的IP以及耗时。有条件的话,在多个地域的服务器上执行拨测,以覆盖不同运营商递归解析器的差异。
# 每30秒检查一次域名解析,并输出时间戳 while true; do echo "$(date +%T) $(dig +short api.ipipp.com)" sleep 30 done
切换完成后不要立刻删除旧记录和旧域名,至少保留一个完整的业务周期,比如两周或一次完整发布窗口。旧域名可以降级为内部跳板,或者仅保留TXT记录说明迁移路径。等到所有调用方都从审计日志中确认切换完毕,再清理旧数据。这个冷却期也是技术债清理中的重要一环。
五、建立DNS变更治理与防回潮机制
重构完成只是清理技术债的起点。没有治理机制,几个月后DNS还是会回到原先的混乱状态。比较务实的做法是把DNS变更纳入应用发布流程:每个新服务申请域名时,必须同时提交DNS配置代码,而不是让开发手动在控制台创建记录。可以使用模板生成基础记录,再由平台团队评审。
可以建立一条简单的记录规范:内部域名统一使用service.env.internal格式,公网域名与内部域名严格分开,生产环境禁止使用随机测试域名。记录中禁止直接绑定单个实例IP,必须使用负载均衡或虚拟IP。TTL设置按服务等级划分,核心服务短TTL便于切换,静态资源可长TTL。规范不复杂,但能减少后续整改成本。
-- 审计表示例,记录每次DNS变更 CREATE TABLE dns_change_audit ( id BIGINT PRIMARY KEY, domain VARCHAR(255) NOT NULL, old_value TEXT, new_value TEXT, operator VARCHAR(64), approved_by VARCHAR(64), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
另外,定期审计是防止技术债复发的有效手段。每季度跑一次解析记录扫描,找出无流量、无负责人、指向已下线服务的“三无记录”。扫描结果直接生成工单,要求归属团队确认后删除。审计脚本不必复杂,关键是把结果公开出来,让技术债无处藏身。
DNS技术债清理最难的部分通常不是技术实现,而是跨团队沟通与风险控制。通过显性化现状、声明式配置、灰度切换以及持续审计,可以把一次危险的手工操作变成可控的工程流程。对任何基础设施团队来说,这套方法都值得沉淀下来。

