1. 从一个真实场景说起:为什么我要认真折腾 DNS
去年帮一个朋友处理他们内部系统的域名解析问题,那套系统跑在云上,前端调用后端服务时偶尔出现几秒钟的解析延迟,业务侧反馈是"偶发超时",但日志里根本看不出问题。我花了整整两天时间,最后定位到是 DNS 解析链路里一个转发器的缓存策略配置不当,导致部分域名在 TTL 到期后出现解析抖动。那次之后我就意识到,DNS 这个东西平时没人关注,一旦出问题就是"玄学故障",排查起来极其痛苦。
这篇内容就是把我这些年折腾 DNS 的经验系统整理出来,重点讲在云环境下的实战配置、排查和优化。不管你是刚接触云服务的运维新人,还是已经管着一堆域名但没深究过解析链路的老手,应该都能从里面找到能直接用的东西。我会从整体设计思路讲起,然后拆解核心配置细节,再给完整的实操流程,最后把我踩过的坑和排查技巧整理成速查表。全程都是可以直接抄作业的配置和命令,不玩虚的。
需要说明的是,下面涉及的所有域名、IP、资源组名称都是示例,你实际用的时候替换成自己的就行。另外云厂商的控制台界面会更新,但底层逻辑和参数含义是稳定的,理解了原理就不怕界面变。
2. 整体设计思路:DNS 在云环境里到底该怎么规划
2.1 先搞清楚云 DNS 的两种基本形态
很多人一上来就懵,因为云上的 DNS 其实分成两块完全不同的东西,混在一起讲就容易乱。我用一个类比帮你理清:公共 DNS 区就像你在电信局登记的对外电话号码,全世界都能查到;私有 DNS 区就像公司内部的分机号表,只有连上公司内网的人才能查到。
公共 DNS 区负责的是你对外暴露的域名,比如www.example.com解析到你的公网负载均衡 IP。这部分的核心诉求是:解析快、全球覆盖、防攻击、支持健康检查。私有 DNS 区负责的是内网服务之间的互相发现,比如db.internal.example.com解析到内网数据库的私有 IP。这部分的核心诉求是:不暴露到公网、解析稳定、支持自动注册和注销。
我见过太多团队把内网服务的域名也放到公共 DNS 区里,然后用私有 IP 做 A 记录。这样做的问题有两个:一是私有 IP 暴露在公网 DNS 查询结果里,虽然外部访问不到,但等于把你的内网拓扑结构告诉了别人;二是公共 DNS 区的解析走的是公网递归链路,内网服务互调还要绕一圈公网,延迟和稳定性都受影响。所以第一条原则就是:内外网域名严格分离,各用各的区。
2.2 解析链路的层次设计
云环境下的 DNS 解析链路,从客户端发起查询到拿到结果,中间会经过好几层。理解这个链路是排查问题的前提。我画不出图(这里也不方便用图),但可以用文字把顺序讲清楚:
客户端(比如一台虚拟机)发起查询时,首先看本地的/etc/hosts或者 Windows 的 hosts 文件,如果有记录就直接返回,不走 DNS。没有的话,查询发到虚拟机配置的 DNS 服务器地址。这个地址通常是云平台自动下发的,指向云内部的递归解析器。递归解析器收到查询后,先看自己的缓存,命中就返回;没命中就根据域名去找对应的权威服务器。如果是私有区的域名,递归解析器会去查私有区的权威记录;如果是公网域名,就按标准递归流程去根、顶级域、权威域一层层查。
这里有个关键点:云平台的递归解析器地址是自动下发的,但你可以改。有些团队为了统一管理,会自己搭一层 DNS 转发器,把虚拟机的 DNS 指向自己的转发器,再由转发器决定哪些域名走云内递归、哪些走自建权威。这样做的好处是灵活,坏处是多了一跳,而且转发器本身如果配置不当就是故障点。我的建议是:除非有明确的合规或特殊需求,否则优先用云平台原生的递归解析器,稳定性和性能都是经过大规模验证的,自己搭转发器往往是给自己找麻烦。
2.3 命名规范与区域划分
设计阶段最容易埋雷的地方是命名。我见过一个项目,所有服务的域名都是service1、service2这种,半年后没人记得哪个是哪个。命名规范这件事,越早定越好,后面改的成本极高。
我的实践是采用三段式:<服务名>.<环境>.<业务域>.internal。比如payment.prod.finance.internal、payment.staging.finance.internal。这样一眼就能看出服务、环境和业务归属。环境隔离尤其重要,生产和预发的域名必须分开,否则测试时的误操作可能直接影响生产。
区域划分上,我通常按业务域拆成多个私有区,而不是所有服务塞一个区。原因是:不同业务域的解析记录由不同团队维护,拆开之后权限可以分开控制,一个团队改错记录不会影响其他业务。公共区则按主域名拆,比如example.com一个区,example.net另一个区,子域名通过 NS 记录委托出去。
2.4 TTL 的取舍逻辑
TTL 这个参数看起来简单,其实是最需要权衡的。设短了,解析频繁回源,权威服务器压力大,客户端延迟增加;设长了,记录变更后生效慢,故障切换不及时。
我的经验值是这样的:对外公共域名,TTL 设 300 秒(5 分钟),兼顾变更速度和查询压力。内网服务域名,TTL 设 30 到 60 秒,因为内网查询量相对小,而且服务发现场景下需要快速感知实例变化。做故障切换的域名,TTL 设 60 秒甚至更短,确保切换能在 1 分钟内生效。长期不变的静态记录,比如 MX、TXT 验证记录,TTL 可以设 3600 秒甚至更长,减少无谓查询。
有个坑要提醒:TTL 是"最大缓存时间",不是"强制刷新时间"。有些递归解析器会忽略过短的 TTL,强制按自己的最小值缓存。所以你把 TTL 设成 1 秒也没用,实际生效时间取决于链路上所有缓存的最小值。我实测下来,云平台递归解析器对短 TTL 的尊重程度还不错,但公网上的某些递归服务可能会强制缓存 60 秒以上。
3. 核心配置细节:公共区与私有区的关键参数
3.1 公共 DNS 区的记录类型与配置要点
公共区最常用的记录类型就那几种,但每种都有细节。
A 记录是最基础的,域名指向 IPv4 地址。配置时要注意:如果指向的是负载均衡,用负载均衡的 IP 而不是后端服务器的 IP。我见过有人直接把 A 记录指向某台后端服务器,结果那台机器挂了整个域名就不可用。CNAME 记录用于别名,比如www指向example.com,或者指向云厂商提供的 CDN 域名。CNAME 有个限制:不能和其他记录类型共存于同一个名字,比如你不能同时有www的 CNAME 和 MX 记录。MX 记录用于邮件,需要配优先级,数字越小优先级越高。TXT 记录常用于域名验证和 SPF,值要用引号包起来。
配置公共区时,我强烈建议开启健康检查。云平台的健康检查会定期探测你配置的端点,如果探测失败,自动把该记录从解析结果里摘掉。这个功能对于多活架构是刚需。健康检查的间隔一般设 30 秒,失败阈值设 3 次,这样最快 90 秒能感知故障。但要注意,健康检查本身也有成本,探测频率太高会产生额外费用,而且可能被误判(比如网络抖动导致误摘)。
3.2 私有 DNS 区的自动注册机制
私有区最省心的功能是自动注册。你把它和虚拟网络关联之后,网络里的虚拟机启动时会自动把自己的主机名注册到私有区,销毁时自动注销。这样你就不用手动维护内网服务的 A 记录了。
但自动注册有几个坑。第一,主机名冲突。如果两台虚拟机起了同样的主机名,后注册的会覆盖先注册的,导致解析指向错误的机器。所以虚拟机命名要有唯一性保证,通常用随机后缀或者资产编号。第二,注册延迟。虚拟机启动到记录生效之间有几十秒的延迟,如果你的服务启动脚本依赖域名解析,要加等待逻辑或者重试。第三,跨网络解析。默认情况下,私有区只对关联的虚拟网络可见。如果你有多个虚拟网络需要互相解析,要么把它们都关联到同一个私有区,要么配置网络间的转发规则。
手动记录和自动注册可以共存。我的做法是:动态的服务实例用自动注册,静态的基础设施(比如数据库、缓存)用手动记录。手动记录的好处是稳定可控,不会因为某台机器重启就消失。
3.3 转发规则与条件转发
当你的环境里既有云平台私有区,又有自建 DNS 服务器(比如公司内网的老系统),就需要配置条件转发。条件转发的逻辑是:匹配特定域名后缀的查询,转发到指定的 DNS 服务器;不匹配的走默认递归。
配置条件转发的关键是顺序和优先级。规则是按最长后缀匹配的,比如你有一条internal.example.com转发到 A 服务器,一条example.com转发到 B 服务器,那么查询db.internal.example.com会走 A,查询www.example.com会走 B。这个逻辑和路由表的最长前缀匹配是一样的。
我踩过的一个坑:转发目标不可达时的行为。如果转发目标挂了,查询会超时,而不是自动回退到默认递归。所以配置转发规则时,一定要确保目标 DNS 服务器是高可用的,最好配两个目标做冗余。另外,转发规则的数量不宜过多,每条规则都会增加查询链路的复杂度,排查问题时更麻烦。
3.4 解析记录的批量管理
当记录数量上百条之后,控制台一条条点就不现实了。这时候要用基础设施即代码的方式管理。主流云平台都支持用配置文件或者 API 来管理 DNS 记录。
我的做法是把所有 DNS 记录写在一个结构化的文件里(比如 JSON 或 YAML),然后用脚本同步到云平台。这样做的好处是:记录变更有版本控制,可以 review,可以回滚;批量修改一条命令搞定;环境之间可以复制配置。同步脚本的逻辑是:读取本地配置,对比云上现状,计算出需要新增、修改、删除的记录,然后执行变更。对比这一步很关键,直接全量覆盖有风险,万一本地配置漏了某条记录,全量覆盖会把它删掉。
提示:批量操作前一定要先做 dry-run,把将要执行的变更列出来人工确认,确认无误再真正执行。我见过直接跑同步脚本把生产记录删光的案例,恢复花了两个小时。
4. 实操过程:从零搭建一套完整的 DNS 解析体系
4.1 环境准备与前置检查
开始之前,先确认几件事。你需要有云平台的管理权限,能创建 DNS 区和虚拟网络。需要知道你的域名是什么,以及域名的注册商在哪里(因为要在注册商那边把 NS 记录指向云平台的权威服务器)。还需要规划好网段和虚拟网络,因为私有区要关联到虚拟网络。
第一步是创建公共 DNS 区。在控制台找到 DNS 服务,新建一个区,名字填你的主域名,比如example.com。创建完成后,云平台会分配一组 NS 记录,形如ns1.xxx.com、ns2.xxx.com这样的四个地址。把这四个 NS 地址填到你域名注册商的 NS 设置里,替换掉默认的。这个变更全球生效需要时间,通常几小时到 48 小时不等,取决于各地递归服务器的缓存刷新。
验证 NS 是否生效,用dig命令查 NS 记录:
dig NS example.com +short如果返回的四个地址和云平台分配的一致,说明委托生效了。如果还是注册商的默认 NS,说明还没刷新,等等再试。
第二步是创建私有 DNS 区。同样新建一个区,名字用internal.example.com这种内部后缀。创建后关联到你的虚拟网络。关联之后,网络里的虚拟机就能解析这个区里的记录了。
4.2 公共区记录配置实操
先配最基础的几条。假设你有一个公网负载均衡,IP 是203.0.113.10,要让它响应www.example.com和example.com(裸域)。
裸域的处理有个细节:很多云平台不允许给裸域直接配 CNAME,只能配 A 记录。所以裸域用 A 记录指向负载均衡 IP,www用 CNAME 指向裸域。配置如下:
| 记录名 | 类型 | 值 | TTL |
|---|---|---|---|
| @ | A | 203.0.113.10 | 300 |
| www | CNAME | example.com | 300 |
| api | A | 203.0.113.11 | 300 |
@符号代表区本身,也就是裸域。配好之后等 TTL 时间过去,用dig验证:
dig www.example.com +short dig api.example.com +short如果返回的 IP 正确,说明解析生效了。如果返回空或者报 NXDOMAIN,检查记录名有没有写错,以及 NS 委托是否真的生效了。
接下来配健康检查。在负载均衡的记录上启用健康检查,探测协议选 HTTPS,路径选一个轻量的健康检查接口比如/health,间隔 30 秒,失败阈值 3。这样当负载均衡后面的服务不可用时,记录会被自动摘除。但要注意,健康检查探测的是负载均衡本身,如果负载均衡是云托管的,它自己就有高可用保证,健康检查的意义在于探测后端服务。如果你的架构是负载均衡后面挂多台服务器,健康检查应该配在后端服务器对应的记录上,而不是负载均衡上。
4.3 私有区自动注册与手动记录
私有区关联虚拟网络后,自动注册默认是开启的。你可以创建一台虚拟机测试一下。虚拟机启动后,在另一台同网络的虚拟机里执行:
nslookup vm-test-01.internal.example.com如果返回虚拟机的私有 IP,说明自动注册生效了。如果解析失败,检查私有区和虚拟网络的关联是否正常,以及虚拟机的 DNS 设置是否指向了云平台的递归解析器(通常是自动下发的,不用改)。
手动记录的配置和公共区类似,只是值填私有 IP。比如你的数据库在内网10.0.1.50,配一条 A 记录:
| 记录名 | 类型 | 值 | TTL |
|---|---|---|---|
| db | A | 10.0.1.50 | 60 |
| cache | A | 10.0.1.51 | 60 |
TTL 设 60 秒,因为内网服务发现需要快速感知变化。配好后同样用nslookup或dig验证。
4.4 条件转发配置实操
假设你有一个自建 DNS 服务器在10.0.2.10,负责解析legacy.corp这个后缀的域名。你需要在云平台的 DNS 服务里配一条条件转发规则:后缀legacy.corp转发到10.0.2.10。
配置完成后,在虚拟机里查询legacy.corp下的域名,应该能正常解析。如果超时,检查云平台递归解析器到10.0.2.10的网络连通性,以及自建 DNS 服务器是否允许来自云平台网段的查询。这里有个常见问题:自建 DNS 服务器通常配了访问控制列表,只允许特定网段查询。云平台递归解析器的源 IP 可能是一个你没想到的地址段,需要把它加进白名单。
4.5 批量管理脚本实操
我用 Python 写了一个简单的同步脚本,逻辑是读取 YAML 配置文件,对比云上现状,生成变更计划。核心代码结构如下:
import yaml import json def load_local_config(path): with open(path, 'r') as f: return yaml.safe_load(f) def fetch_remote_records(zone): # 调用云平台 API 获取当前记录 # 返回格式:[{'name': 'www', 'type': 'A', 'value': '1.2.3.4', 'ttl': 300}, ...] pass def diff_records(local, remote): local_set = {(r['name'], r['type']): r for r in local} remote_set = {(r['name'], r['type']): r for r in remote} to_create = [] to_update = [] to_delete = [] for key, rec in local_set.items(): if key not in remote_set: to_create.append(rec) elif rec != remote_set[key]: to_update.append(rec) for key, rec in remote_set.items(): if key not in local_set: to_delete.append(rec) return to_create, to_update, to_delete def apply_changes(zone, to_create, to_update, to_delete, dry_run=True): if dry_run: print("将创建:", to_create) print("将更新:", to_update) print("将删除:", to_delete) return # 实际调用 API 执行变更 pass这个脚本的关键在于diff_records函数,它用(name, type)作为唯一键来对比。注意这里假设同一个名字同一种类型只有一条记录,如果你的场景有多值记录(比如一个名字对应多个 IP),需要把值也纳入键的计算。dry-run 模式一定要先跑,确认变更计划符合预期再执行。
注意:删除操作是最危险的,脚本里应该加一道保险,比如删除超过 10 条记录时要求二次确认,或者把删除的记录先备份到文件再执行。
5. 常见问题与排查技巧实录
5.1 解析不生效的排查顺序
解析不生效是最常见的问题,排查要按链路顺序来,不要跳步。我整理了一个排查顺序表:
| 排查步骤 | 检查内容 | 常用命令 |
|---|---|---|
| 1 | 本地 hosts 文件是否有覆盖 | cat /etc/hosts |
| 2 | 虚拟机 DNS 服务器地址是否正确 | cat /etc/resolv.conf |
| 3 | 直接查询权威服务器是否正常 | dig @ns1.xxx.com example.com |
| 4 | 通过递归解析器查询是否正常 | dig example.com |
| 5 | NS 委托是否生效 | dig NS example.com +short |
| 6 | 记录是否真的存在 | 控制台查看或 API 查询 |
大部分问题出在第 1 步和第 2 步。我遇到过好几次,虚拟机里有人手动改了 hosts 文件做测试,忘了删,导致解析一直走本地。还有一次是虚拟机的 DNS 地址被手动改成了公网 DNS,结果内网域名解析不了。
5.2 解析延迟高的定位方法
解析延迟高通常有三个原因:递归链路长、缓存未命中、权威服务器响应慢。定位方法是分段计时。
用dig的+stats选项可以看到查询耗时:
dig example.com +stats输出里的Query time就是总耗时。如果这个值很大(超过 100ms),说明链路有问题。进一步用+trace看每一跳的耗时:
dig example.com +trace+trace会从根服务器开始逐级查询,显示每一级的响应时间。如果某一级特别慢,问题就在那一级。常见的是权威服务器响应慢,可能是服务器负载高或者网络抖动。
缓存未命中导致的延迟,表现为第一次查询慢、后续查询快。这是正常的,但如果频繁未命中,说明 TTL 设得太短,或者查询的域名太分散(缓存命中率低)。解决办法是适当调大 TTL,或者把相关域名归拢到同一个区里。
5.3 内网域名解析到公网 IP 的问题
这个问题的表现是:内网服务互调时,解析出来的 IP 是公网 IP,导致流量绕了一圈公网,延迟高且可能被防火墙拦截。原因是内网域名被解析到了公共区的记录,而不是私有区。
排查方法是查一下解析结果:
nslookup db.internal.example.com如果返回的是公网 IP,说明查询走了公共区。检查私有区里是否有对应的记录,以及虚拟网络是否关联了私有区。如果私有区里有记录但没生效,检查是否有同名的公共区记录优先级更高。有些云平台的解析顺序是:先查私有区,再查公共区。如果私有区没有记录,就会 fallback 到公共区。所以确保私有区里有完整的记录。
5.4 健康检查误摘记录的处理
健康检查误摘是指后端服务明明正常,但健康检查探测失败,导致记录被摘除。常见原因有三个:探测路径返回了非 200 状态码、探测超时、探测源 IP 被后端防火墙拦截。
排查方法是看健康检查的日志(云平台通常有记录)。如果日志显示探测请求根本没到达后端,那就是网络或防火墙问题。如果到达了但返回了错误码,检查探测路径的接口是否正常。如果超时,检查后端服务的响应时间是否超过了健康检查的超时阈值。
我的经验是:健康检查的路径要选一个轻量且稳定的接口,不要选依赖数据库或外部服务的接口。我见过有人把健康检查路径设成/api/health,而这个接口会查数据库,数据库一慢健康检查就失败,导致记录被误摘,反而放大了故障。正确的做法是选一个只检查进程存活和端口监听的接口,比如/ping。
5.5 常见问题速查表
| 现象 | 可能原因 | 快速验证 | 解决办法 |
|---|---|---|---|
| 解析返回 NXDOMAIN | 记录不存在或名字写错 | 控制台查看记录 | 补记录或改名字 |
| 解析返回旧 IP | TTL 未过期或缓存未刷新 | dig看 TTL 剩余 | 等 TTL 过期或清缓存 |
| 内网解析走公网 | 私有区无记录或未关联 | nslookup看结果 | 补私有区记录 |
| 解析间歇性失败 | 转发器不稳定或网络抖动 | 多次dig对比 | 检查转发器冗余 |
| 健康检查误摘 | 探测路径或阈值不当 | 看健康检查日志 | 调整路径和阈值 |
| 批量同步删记录 | 本地配置不完整 | dry-run 对比 | 补全配置再执行 |
6. 几个我踩过的坑和独家技巧
6.1 不要忽视 DNS 查询的源端口耗尽问题
这个坑比较隐蔽。当一台虚拟机在短时间内发起大量 DNS 查询时,源端口会被快速消耗。如果查询的目标是同一个递归解析器,源端口范围有限(通常 1024 到 65535),高并发下可能耗尽,导致新查询失败。表现是间歇性的解析超时,而且越忙的时候越容易出。
解决办法有两个:一是减少不必要的 DNS 查询,比如在应用层做 DNS 缓存,不要每次请求都解析;二是配置多个递归解析器地址,让查询分散到不同的目标,源端口的使用就分散了。云平台通常下发两个递归解析器地址,默认就是做冗余的,但很多虚拟机只用了第一个,第二个没配。检查/etc/resolv.conf,确保两个地址都在。
6.2 域名末尾的点号不是可有可无的
在 DNS 配置文件里,域名末尾的点号表示"绝对域名",不加点号表示"相对域名",会自动拼接当前区名。这个区别在配 CNAME 和 MX 记录时特别重要。
比如你在example.com区里配一条 CNAME,值是target.example.com(不加末尾点号),实际会被解析成target.example.com.example.com,这就错了。正确的写法是target.example.com.(加末尾点号)。我见过好几次因为这个点号导致解析失败,排查半天才发现。
用dig查询时也要注意,dig example.com和dig example.com.在大多数情况下结果一样,但在某些工具和脚本里行为不同。写脚本时统一加末尾点号,避免歧义。
6.3 私有区的解析顺序陷阱
有些云平台的私有区解析顺序是:先匹配最具体的区,再匹配上级区。比如你有internal.example.com和example.com两个私有区,查询db.internal.example.com时,先匹配internal.example.com,如果那里没有记录,再匹配example.com。这个逻辑本身没问题,但如果你在example.com私有区里也有一条db.internal的记录,就会产生歧义。
我的做法是:私有区之间不要有重叠的域名空间。internal.example.com下的所有记录都放在这个区里,example.com私有区里不放internal开头的记录。这样解析顺序就唯一了,不会出现意外匹配。
6.4 用 dig +short 快速验证,但别依赖它排查复杂问题
dig +short输出简洁,适合快速验证。但它只显示最终结果,不显示查询过程、TTL、响应码等信息。排查复杂问题时,要用完整的dig输出:
dig example.com完整输出里的status字段告诉你响应码(NOERROR、NXDOMAIN、SERVFAIL 等),ANSWER SECTION显示记录值和 TTL,Query time显示耗时,SERVER显示是哪个服务器响应的。这些信息在排查时缺一不可。
我习惯用dig +noall +answer只看答案段,比+short多显示 TTL,比完整输出简洁:
dig example.com +noall +answer6.5 定期审计解析记录,清理僵尸记录
环境跑久了,解析记录会积累很多不再使用的。这些僵尸记录不仅占地方,还可能造成安全隐患(比如指向已经释放的 IP,被其他人申请后你的域名就指向了别人的服务)。我建议每季度做一次审计:导出所有记录,对照当前在用的服务清单,把不用的标记出来,确认后删除。
审计时特别要注意 CNAME 记录。CNAME 指向的目标如果已经不存在了,查询会返回 NXDOMAIN 或者超时。这种"悬空 CNAME"是常见的安全风险点。用脚本批量检查所有 CNAME 的目标是否可解析,不可解析的就要处理。
7. 写在最后的一点个人体会
DNS 这个东西,平时不出问题的时候感觉不到它的存在,一出问题就是全局性的。我这些年处理过的故障里,DNS 相关的占比不低,而且往往是最难排查的那一类,因为它的表现是"间歇性"、"偶发"、"部分用户受影响",很容易被误判成应用层的问题。
我的核心体会是:把 DNS 当成基础设施来对待,而不是配置完就不管的附属品。该做的监控要做(解析成功率、解析延迟、健康检查状态),该有的规范要有(命名、TTL、区域划分),该做的审计要定期做。这些投入在平时看不出价值,但在故障发生时能帮你快速定位,少熬几个夜。
另外,云平台的 DNS 服务虽然托管了大部分复杂性,但不等于你可以完全不懂原理。理解解析链路、缓存机制、记录类型这些基础,才能在出问题时知道从哪里下手。我见过太多人一遇到 DNS 问题就重启虚拟机或者找云厂商提工单,其实大部分问题自己十分钟就能定位。
最后分享一个我常用的排查习惯:遇到解析问题,先用dig @8.8.8.8 example.com和dig @云平台递归地址 example.com各查一次,对比结果。如果公网递归能解析而云平台递归不能,问题在云平台的私有区或转发配置;如果两个都不能,问题在权威服务器或 NS 委托。这一步能快速缩小排查范围,省下大量时间。