systemd-resolved 把 NXDOMAIN 当终局答案:DNS 兜底列表为何永远不生效
在一台 Ubuntu 22.04 服务器上配 Docker registry mirror,docker pull 死活报 "no such host"。nslookup 指向 127.0.0.53 返回 NXDOMAIN,但拿同一个域名直接 dig @223.5.5.5 秒出 A 记录。netplan 里明明已经填了公共 DNS 作为兜底——却像从来没写一样。
折腾了很久才发现,根子不在域名、不在镜像源,而在 systemd-resolved 一个让人误解的核心行为。
现象
# 失败
nslookup docker.m.daocloud.io
# ** server can't find docker.m.daocloud.io: NXDOMAIN
# server: 127.0.0.53
# 成功(直接绕过 systemd-resolved)
dig +short @223.5.5.5 docker.m.daocloud.io
# 返回真实 IP
# netplan 配置
nameservers:
addresses: [192.168.1.1, 223.5.5.5]resolvectl status enp2s0 显示两个 DNS 都在列表里,看起来没问题。但只要把 192.168.1.1 删掉,resolution 立即恢复正常。
根因
很多人对"多个 nameserver"的直觉来自传统 glibc resolver:上一个 timeout 才试下一个,是严格的顺序兜底链。
systemd-resolved 完全不同:
NXDOMAIN is treated as an authoritative negative response.
它对多个 upstream DNS 的策略是并发竞速——同时发出查询,谁先回来就用谁的结果。路由器 DNS 在内网,延迟最低,通常最先响应。一旦它返回 NXDOMAIN(被 DNS 污染、运营商封锁或路由器策略),systemd-resolved 把这个结果当权威终局答案,立即结束查询,不会等公共 DNS 的响应。
加再多公共 DNS 也无济于事——它们已经没有参赛资格了。
传统 resolver 的"超时失败才换下一个"逻辑在 systemd-resolved 里完全失效。NXDOMAIN 被视为权威负回答,一票否决,竞速就此终止。把公共 DNS 写在路由器 DNS 后面,等于白写。
正解
把可靠的公共 DNS 放列表第一位,让它赢得竞速;路由器 DNS 退为兜底(仅用于内网 hostname 解析)或直接删掉。
/etc/netplan/01-static.yaml:
network:
version: 2
ethernets:
enp2s0:
addresses: [192.168.100.x/24]
routes:
- to: default
via: 192.168.100.1
nameservers:
addresses:
- 223.5.5.5 # 阿里 DNS — 优先,延迟低且不污染
- 119.29.29.29 # DNSPod — 备用
- 192.168.1.1 # 路由器 — 仅内网 host,兜底sudo netplan apply
resolvectl status enp2s0 | grep "DNS Servers"
# 确认 223.5.5.5 排第一
nslookup docker.m.daocloud.io
# 应该返回真实 IPsystemd-resolved 是竞速模型,不是兜底链。第一个返回权威响应的 DNS 说了算。把延迟低且干净的公共 DNS 排第一,路由器 DNS 的 NXDOMAIN 就没机会先到。
几个容易踩的附坑
Ubuntu 22.04 的 /etc/resolv.conf 是一个指向 127.0.0.53 的 stub,实际上游由 systemd-resolved 管理。直接编辑 resolv.conf 会被 NetworkManager 或 cloud-init 覆盖,也绕不过 systemd-resolved 的竞速逻辑。DNS 配置必须走 netplan。
如果根本不需要内网 hostname 解析,最干净的方案是直接只填公共 DNS:
nameservers:
addresses: [223.5.5.5, 119.29.29.29]零污染机会,省去排查路由器策略的麻烦。
一句话外卖
systemd-resolved 多 DNS 是竞速而非兜底;NXDOMAIN 是终止信号,把干净的 DNS 放第一才有意义。