目录

网络-11 DNS

DNS(Domain Name System)把域名翻译成 IP 地址。它是每一次网络访问的第一步——DNS 慢了,后面所有优化都白费。

1. 域名分级

./域名分级.png

域名从右往左逐级变大,这个顺序和解析顺序一致:

www.example.com.
│   │       │  └── 根域(root),平时省略不写
│   │       └───── 顶级域 TLD(Top-Level Domain)
│   └───────────── 二级域,你注册的那部分
└───────────────── 主机名/三级域
  • 根域:全球 13 组根域名服务器(a.root-servers.net ~ m.root-servers.net)。注意是 13 组而非 13 台——通过任播(Anycast)技术,实际有上千台物理服务器分布全球,同一个 IP 会路由到最近的那台。
  • 顶级域.com.org.net 等通用顶级域,以及 .cn.jp 等国家顶级域。
  • 权威域名服务器(Authoritative):真正持有该域名解析记录的服务器,是解析链的终点。

FQDN(完全限定域名) 指以点结尾的完整写法 www.example.com. ——末尾那个点代表根域。这个点看起来无关紧要,但在 K8s 里它直接关系到解析性能,本文最后会讲。

2. 记录类型

类型 作用 示例
A 域名 → IPv4 地址 example.com → 93.184.216.34
AAAA 域名 → IPv6 地址 example.com → 2606:2800:220:1::
CNAME 域名 → 另一个域名(别名) www → example.com
NS 指定该域由哪些权威服务器负责 example.com → ns1.dns.com
MX 邮件服务器,带优先级 example.com → mail.example.com
TXT 任意文本,用于域名所有权验证、SPF 反垃圾邮件 v=spf1 include:...
PTR IP → 域名(反向解析 邮件服务器用它验证发送方
SOA 该域的起始授权记录,含管理员邮箱、序列号、各种 TTL
SRV 服务发现,指定服务的主机 + 端口 K8s 内部大量使用

CNAME 有两个必须知道的限制

  1. 根域名不能用 CNAMEexample.com 本身必须是 A 记录,因为根域必须同时有 SOA 和 NS 记录,而 CNAME 不允许与其他记录共存。想让根域名指向 CDN,只能用云厂商的私有扩展(AWS 的 ALIAS、阿里云的隐性 URL 等)。
  2. CNAME 会增加一次解析www → cdn.provider.com → A 记录,链条越长首次解析越慢。

CDN 的接入基本都靠 CNAME:你把 www 指向 CDN 厂商的域名,CDN 再根据用户地理位置返回最近的边缘节点 IP。

3. 递归查询与迭代查询

./dns迭代和递归查询.png

这两个概念最容易混淆,关键在于谁在替谁跑腿

  • 递归查询:客户端问本地 DNS,本地 DNS 必须给出最终答案。客户端只发一次问、收一次答,中间过程与它无关。
  • 迭代查询:被问的服务器只告诉你下一步该问谁,不代你去问。本地 DNS 需要自己一级级追下去。

实际的组合是固定的:

客户端 ──递归──> 本地 DNS ──迭代──> 根域 ──> 顶级域 ──> 权威域
       (帮我查到底)         (我只告诉你下一步问谁)

为什么这样分工? 如果所有客户端都对根服务器做递归查询,根服务器要替全世界跑腿,瞬间被压垮。改成迭代后,根服务器只需回一句"去问 .com 的服务器"就完事,压力极小。而本地 DNS 数量有限且有缓存,承担递归的开销是划算的。

完整的解析链(假设全部缓存未命中):

① 浏览器缓存        →  未命中
② 操作系统缓存      →  未命中
③ hosts 文件        →  未命中
④ 本地 DNS 缓存     →  未命中,开始迭代
⑤ 根域服务器        →  "去问 .com 的 TLD 服务器"
⑥ .com TLD 服务器   →  "去问 ns1.example.com"
⑦ 权威服务器        →  "93.184.216.34"  ← 终点
⑧ 本地 DNS 缓存结果并返回给客户端

八步里前四步都是缓存。实际生产中绝大多数查询在第 1~4 步就返回了,完整走完八步是少数情况。

4. 缓存与 TTL

每条 DNS 记录都带 TTL(Time To Live),单位秒,表示这条记录可以被缓存多久。

TTL 的取值是个权衡:

TTL 优点 缺点
(60~300s) 改 DNS 后生效快,适合灰度、故障切换 查询量大,解析延迟高
(86400s) 缓存命中率高,解析快 改了 DNS 要等很久才全网生效

实践经验:平时设长 TTL(如 1 小时),计划变更前提前几小时把 TTL 调短(如 60s),变更完成、观察稳定后再调回去。这样既保证日常性能,又让变更可控。

为什么"改了 DNS 还是访问旧 IP"? 因为链路上每一层都有缓存,任何一层没过期都会返回旧值。而且现实更糟——很多客户端和中间层不严格遵守 TTL:浏览器有自己的缓存策略,某些运营商 DNS 会强行延长 TTL 以降低自己的查询量。所以 DNS 变更永远不要假设它能在 TTL 时间内全网生效,切换 IP 时必须让新旧两个 IP 同时可用一段时间

手动清缓存:

# macOS
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# Linux (systemd-resolved)
sudo resolvectl flush-caches
# Windows
ipconfig /flushdns

排查工具首选 dig

dig example.com                    # 查 A 记录
dig example.com MX                 # 查指定类型
dig +trace example.com             # 完整展示迭代过程,排障最有用
dig +short example.com             # 只输出结果
dig @8.8.8.8 example.com           # 指定 DNS 服务器,用于对比排查

nslookup 也能用,但 dig 的输出更完整,+trace 尤其适合定位"哪一级解析出了问题"。

5. UDP 还是 TCP

DNS 默认用 UDP 53 端口,这是它快的关键——见 网络-06 UDP:一问一答两个包搞定,不用握手。

DNS 也会用 TCP,两种情况:

  1. 响应超过 512 字节。传统 DNS 限制 UDP 响应体最大 512 字节,超了就在响应里置 TC(Truncated)标志位,客户端收到后改用 TCP 重发查询
  2. 主从同步(AXFR/IXFR)。区域文件传输数据量大且要求可靠,直接走 TCP。

这也意味着防火墙只放开 UDP 53 是错的——遇到大响应会因 TCP 53 被封而解析失败。这类故障的特征很典型:小域名解析正常,某些返回记录多的域名(比如带一堆 A 记录的 CDN 域名)稳定失败。

现代做法是 EDNS0(RFC 6891),它允许客户端声明"我能接收更大的 UDP 包"(通常 4096 字节),从而避免大部分 TCP 回退。DNSSEC 的签名数据很大,基本依赖 EDNS0 才能走 UDP。

6. 加密 DNS

传统 DNS 是明文的,这带来两个问题:

  • 可被窃听:链路上任何节点都能看到你访问了哪些域名;
  • 可被篡改:这就是 DNS 劫持——运营商或攻击者返回一个假 IP,把你导向广告页或钓鱼站。国内曾经普遍存在的"运营商插广告"就是这么做的。

三种应对方案:

方案 全称 传输方式 特点
DoT DNS over TLS TLS,853 端口 独立端口,易于网络管理员识别和管控
DoH DNS over HTTPS HTTPS,443 端口 混在普通 HTTPS 流量里,难以被区分和封锁
DNSSEC DNS Security Extensions 仍是明文 给记录加数字签名,只防篡改不防窃听

关键区别要分清:DoH/DoT 解决的是机密性(防窃听)和传输完整性;DNSSEC 解决的是数据来源真实性(防篡改),但内容依然明文可见。三者不互相替代。

DoH 因为走 443 端口且和网页流量混同,抗封锁能力最强,Chrome 和 Firefox 都已默认或可选启用。但它也带来争议:企业和校园网的 DNS 层管控(内容过滤、审计)会被绕过,所以不少企业内网会主动禁用 DoH。

7. 实战:K8s 的 ndots 陷阱

这是 K8s 环境下最经典的 DNS 性能故障,也是"为什么要理解 FQDN 末尾那个点"的答案。

看一眼 Pod 里的 /etc/resolv.conf

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

ndots:5 的含义:如果待解析的域名中点的数量少于 5 个,就先依次拼接 search 里的每个后缀去尝试,全部失败后才当作绝对域名直接查。

于是在 Pod 里访问 api.github.com(2 个点,小于 5)会发生:

① api.github.com.default.svc.cluster.local  → NXDOMAIN ❌
② api.github.com.svc.cluster.local          → NXDOMAIN ❌
③ api.github.com.cluster.local              → NXDOMAIN ❌
④ api.github.com                            → 成功 ✓

一次解析变成 4 次查询。更糟的是,如果同时启用了 IPv6,每次查询会同时发 A 和 AAAA 两个请求,实际是 8 个 DNS 请求才换回一个 IP。高并发下这会把 CoreDNS 打满,表现为接口偶发超时、延迟毛刺,而应用日志里看不出任何异常——因为慢在解析阶段,不在业务逻辑里。

三种解法

# 方案一(推荐):域名末尾加点,声明为 FQDN,直接跳过所有 search 后缀
# 代码里写 "api.github.com." 而不是 "api.github.com"

# 方案二:单个 Pod 覆盖 ndots
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

# 方案三:集群层面部署 NodeLocal DNSCache,在每个节点本地做缓存

方案一最干净——一个点解决问题,零配置成本。但注意它只对你能改的代码有效,第三方 SDK 里硬编码的域名管不了,这种情况才需要方案二或三。

集群内部访问反而不受影响:my-service 只有 0 个点,第一个 search 后缀 my-service.default.svc.cluster.local 就命中了。受害的恰恰是访问外部域名的场景,这也是它容易被忽视的原因——本地开发和集群外测试都正常,一上集群就偶发超时。

8. 本章面试题

说一下完整的 DNS 解析过程

八步,前四步都是缓存:

  1. 浏览器缓存 → 2. 操作系统缓存 → 3. hosts 文件 → 4. 本地 DNS 缓存 → 5. 根域服务器(告知 TLD 服务器地址)→ 6. TLD 服务器(告知权威服务器地址)→ 7. 权威服务器(返回最终 IP)→ 8. 本地 DNS 缓存并返回

实际生产中绝大多数查询在前四步就返回了,走完全程是少数情况。

递归查询和迭代查询的区别?为什么这样设计?

区别在于谁替谁跑腿

  • 递归:被问方必须给出最终答案。客户端 → 本地 DNS 用递归。
  • 迭代:被问方只告诉你下一步问谁。本地 DNS → 根/TLD/权威用迭代。

为什么这样分工:如果所有客户端都对根服务器做递归,根服务器要替全世界跑腿,瞬间被压垮。改成迭代后根服务器只回一句"去问 .com",压力极小。本地 DNS 数量有限且有缓存,承担递归开销是划算的。

DNS 用 UDP 还是 TCP?什么时候用 TCP?

默认 UDP 53——一问一答,无需握手,快。

两种情况用 TCP

  1. 响应超过 512 字节:响应里置 TC(Truncated)标志位,客户端改用 TCP 重发;
  2. 主从区域传输(AXFR/IXFR):数据量大且要求可靠。

所以防火墙只放开 UDP 53 是错的。典型故障特征:小域名正常,返回记录多的域名稳定失败。

EDNS0 可声明支持更大的 UDP 包(通常 4096 字节),避免大部分 TCP 回退。

为什么改了 DNS 记录还是访问到旧 IP?

链路上每一层都有缓存(浏览器、操作系统、本地 DNS、运营商),任何一层未过期都会返回旧值

更麻烦的是很多客户端和中间层不严格遵守 TTL:浏览器有自己的策略,某些运营商 DNS 会强行延长 TTL 降低查询量。

所以切换 IP 时必须让新旧两个 IP 同时可用一段时间,永远不要假设 TTL 到了就全网生效。

实践:平时设长 TTL,计划变更前几小时调短到 60s,变更稳定后再调回。

CNAME 和 A 记录的区别?根域名能用 CNAME 吗?

A 指向 IP,CNAME 指向另一个域名。

根域名不能用 CNAME。因为根域必须同时有 SOA 和 NS 记录,而 CNAME 不允许与其他记录共存。想让根域名指向 CDN 只能用云厂商私有扩展(AWS ALIAS、阿里云隐性 URL)。

另外 CNAME 会多一次解析,链条越长首次解析越慢。CDN 接入基本都靠 CNAME。

DoH、DoT、DNSSEC 分别解决什么问题?

必须分清防的是什么

  • DoH(over HTTPS,443)/ DoT(over TLS,853)解决机密性——防窃听、防劫持。DoH 混在普通 HTTPS 流量里,抗封锁最强。
  • DNSSEC 给记录加数字签名,解决来源真实性——防篡改,但内容仍是明文,不防窃听

三者不互相替代。DoH 的副作用是绕过企业 DNS 层管控,所以不少企业内网主动禁用它。

K8s 里访问外部域名偶发超时,怎么排查?

优先怀疑 ndots:5。Pod 的 /etc/resolv.confoptions ndots:5 意味着:域名中点数少于 5 个时,先依次拼接 search 后缀尝试。

访问 api.github.com(2 个点)会变成 4 次查询(3 次 NXDOMAIN + 1 次成功);若启用 IPv6,A + AAAA 并发则是 8 个请求。高并发下打满 CoreDNS,表现为接口偶发超时,但应用日志无异常——慢在解析阶段。

解法:① 域名末尾加点写成 FQDN api.github.com.(最干净);② Pod 的 dnsConfig 覆盖 ndots:2;③ 部署 NodeLocal DNSCache。

注意受害的是访问外部域名——集群内 my-service 第一个后缀就命中了。所以本地开发正常、上集群才偶发超时。