域名 / DNS
api.example.com 可以先指向一台服务器,再迁移到负载均衡或 CDN,调用方仍使用原来的名字。地址变化由 DNS 记录表达;记录由哪组服务器回答,则由上级域的委派决定。两处配置分属不同层次,也可能由不同团队管理。
一次解析还会经过缓存。权威服务器已经返回新地址时,某些用户仍可能取得旧地址。理解名称的管理关系、查询经过的位置和每份数据的保存时间,才能判断应该改记录、修委派,还是继续检查访问入口。
域名怎样组织,又由谁管理
从 URL 中取出 DNS 要查询的名字
https://api.example.com:8443/orders?status=paid
├─ https 通信方案;这里使用 TLS 保护 HTTP
├─ api.example.com 目标主机名;DNS 查询使用这个名字
├─ 8443 显式端口;省略时 HTTPS 通常使用 443
├─ /orders 资源路径;由 HTTP 请求交给服务端
└─ status=paid 查询参数;不写入 A / AAAA 记录
api.example.com. 完全限定域名,末尾的点表示根
└─ . 根域
└─ com. 顶级域 TLD
└─ example.com. 本例中的注册域名
└─ api.example.com. 子域名,也可用作主机名域名从右向左体现层级,不能一律把“最后两段”认作可注册域。例如某些顶级域在下一层继续划分公共注册后缀。www 和 api 都是普通标签,没有必须存在的协议含义。
DNS 名称的单个普通标签最多 63 个八位字节,完整线上编码最多 255 个八位字节,包含标签长度和根标签;不能简单理解为任意 255 个汉字。国际化域名使用 IDNA 规则转换,常见 ASCII 形式以 xn-- 开头。排查中文域名时应同时核对显示名称与转换后的名称,避免相似字符造成误认。名称编码见 RFC 1035,国际化名称见 RFC 5890。
api.example.com. 的结尾点在区文件和查询命令中很重要:它明确表示绝对名称。区文件中省略结尾点的目标通常会追加当前 $ORIGIN;把 CNAME 目标写成 edge.example.net,可能实际保存成 edge.example.net.example.com.。云控制台可能自动补全,应以保存后的完整记录名为准。
注册、DNS 托管和服务部署是三项管理任务
| 角色或服务 | 管理内容 | 接管时应确认什么 |
|---|---|---|
| 域名持有人 Registrant | 按注册协议持有并管理域名 | 组织身份、联系人、续费方式、账户恢复 |
| 注册商 Registrar | 提供注册、续费、转移和上级委派设置入口 | 登录权限、域名状态、当前 NS、DNSSEC 的 DS 管理入口 |
| 注册局 Registry | 维护相应顶级域的注册数据与委派 | 该后缀的注册规则、状态及生命周期 |
| 权威 DNS 托管商 | 保存并对外回答某个区的记录 | 区是否启用、记录内容、授权成员、API 凭据 |
| 主机、负载均衡或 CDN | 接收解析后到达的业务连接 | 地址、端口、站点、证书与后端配置 |
这些服务可以由同一家厂商提供,也可以分开采购。换 DNS 托管商通常需要调整 NS 委派;换注册商通常涉及域名转移;换服务器地址通常只改记录。不能因为某个控制台允许添加 A 记录,就认定公网正在使用这组 DNS。
注册与续费关系可查 ICANN 域名持有人资料。接管未知域名时,用 ICANN Lookup 查询注册数据和状态,再与组织的注册商账户核对;隐私保护可能隐藏联系人,查询结果也不能替代账户授权。
clientHold、serverHold 会影响域名在 DNS 中的激活,原因应向注册商核实;它们与防止转移的 clientTransferProhibited 不同,详见 ICANN EPP 状态说明。域名到期后可能经历暂停、赎回和删除,各后缀及注册商政策不同。启用多因素认证、保持可恢复的组织邮箱、验证自动续费扣款并设置独立提醒,比故障发生后查一条 A 记录更能避免整域停用。
域名注册、DNS 解析和网站接入备案也分别办理。中国内地的网站接入及 CDN 等产品是否需要备案,应按业务形态、资源地域和接入商要求确认;DNS 返回地址不会完成这些手续。查询 工信部备案管理系统与接入商的具体说明,例如阿里云解析与备案 FAQ。
父区用 NS 委派,子区保存自己的记录
域名树可以分成多个独立管理的 区(zone)。example.com 下的 api、www 通常都保存在同一个区;只有建立委派后,相应子树才由另一组权威服务器负责。区和域名层级的定义见 RFC 9499。
com. 父区
example.com. NS ns1.example.net. ──指向 example.com 的权威服务器
↓
example.com. 区
@ SOA ... 区的管理与同步参数
@ NS ns1.example.net. 子区自身声明的权威服务器
api A 203.0.113.20
dev NS ns1.dev.example.com. ──将 dev 子树另行委派
ns1.dev A 192.0.2.53 启动查询所需的 glue 地址
↓
dev.example.com. 区
@ SOA ...
@ NS ns1.dev.example.com.
ns1 A 192.0.2.53
api A 192.0.2.20查询 api.dev.example.com 时,父区返回“请问 ns1.dev.example.com”。但这个服务器名字也位于被委派的子树内,仅靠它会形成“先查子区才能找到子区服务器”的循环。父区附带的 glue(胶水地址) 用来打破启动依赖;子区仍需维护服务器自身的权威地址。服务器在被委派子树以外时,解析器通常可以沿另一条委派链找到地址。委派机制见 RFC 1034。
父侧 NS、必要的 glue、子侧 NS 与权威服务器实际加载的区需要相互匹配。父区指向一台没有加载该区的服务器,会出现错误委派;只在子区增加一条 NS,无法替代注册商向父区提交的委派变更。
SOA 中的 serial 是区版本序号,供辅助权威服务器判断是否需要同步,refresh、retry、expire 控制其刷新和失联后的行为。它们不负责强制刷新所有递归缓存。多个权威节点必须从不同网络持续检查:一台更新、一台仍返回旧内容,会使用户在旧值与新值之间反复变化。
记录、查询和缓存如何共同决定结果
记录保存的是哪一种数据
一条常见区记录可拆成:
api.example.com. 300 IN A 203.0.113.20
└─ 所有者名称 TTL 类 类型 记录数据 RDATA同名、同类、同类型的记录组成 RRset。一个名字可以有多条 A,也可以同时有 A 和 AAAA;同一个 RRset 的 TTL 应保持一致。多个地址使客户端有多个候选,健康检查、故障剔除和按请求分流仍需要客户端或负载均衡系统配合。RRset 与 TTL 一致性见 RFC 2181。
| 类型 | 数据形态与常见用途 | 配置时容易漏掉的条件 |
|---|---|---|
| A | 203.0.113.20,IPv4 地址 | 不填协议、端口和 URL 路径 |
| AAAA | 2001:db8::20,IPv6 地址 | 同时验证 IPv6 路由、监听、防火墙、TLS;有 IPv4 成功结果还不够 |
| CNAME | edge.example.net.,该名称的别名目标 | 目标仍需继续解析;避免循环和过长链 |
| NS | ns1.example.net.,区的权威服务器名 | 注意父侧委派、子侧声明及必要的 glue |
| SOA | 主服务器、管理员邮箱编码、序号和计时参数 | 每区必需;负缓存也会使用其中的字段 |
| MX | 10 mx1.example.net.,邮件交换服务器 | 数值越小越优先;目标是主机名,不是 IP,也不应是 CNAME |
| TXT | "verification=...",文本数据 | 多用于归属验证、SPF、DKIM、DMARC;核对完整记录名,勿覆盖已有值 |
| PTR | api.example.com.,反向名称 | 由 IP 所属反向区管理;增加 A 不会自动得到 PTR |
| SRV | 10 5 8443 service.example.com. | 依次为优先级、权重、端口、目标;客户端必须支持对应服务发现协议 |
| CAA | 0 issue "letsencrypt.org" | 约束证书签发授权;不会让浏览器自动信任任意证书 |
| HTTPS / SVCB | 优先级、目标和服务参数 | 支持的客户端可发现协议或端点参数,需与 A/AAAA 及实际服务保持一致 |
基础记录格式见 RFC 1035,AAAA、SRV、CAA、HTTPS/SVCB 分别见 RFC 3596、RFC 2782、RFC 8659和 RFC 9460。日常配置选用服务实际要求的记录即可,无须把全部类型都添加一遍。
控制台的“主机记录 @”通常表示当前区顶点,api 表示 api.example.com;有的界面接受完整名称,有的自动拼接后缀。TXT 的外层引号也可能由界面代为处理。保存后直接查询完整名称,确认实际返回,而不是只检查输入框。厂商操作可参考阿里云添加解析记录。
CNAME、裸域和泛解析的限制
CNAME 表示同名数据应从别名目标取得,因此该名称不能再同时拥有普通 A、MX、TXT 等数据;DNSSEC 的相关元数据有规范例外。区顶点必须有 SOA、NS,也就无法再放一个标准 CNAME。服务商的 ALIAS、ANAME、CNAME flattening 属于产品能力,通常由服务端追踪目标后合成地址答复,查询者未必看到 CNAME。使用前核对目标支持范围、TTL 和 DNSSEC 行为,例如 Cloudflare CNAME flattening。
www.example.com CNAME edge.example.net 也不会把浏览器 URL 改成 edge.example.net。浏览器仍以 www.example.com 发送 SNI 和 HTTP 主机信息;跳转 URL 要由 HTTP 重定向完成。
DNS 泛解析有明确的名称树规则:
*.example.com. A 203.0.113.20
shop.example.com. TXT "existing-name"
查询 new.example.com A → 可从通配记录合成 A
查询 shop.example.com A → 名称已存在,但没有 A;不会借用通配 A通配记录依据“最接近的已存在祖先”选择,存在的节点及委派会影响匹配。没有中间节点时,它甚至可能为更深层的名字合成答案,不能套用证书的单层通配规则。具体算法见 RFC 4592。TLS 证书中的 *.example.com 则按服务身份规则匹配一个最左标签,不能代替 example.com 或 a.b.example.com 的证书名称,见 RFC 9525。
递归解析器怎样找到权威答案
应用通常把查询交给系统解析设施,系统再联系配置的递归解析器。递归解析器承担查找最终答案的工作;迭代查询中的服务器可以只返回下一组应查询的 NS。企业解析器还可能把请求转发给上游,而不自行从根查起。
应用自己的名称缓存或连接池
↓ 需要重新解析时
系统解析路径:NSS / hosts / 本地解析服务 / 应用自带 DNS 客户端
↓ 需要 DNS 查询时
递归解析器
├─ 命中可用缓存 → 直接返回
├─ 匹配转发规则 → 向指定上游查询
└─ 自行迭代且缺少相关缓存
根 → TLD → 目标区权威服务器
├─ A / AAAA 等答案
├─ CNAME → 继续查询目标
└─ 不存在或无此类型的否定答案
↓
应用选取端点 → 建立传输连接 → HTTPS 校验 → HTTP 请求这是几种常见路径的组合,不要求每次查询都访问根和 TLD。递归解析器会缓存委派、地址和答案;浏览器启用安全 DNS 或应用使用自己的解析库时,还可能绕过部分系统设置。
传统 DNS 使用 UDP 和 TCP 的 53 端口。UDP 答复太大可能设置 TC,客户端再使用 TCP;TCP 也可直接用于普通查询,并非只负责区传送。只放行 UDP 53 会造成某些名称能查、某些大答复失败。传输要求见 RFC 7766。DoT、DoH 分别用 TLS、HTTPS 保护查询传输,不能因此推断所返回的数据已经通过 DNSSEC 验证,协议见 RFC 7858与 RFC 8484。
TTL 保存在哪,更新为什么会延迟
权威区给出记录的 TTL。递归解析器收到答案后保存副本,通常随着时间递减剩余 TTL;下一次命中缓存时直接返回副本,不再询问权威。
t=0 递归器取得旧地址,TTL=3600
t=600 管理员把权威记录 TTL 改成 300
该递归器仍持有旧地址,剩余约 3000 秒
t=3600 缓存过期;后续查询才可能重新取得权威当前值降低权威 TTL 不会追溯修改已经发出去的副本。切换前需先让全部权威节点发布较低 TTL,再为旧缓存留出原 TTL 的消退时间;CNAME、目标地址、NS 和 glue 还可能各有自己的缓存。
“查不到”也会被缓存。NXDOMAIN 表示名称不存在;NODATA 通常表现为 NOERROR 但没有所需类型的答案,表示存在名称却没有该类型的数据。权威否定答复携带 SOA 时,负缓存 TTL 按 SOA 自身 TTL 与 SOA MINIMUM 的较小值确定。新建记录后仍短暂查不到,可能正在命中此前的负缓存。算法见 RFC 2308。
SERVFAIL 表示解析过程失败,例如上游不可达、委派异常或 DNSSEC 校验失败。它没有证明名称不存在;解析失败本身也可能被短时缓存,见 RFC 9520。
TTL 不能作为“所有客户端已切换”的时刻承诺。有些解析器会在权威不可达时按 serve-stale 策略继续返回过期数据;应用还可能另存名称结果,已建立的长连接也不会因 DNS 更新而迁移。serve-stale 的适用条件见 RFC 8767。旧入口何时退役,要结合解析观察与实际连接、请求和业务状态决定。
用 Linux 查询,并复现正缓存和负缓存
先学会阅读一份答复
Ubuntu 24.04 主机上,管理员可安装 sudo apt-get update、sudo apt-get install -y dnsutils curl。后续查询用普通用户运行,不需要 sudo。无公网出口时,使用组织的软件仓库或事先准备的同架构工具镜像,不临时把生产主机的解析器改成公共 DNS。
以下命令是只读查询模板。将 NAME 改为待查名称,将 RESOLVER_IP 改为当前网络批准使用的递归器地址;192.0.2.53 是文档地址,不能原样当真实服务使用。
NAME=api.example.com.
RESOLVER_IP=192.0.2.53
dig -r "@$RESOLVER_IP" "$NAME" A +time=2 +tries=1-r 禁止读取用户 .digrc,减少默认参数干扰;结尾点避免名称歧义。+time=2 +tries=1 限定本次查询的等待和尝试。dig 的参数、状态与 +trace 语义见 BIND 工具手册。
下面是答复形态示例,事务 ID 和耗时等动态字段省略:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0
;; QUESTION SECTION:
;api.example.com. IN A
;; ANSWER SECTION:
api.example.com. 287 IN A 203.0.113.20
;; SERVER: 192.0.2.53#53先看 QUESTION 是否查对名称和类型,再看 status、各区段及 SERVER。rd 表示请求希望递归,ra 表示服务器提供递归能力;它们没有说明本次是否命中缓存。aa 表示权威答复,ad 是验证解析器对数据真实性的声明,需要可信的解析器及可信通信路径,不能仅凭一个标志信任陌生服务器。
287 在这里是剩余 TTL。ANSWER: 0 要继续区分 NXDOMAIN、NODATA、referral 和失败,不能直接说“没有配置”。+short 适合提取已确认的结果,却会隐藏这些诊断信息。DNS 返回 NXDOMAIN 时 dig 通常仍退出 0;自动检查必须同时判断响应状态和答案。
创建一个没有外部网络的实验环境
实验使用两个真实 BIND 进程:权威服务器监听 127.0.0.1:15353,缓存解析器监听 127.0.0.1:15354。两者都在同一容器中,运行 UID 10001。容器不发布端口、不挂宿主目录,启动时使用 --network none;查询只在回环接口流动。
宿主需要 Linux Bash 与可用的 Docker Engine,Docker Desktop 可在 WSL 的 Bash 中执行。构建镜像需要拉取 Ubuntu 并访问软件源,运行实验不需要外网。能操作 Docker daemon 的账户通常拥有较高宿主权限,应在开发或专用测试环境执行。
在一个新工作目录中把下列内容保存为 Dockerfile。构建安装阶段使用 root,最终 USER 明确切换成普通身份。
FROM ubuntu:24.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends bind9 dnsutils ca-certificates && rm -rf /var/lib/apt/lists/*
USER 10001:10001
WORKDIR /tmp
CMD ["bash"]然后在宿主该目录运行:
docker build -t local/domain-dns-lab:1 .
docker run --rm -it --network none local/domain-dns-lab:1 bash后面的实验命令全部在这个容器的同一个 Bash 会话内执行。先确认身份和版本,再创建只属于本次实验的目录:
id
named -v
dig -v
LAB_DIR=$(mktemp -d /tmp/domain-dns.XXXXXX)
mkdir "$LAB_DIR/auth" "$LAB_DIR/cache"id 应显示 uid=10001 gid=10001。这组命令在 Ubuntu 24.04 的 BIND 9.18.39 上验证;更新仓库可能改变补丁版本,以 named -v 为准。创建目录失败先检查容器身份和 /tmp 可写性。
建立权威区和缓存转发器
区名使用保留测试后缀 .test,不应添加到公网域名控制台;特殊用途名称见 IANA 登记表。在容器内执行:
cat > "$LAB_DIR/auth/lab.zone" <<'ZONE'
$ORIGIN lab.test.
$TTL 20
@ 8 IN SOA ns.lab.test. hostmaster.lab.test. ( 1 60 30 3600 8 )
@ IN NS ns.lab.test.
ns IN A 127.0.0.1
api IN A 192.0.2.10
www IN CNAME api.lab.test.
ZONE
cat > "$LAB_DIR/auth/named.conf" <<CONF
options {
directory "$LAB_DIR/auth";
listen-on port 15353 { 127.0.0.1; };
listen-on-v6 { none; };
recursion no;
allow-query { 127.0.0.1; };
allow-transfer { none; };
dnssec-validation no;
pid-file "$LAB_DIR/auth/named.pid";
session-keyfile "$LAB_DIR/auth/session.key";
};
controls {};
zone "lab.test" { type primary; file "lab.zone"; };
CONF
cat > "$LAB_DIR/cache/named.conf" <<CONF
options {
directory "$LAB_DIR/cache";
listen-on port 15354 { 127.0.0.1; };
listen-on-v6 { none; };
recursion yes;
allow-query { 127.0.0.1; };
allow-recursion { 127.0.0.1; };
allow-transfer { none; };
dnssec-validation no;
empty-zones-enable no;
stale-answer-enable no;
pid-file "$LAB_DIR/cache/named.pid";
session-keyfile "$LAB_DIR/cache/session.key";
};
controls {};
zone "lab.test" {
type forward;
forward only;
forwarders { 127.0.0.1 port 15353; };
};
CONF
named-checkconf "$LAB_DIR/auth/named.conf"
named-checkconf "$LAB_DIR/cache/named.conf"
named-checkzone lab.test "$LAB_DIR/auth/lab.zone"named-checkconf 成功时通常不输出内容,退出状态为 0;named-checkzone 应报告 loaded serial 1 和 OK。失败就停在配置检查阶段,按报错行修复。这里 SOA 的序号是 1,最后的 8 与 SOA 自身 TTL 8 使否定答复最多按 8 秒缓存;A 记录继承 $TTL 20。
recursion no 让权威进程只回答自己负责的数据。缓存进程仅把 lab.test 转发到指定高端口,forward only 禁止该区转发失败后再自行迭代。禁用 DNSSEC 是因为实验区没有签名及信任配置,禁用过期答复是为了清楚观察 TTL 到期;这些选择不能照搬到公共递归服务。配置项见 BIND 配置参考。
检查通过后启动两个进程,并保存各自 PID:
named -g -c "$LAB_DIR/auth/named.conf" > "$LAB_DIR/auth.log" 2>&1 &
AUTH_PID=$!
named -g -c "$LAB_DIR/cache/named.conf" > "$LAB_DIR/cache.log" 2>&1 &
CACHE_PID=$!
dnsq() {
dig -r @127.0.0.1 -p "$1" "$2" "$3" +time=1 +tries=1 \
+noall +comments +answer +authority
}
dnsq 15353 api.lab.test. A
dnsq 15354 api.lab.test. A两次都应返回 192.0.2.10。查询权威进程时可能看到 recursion requested but not available:dig 默认请求递归,而这个进程关闭了递归;只要有正确的 aa 答案,这条提示不表示区加载失败。首次进程尚未就绪时可能拒绝连接,稍后重新查询;持续失败查看 tail -n 30 "$LAB_DIR/auth.log" 与缓存日志,先解决启动或端口错误。
再对比类型、别名与传输:
dnsq 15353 www.lab.test. A
dnsq 15353 api.lab.test. AAAA
dnsq 15353 missing.lab.test. A
dig -r @127.0.0.1 -p 15353 api.lab.test. A +tcp +norecurse \
+time=1 +tries=1 +noall +comments +answer
dig -r @127.0.0.1 -p 15353 unrelated.example. A +recurse \
+time=1 +tries=1 +noall +comments +answerwww 的回答包含 CNAME 和目标 A;api AAAA 是 NOERROR、没有 AAAA 答案并带 SOA;missing A 是 NXDOMAIN 并带 SOA。TCP 查询应返回 A 和 aa。最后一条请求权威进程递归查询不负责的区,应得到 REFUSED,也不会出现 ra。这说明权威回答、无此类型、名称不存在和拒绝递归是四种不同结果。
只更新权威,观察两份缓存
下面整个代码块连续执行:先预热正缓存和负缓存,再更新区内容、增加序号并只重载权威进程。不要在中间停留超过演示 TTL,也不要重启缓存进程。
dnsq 15354 api.lab.test. A
dnsq 15354 future.lab.test. A
cat > "$LAB_DIR/auth/lab.zone" <<'ZONE'
$ORIGIN lab.test.
$TTL 5
@ 8 IN SOA ns.lab.test. hostmaster.lab.test. ( 2 60 30 3600 8 )
@ IN NS ns.lab.test.
ns IN A 127.0.0.1
api IN A 192.0.2.20
www IN CNAME api.lab.test.
future IN A 192.0.2.30
ZONE
if named-checkzone lab.test "$LAB_DIR/auth/lab.zone"; then
kill -HUP "$AUTH_PID"
fi
# 重载异步执行;等到权威 SOA 序号为 2,再进行对照。
for attempt in {1..20}; do
SERIAL=$(dig -r @127.0.0.1 -p 15353 lab.test. SOA +short +time=1 +tries=1 | awk '{print $3}')
[ "$SERIAL" = 2 ] && break
sleep 0.1
done
if [ "$SERIAL" = 2 ]; then
dnsq 15353 api.lab.test. A
dnsq 15354 api.lab.test. A
dnsq 15353 future.lab.test. A
dnsq 15354 future.lab.test. A
else
printf '%s\n' '权威区尚未加载序号 2,先检查 auth.log,停止结果判断。'
fi预期对照如下,剩余 TTL 取决于执行速度:
| 查询位置 | api A | future A |
|---|---|---|
| 权威 15353 | 192.0.2.20,TTL 5 | 192.0.2.30,TTL 5 |
| 缓存 15354,原缓存未到期 | 192.0.2.10,剩余 TTL ≤20 | NXDOMAIN,SOA 中剩余 TTL ≤8 |
一次连续执行取得的关键输出如下,省略了事务 ID、Cookie 和无关区段:
权威 api A:
flags: qr aa rd
api.lab.test. 5 IN A 192.0.2.20
缓存 api A:
flags: qr rd ra
api.lab.test. 20 IN A 192.0.2.10
缓存 future A:
status: NXDOMAIN
lab.test. 8 IN SOA ns.lab.test. hostmaster.lab.test. 1 60 30 3600 8旧否定答复里甚至仍带 SOA 序号 1,与已经加载序号 2 的权威进程形成直接对照。
权威已经加载序号 2,缓存进程却不读取这个区文件,也不会因权威收到 HUP 就清除自己的数据。新增 future 没能立即覆盖旧的“不存在”答复;把 A 的 TTL 降到 5 也没能改写缓存中旧 A 的到期时间。
如果两个位置立即都返回新值,先检查执行是否跨过旧 TTL,以及缓存进程是否被重启。这种结果说明本次未保留住缓存对照,不能据此推翻缓存机制。退出容器后重新运行实验可恢复初始状态。
等待覆盖演示中的最长旧 TTL,再查询:
sleep 21
dnsq 15354 api.lab.test. A
dnsq 15354 future.lab.test. A
kill "$CACHE_PID" "$AUTH_PID"
wait "$CACHE_PID" "$AUTH_PID"
exit两条查询应分别返回 192.0.2.20 和 192.0.2.30。退出后 --rm 删除本实验容器及其临时文件,宿主构建目录与镜像仍保留。整个实验只改变了容器内部的区与缓存,没有设置宿主 DNS,也没有测试公网根委派或真实 DNSSEC 信任链。
系统与应用为什么可能查出不同结果
dig、getent 和 systemd-resolved 各在查什么
dig @服务器 直接向指定 DNS 服务器查询。使用系统名称服务的程序则可能按 /etc/nsswitch.conf 的 hosts: 顺序查询文件、解析服务或 DNS,/etc/hosts 只是其中一个来源。读取方式见 nsswitch.conf 手册。
在出现问题的 Ubuntu 主机上,以普通用户执行以下只读检查;NAME 换成故障名称:
NAME=api.example.com
grep '^hosts:' /etc/nsswitch.conf
grep -F "$NAME" /etc/hosts
readlink -f /etc/resolv.conf
cat /etc/resolv.conf
getent ahosts "$NAME"
dig -r "$NAME." A +time=2 +tries=1grep 没找到条目会返回非零状态,这是“未匹配”,无需因此修改文件。getent ahosts 展示系统名称服务得到的地址,可能因套接字类型而重复;dig 展示 DNS 查询结果,通常不走 NSS 的 hosts 文件查找。两者不同,先追解析路径。也要注意 systemd-resolved 本身可以读取 hosts,因此查询它的 stub 时可能间接受到 hosts 影响。
如果 /etc/resolv.conf 指向 systemd-resolved 的 stub 文件,看到 127.0.0.53 是本地解析入口;实际的上游和按接口路由规则还需继续查:
resolvectl status
resolvectl query "$NAME"
resolvectl statistics这些命令只适用于正在使用 systemd-resolved 的系统。关注每块网卡的 DNS Servers、DNS Domain 与 DefaultRoute,尤其是 VPN 接口。~corp.example.com 是按该后缀路由查询的域,~. 可把所有未更具体匹配的名称导向某条连接;最长匹配的域会影响选择。官方机制和命令分别见 systemd-resolved与 resolvectl。
确认权威已经修复、需要复测本机缓存时,可在有权限的主机上执行 sudo resolvectl flush-caches,然后重新查询。它清理的是该本地服务的缓存;递归上游、浏览器和应用里的副本仍可能存在。不要用手改 /etc/resolv.conf 的方式覆盖 NetworkManager、Netplan 或 VPN 客户端生成的配置,应回到实际管理者修改并验证重新连接后的结果。
内外网分区解析与容器命名空间
同一个名称在公网返回公网入口,在企业网络返回私有地址,称为分区解析(split DNS)。这可以减少绕行并隐藏内部地址,但要求客户端能选中正确的解析器,而且获得的地址确实可达。
把企业 DNS 换成公共 DNS 可能使公网名称恢复,却同时破坏内部服务发现,还会把内部名称发送到外部。排障对比应先选组织批准的解析器;只有公开域名且网络政策允许时,再从外部观测点查询。办公室、VPN、服务器子网和容器网应分别取样。
名称规划优先使用组织持有域名下的专用子域,如 corp.example.com。.local 有 mDNS 特殊用途,容易与单播内网 DNS 冲突;.test 适合隔离测试,不适合作为组织的公网名称。离线部署要准备内部区、客户端解析配置、工具和必要的证书信任材料,不能靠每台机器长期手填 hosts。
Docker 容器拥有自己的网络命名空间。容器里的 127.0.0.1 指向容器自身,不能把宿主回环上的 DNS 服务地址直接塞给容器。默认 bridge 与自定义网络的解析处理也有区别;自定义网络通常使用 Docker 内嵌 DNS 127.0.0.11,并转发外部查询。具体规则见 Docker 网络文档。
在已获授权排查的 Docker 主机上,把 CONTAINER 换成现有应用容器名,然后执行:
CONTAINER=app
docker inspect --format '{{.HostConfig.NetworkMode}} {{json .HostConfig.Dns}} {{json .HostConfig.DnsSearch}}' "$CONTAINER"
docker exec "$CONTAINER" cat /etc/resolv.conf
docker exec "$CONTAINER" cat /etc/hosts
docker exec "$CONTAINER" getent ahosts api.example.com这些输出用于定位解析配置,不需要重启 daemon。精简镜像没有 getent 时,使用获准的诊断方式查看实际应用的解析结果;另起诊断容器可以测网络,但它的 hosts、搜索后缀和用户空间配置未必与应用相同。
Kubernetes Pod 通常通过集群 DNS 解析 Service,并配置自己的 search 和 ndots。短名称可能先追加多个搜索后缀;排查时同时检查 Pod 的 /etc/resolv.conf、dnsPolicy 和完整查询名,具体见 Kubernetes DNS 调试。宿主查询成功后,仍要在真正发起业务请求的 Pod 内验证。
浏览器和 Java 还保存哪些状态
浏览器可能使用安全 DNS、系统代理、名称缓存、连接池,以及 HTTP 缓存。F12 的 Network 面板可以查看具体请求的 Remote Address、Timing、状态码和是否命中浏览器缓存;Disable cache 主要针对 HTTP 缓存,不是全网 DNS 刷新开关。Timing 中没有 DNS Lookup 阶段,也可能是连接复用或已有解析结果。面板说明见 Chrome DevTools Network。
先选定发生故障的那条请求,不要把页面里任意一个静态资源的地址当成 API 的地址。开启代理时,远端连接信息也要结合代理路径理解。把 Network 截图或 HAR 发给别人前,清理 Cookie、Authorization、请求体和内部地址。
Java 的 InetAddress 会缓存成功和失败的名称查询,相关配置包括 networkaddress.cache.ttl、networkaddress.cache.negative.ttl,较新 JDK 还提供过期数据缓存策略。它们是安全属性,不能随意假设加一个同名 -D 就会生效;默认值和具体解析实现要按所用 JDK 核实,见 Java InetAddress 文档。
Spring Boot 应用还可能通过 HTTP 客户端、Netty 或连接池保留已连接的端点。操作系统查询已更新,应用仍连接旧地址时,检查应用解析器与连接生命周期,不要立即重复修改 DNS。把缓存全部设成零会增加查询压力,也不能迁移已经建立的 TCP 连接。
域名接入与迁移怎样验证
分开验证解析、TLS 和 HTTP 站点
首次接入应先准备可达入口、正确的站点配置和证书,再建立记录或进入批准的切换窗口。对于通常的 HTTPS 访问,选定地址后建立连接,TLS 按主机身份校验证书,随后才发送 HTTP。DNS 记录不包含 Nginx server_name,也不会自动申请证书。
在有权限访问新入口、且网络政策允许直连的 Linux 测试端,定义下面三个变量。这里的域名和地址仍是占位值,CHECK_PATH 必须替换成已知、只读并预期返回 200 的检查端点。
HOST_NAME=api.example.com
TARGET_IP=203.0.113.20
CHECK_PATH=/health
curl -q --noproxy '*' --fail-with-body --silent --show-error \
--connect-timeout 3 --max-time 10 \
--resolve "$HOST_NAME:443:$TARGET_IP" \
--write-out '\nhttp=%{http_code} peer=%{remote_ip} tls=%{ssl_verify_result}\n' \
"https://$HOST_NAME$CHECK_PATH"-q 放在第一个选项位置,跳过默认 curl 配置;--noproxy '*' 关闭本次请求的代理路径。--resolve 只替换指定主机和端口的地址选择,URL 主机名继续用于 SNI、证书校验和 HTTP。参数职责见 curl 官方手册。
预期是正确的健康响应体、http=200、peer 等于目标地址、tls=0,且命令退出 0。--fail-with-body 会把 HTTP 400 以上转成失败,但不会把 301 等重定向当错误,因此仍要核对精确状态与响应内容。证书错误先核对 SAN、信任链和实际选中的站点;连接错误转到防火墙 / 端口 / 网络。不要添加 -k 把身份校验失败掩盖掉。
再以同样的代理、端口、路径和超时条件进行正常名称查询对照:
curl -q --noproxy '*' --fail-with-body --silent --show-error \
--connect-timeout 3 --max-time 10 \
--write-out '\nhttp=%{http_code} peer=%{remote_ip} tls=%{ssl_verify_result}\n' \
"https://$HOST_NAME$CHECK_PATH"指定地址成功而普通访问失败时,对比普通访问选中的地址与解析路径:可能是旧缓存、AAAA、另一个入口或分区策略。这个对照没有验证所有用户网络,也没有证明业务全部正常。网络必须通过代理时,保留实际代理路径另做验证,并确认名称在本地还是代理端解析,不能沿用上述直连结论。
直接访问 https://IP 再手加 Host,不会等价地保留 TLS 主机身份。站点匹配见 Nginx 请求处理,完整配置与证书部署分别参阅 Nginx 与反向代理和 SSL 证书。
DNS-01 与 CDN 接入需要额外检查什么
DNS-01 验证会读取 _acme-challenge 下的 TXT。例如验证 api.example.com 时,查询 _acme-challenge.api.example.com;验证 *.example.com 时则使用 _acme-challenge.example.com。公网 CA 要能从公网权威 DNS 取得挑战值,业务服务器本身可以在内网。只把 TXT 放入企业内部区,公网验证端仍看不到。
可以通过 CNAME 或 NS 把挑战名称交给专用验证区,但 ACME 客户端的插件是否会自动跟随并写入目标区,需另行确认。多次并发验证可能需要同时保留多个 TXT 值。自动化账户应只获准修改所需区或记录;厂商无法限制到单记录时,可用专用挑战区缩小影响。协议与实践见 Let’s Encrypt 挑战类型。续期还需检查记录发布、签发、证书部署和服务重载,不能以“TXT 已添加”作为终点。
接入 CDN 后,客户端通常先连接边缘节点,再由 CDN 连接源站。代理模式与 DNS-only 的地址语义可参考 Cloudflare 代理状态。DNS 返回 CDN 地址仅说明本次查询得到候选入口,要结合实际请求的远端地址、CDN 日志和源站日志确认流量。
边缘与源站各自涉及协议、证书、端口和 Host。源站可能只允许 CDN 地址访问,或要求回源认证;此时测试机直连失败是访问策略的预期结果,不能据此删除限制。CDN 内容缓存与 DNS 缓存分别刷新,删除 DNS 记录不会清空已经缓存的页面。
A / AAAA / CNAME 切换与回退
切换准备中要写清具体记录名、类型、旧值、新值、当前 TTL、各权威节点、检查端点和回退值。同时确认旧入口、新入口在过渡期间使用兼容的数据和会话方案,否则双入口可达仍可能产生业务不一致。
提前准备
全部权威发布较低 TTL → 等原 TTL 的缓存窗口消退
新入口直连验证 → 证书、站点、只读功能及数据兼容性通过
↓
修改一个已批准的记录集
逐台权威检查 → 从实际递归器和用户网络取样
↓
新旧入口同时观察
连接 / 请求量、错误率、延迟、TLS 与业务结果
├─ 稳定:恢复合适 TTL;确认残留使用情况后退役旧入口
└─ 异常:按故障组件回退;DNS 改回后仍保留过渡服务较低 TTL 会增加递归器向权威查询的频率,也可能增加托管费用与上游失联时的影响;根据变更窗口选值,不长期照搬实验里的 5 秒。权重或地域解析如果由服务商支持,其比例通常作用于 DNS 答复,受递归共享缓存影响,不能保证每个用户或每个 HTTP 请求按精确比例分流。
不要只切 A 而遗漏 AAAA、CNAME 目标或 HTTPS 记录中的端点信息。发生错误时先确认是新应用、证书、CDN 还是记录本身有问题;在对应组件修复可能比反复改 DNS 更快。DNS 回退也要等待已分发的新值消退,旧证书、入口权限和服务应保留到实际使用情况允许下线。
更换权威服务商,还要迁移委派和签名关系
更换 NS 会改变“向谁查询”,与只改 A 的影响不同。先在新服务商建立完整区,迁移的不仅是 Web 地址,还包括邮件、TXT 验证、CAA、SRV、子区委派和通配记录;原服务商的线路、代理、flattening 等产品设置不能只靠标准区文件推断。
保留可恢复的区导出与产品设置记录,变更账户和自动化 token 分开授权。自管权威服务要限制区传送和动态更新的来源及身份,递归服务只面向批准的客户端;公开权威查询和向任何人开放递归是两种不同权限。前面的实验分别用 allow-transfer 与 allow-recursion 表达了这项区别。
先直接查询每个新权威节点,核对 SOA、NS 和业务记录,并测试 TCP/UDP 可达。确认新旧两组能在过渡期间返回一致数据后,再在注册商或父区管理入口修改委派及必要的 glue。随后从父区检查实际发布结果,继续保留旧权威服务,直到委派缓存和残留查询得到充分观察。只删除旧服务商账户会让仍缓存旧 NS 的解析器无处查询。
DNSSEC 通过 DNSKEY、RRSIG 和父区 DS 等记录建立签名验证链,保护数据来源和完整性;它不加密 DNS 查询。基础模型见 RFC 4033。父区仍保存旧 DS,而新服务商没有相应密钥或签名时,验证解析器可能返回 SERVFAIL。
因此,已启用 DNSSEC 的区要按两家服务商支持的迁移方案协调密钥、签名和 DS 生效顺序,先验证链路再改变父侧信任关系。不能把“关闭校验”当作对用户的修复,也不能先删旧签名再慢慢研究迁移。自管多权威同步、签名与密钥生命周期可从 BIND DNSSEC 指南继续查阅。
按答复状态定位故障
先查用户正在使用的解析器,再查父区和权威
故障记录至少包含完整名称、类型、执行端网络、所用解析器、返回状态、地址和剩余 TTL。分享时去掉账号、令牌及不必要的内部拓扑,不公开包含敏感数据的完整日志。
在获准进行公网 DNS 查询的 Linux 测试端,可以用以下模板观察委派。把示例名称替换成待查的公开域名,不向公网发送内部名称:
ZONE_NAME=example.com.
NAME=api.example.com.
dig -r "$ZONE_NAME" NS +time=2 +tries=1
dig -r +trace "$NAME" A +time=2 +tries=1+trace 由运行 dig 的机器执行迭代,初始根信息取得后会直接查询相应服务器。企业网络若只允许访问指定递归器,追踪可能在中途超时;这不能证明该位置的权威服务宕机。它也不是对某个递归解析器内部历史的抓取,更不会自动完成 DNSSEC 验证。
从注册商配置和追踪结果中核对父区、权威服务器的实际 IP;以下地址仅作占位。使用 IP 查询能避免“先解析 NS 主机名”引入另一次未知依赖。
PARENT_IP=192.0.2.53
AUTH_IP=192.0.2.54
dig -r "@$PARENT_IP" "$ZONE_NAME" NS +norecurse +time=2 +tries=1
dig -r "@$AUTH_IP" "$ZONE_NAME" NS +norecurse +time=2 +tries=1
dig -r "@$AUTH_IP" "$ZONE_NAME" SOA +norecurse +time=2 +tries=1
dig -r "@$AUTH_IP" "$NAME" A +norecurse +time=2 +tries=1
dig -r "@$AUTH_IP" "$NAME" AAAA +norecurse +time=2 +tries=1父区的 NS 可能出现在 AUTHORITY 区段;子区权威回答应与预期区相符。对每个委派目标分别执行,而不是只测列表第一台。父侧指错查注册商或父区配置,子侧记录不一致查区加载、同步与视图;所有权威一致后,才进一步解释递归缓存或客户端差异。
把现象落实到下一项检查
| 结果或现象 | 能支持的判断 | 下一项检查 |
|---|---|---|
| NXDOMAIN | 查询链路认为该名称不存在 | 完整名称、搜索后缀、父侧委派、权威同名查询、负缓存 SOA |
| NOERROR 但所需答案为空 | 可能是 NODATA,也可能是尚需继续处理的 referral/CNAME | 查看 authority、CNAME 目标及目标类型,不只看 +short |
| SERVFAIL | 解析未能完成 | 权威可达性、错误委派、签名链、解析器日志、失败缓存 |
| REFUSED | 该服务器拒绝这类请求 | 是否问错服务器、递归 ACL、来源、查询权限 |
| timeout / no servers could be reached | 在等待窗口内未取得可用答复 | 执行端到所选服务器的 UDP/TCP 路径、端口和监听 |
| 权威新值、递归旧值 | 至少观察到了两份不同数据 | 剩余 TTL、CNAME/NS 缓存、serve-stale、全部权威是否一致 |
| dig 正常、应用失败 | DNS 工具路径可用 | NSS、VPN、应用解析器、连接池、代理和 IPv6 选址 |
| 地址正确但证书或页面错误 | 已进入入口身份或站点处理环节 | SNI、证书 SAN/链、Host、CDN 回源与默认站点 |
怀疑 UDP/TCP 差异时,从同一执行端对同一个已批准的解析器比较:
RESOLVER_IP=192.0.2.53
NAME=api.example.com.
dig -r "@$RESOLVER_IP" "$NAME" A +notcp +ignore +time=2 +tries=1
dig -r "@$RESOLVER_IP" "$NAME" A +tcp +time=2 +tries=1第一条的 +ignore 禁止收到截断 UDP 答复后自动转 TCP,便于观察 TC;它是诊断选择,不是应用的通用推荐。UDP 有 TC 而 TCP 超时,优先检查 TCP 53 路径;两条都失败则先确认解析器地址与连通性。具体网络定位沿用网络篇。
对 DNSSEC 相关 SERVFAIL,可以在同一个获准使用的验证解析器上作一次只读对比:
dig -r "@$RESOLVER_IP" "$NAME" A +dnssec +time=2 +tries=1
dig -r "@$RESOLVER_IP" "$NAME" A +dnssec +cdflag +time=2 +tries=1+dnssec 请求相关签名数据,并不让 dig 自己验证。若通常查询 SERVFAIL,而设置 CD 后得到数据,应进一步核对 DS、DNSKEY、RRSIG、签名有效时间及验证日志;这只是指向验证过程的线索。CD 返回的数据尚不能作为可信修复结果,更不应给生产客户端统一关闭校验。
只有 IPv6 用户失败时,从具备 IPv6 路由的故障网络检查 AAAA、实际远端地址和 curl -6 的 TLS/HTTP 结果,再与 IPv4 比较。确需暂撤 AAAA 时按正式记录变更处理,保留回退值并继续观察缓存;不要把任何部分用户故障都归结为 IPv6。
陌生地址也可能来自 split DNS、CDN 调度、代理或 hosts。先保存实际答复、解析器和连接信息,再核对权威与可信外部观测;单次查询差异不足以直接判定劫持。最终应能够解释:是谁回答了这个名字、答案保存在哪里、应用最终连接到哪个端点,以及故障停在解析、连接、TLS 还是业务响应阶段。
权威资料与规范地址
名称、注册与记录
- ICANN 域名持有人
- ICANN Lookup 注册数据查询
- ICANN EPP 域名状态
- RFC 9499:DNS 术语
- RFC 1034:名称、委派与解析
- RFC 1035:编码、报文与基础记录
- RFC 5890:国际化域名
- RFC 2181:RRset 与规范澄清
- RFC 3596:AAAA
- RFC 2782:SRV
- RFC 8659:CAA
- RFC 9460:SVCB 与 HTTPS
- RFC 4592:DNS 通配符
- IANA 特殊用途域名
- 阿里云添加解析记录
- Cloudflare CNAME flattening
缓存、传输与 DNSSEC
- RFC 2308:负缓存
- RFC 9520:解析失败缓存
- RFC 8767:过期数据服务
- RFC 7766:DNS over TCP
- RFC 7858:DNS over TLS
- RFC 8484:DNS over HTTPS
- RFC 4033:DNSSEC
- BIND 工具手册
- BIND 配置参考
- BIND DNSSEC 指南
系统解析与应用诊断
- Linux nsswitch.conf
- systemd-resolved
- resolvectl
- Docker 网络
- Kubernetes DNS 调试
- Chrome DevTools Network
- Java InetAddress
- curl 手册
