DNS¶
主要作者
本文编写中
DNS 是网络最重要的组件之一。如果 DNS 出现问题,那么可能会以非预期的方式把其他的东西一起弄坏,甚至在不少「高可用」的场景下,DNS 故障也可能会让整个集群出现问题。以至于有人写俳句如下:
It’s not DNS
There’s no way it’s DNS
It was DNS.
以下分别介绍在 Linux 客户端和服务端,DNS 相关的配置和使用方法。
客户端¶
说到 DNS,你可能首先想到的是 /etc/resolv.conf 文件,可以像这样配置使用的 DNS 服务器:
??? note "公共的 DNS 服务器" {#public-dns-servers}
虽然在绝大多数场景下,用户都不需要使用公共的 DNS 服务器,因为:
- 在家用场景,运营商通过 DHCP 提供的 DNS 一般来说能够让各网站(特别是使用 CDN 的站点)解析到离自己最近的节点,提升访问速度。
- 在服务器配置场景下,一般来说所属组织会有自己的 DNS 服务器,直接使用即可。
不过你可能会看到一些配置中使用公共的 DNS 服务器(包括一部分软件中会配置自动回退到这些服务器),出于隐私等需求考虑,一些用户也会主动选择使用这些服务器。以下介绍一些常见的 DNS 服务器以及其 IPv4 地址,以供参考(不代表推荐意见):
- [Google Public DNS](https://developers.google.cn/speed/public-dns/docs/using?hl=zh-cn):8.8.8.8、8.8.4.4
- [Cloudflare 1.1.1.1](https://www.cloudflare.com/learning/dns/what-is-1.1.1.1/):1.1.1.1、1.0.0.1
- [Quad9](https://quad9.net/):9.9.9.9、149.112.112.112
- [114DNS](https://www.114dns.com/):114.114.114.114、114.114.115.115
- [阿里云公共 DNS](https://www.aliyun.com/activity/dns/pubdns_launch):223.5.5.5、223.6.6.6
- [DNSPod Public DNS](https://www.dnspod.cn/products/publicdns):119.29.29.29
- [百度公共 DNS](https://dudns.baidu.com/index.html):180.76.76.76
- [CNNIC SDNS 递归云解析](https://www.cnnic.cn/n4/2022/0829/c32-10038.html):1.2.4.8、210.2.4.8
事实上,上面的知识对解决一部分 DNS 问题已经足够了。但是很多时候事情没有那么简单:
- nsswitch.conf 是什么东西?
- 为什么我的 resolv.conf 写的是 127.0.0.53?
- 为什么 Alpine 容器的 DNS 行为好像不太一样?
为了解决这些疑难杂症,我们就需要完整了解 Linux 下 DNS 解析的相关组件。
C 库提供的 DNS 解析接口¶
在最早期的时候,C 运行时库提供 gethostbyname() 和 gethostbyaddr() 函数来进行 DNS 解析:
// 获取 example.com 解析的 IP
struct hostent host_1 = gethostbyname("example.com");
// 地址在 he->h_addr_list 列表中
// 获取 1.1.1.1 对应的域名
const char *ip_str = "1.1.1.1";
struct in_addr ip;
inet_aton(ip_str, &ip);
struct hostent host_2 = gethostbyaddr(&ip, sizeof(ip), AF_INET);
// 域名在 he->h_name 中
!!! tip "从 IP 反查域名" {#reverse-dns-lookup}
很多人对 DNS 的理解仅限于「从域名查 IP」(A 记录和 AAAA 记录),但 DNS 也支持「从 IP 查域名」(PTR 记录)。例如对上面 1.1.1.1 的域名的查询,可以先构造出 `1.1.1.1.in-addr.arpa` 这个域名,然后查询这个域名的 PTR 记录。
PTR 记录由 IP 的所有者(ISP/云服务商等)负责维护。
但是 gethostbyname 和 gethostbyaddr 这两个函数已经过时了——gethostbyname 不支持 IPv6(AAAA),而且这两个函数都不是线程安全的。因此现代 POSIX 标准引入了 getaddrinfo()(有时候也简称为 gai)和 getnameinfo() 函数来替代它们:
// 获取 example.com 解析的 IP
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 + IPv6
hints.ai_socktype = SOCK_STREAM;
int ret_1 = getaddrinfo("example.com", NULL, &hints, &res);
// 地址在 res 链表中
// 获取 1.1.1.1 对应的域名
struct sockaddr_in sa;
char hostname[NI_MAXHOST];
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(53);
inet_pton(AF_INET, "1.1.1.1", &sa.sin_addr);
int ret_2 = getnameinfo((struct sockaddr *)&sa, sizeof(sa),
hostname, sizeof(hostname), NULL, 0, 0);
// 域名在 hostname 中
!!! note "res_query" {#res-query}
尽管 `getaddrinfo()` 可以解决不少问题,并且跨平台兼容性也不错,但是如果我们需要更底层的 DNS 查询功能(例如查询 A/AAAA 以外的记录)的时候,上面的 API 就不太够用了。而 libresolv(包含在 glibc 中)则提供了更底层的 [`res_nquery()`/`res_query()`][resolver.3] 等接口,便于需要直接构造 DNS 报文进行查询的程序使用。
musl 不支持 `res_nquery()`,但是支持 `res_query()`。
libresolv 在其他平台上可能有不同的行为,可参考:[getaddrinfo sucks. everything else is much worse](https://valentin.gosu.se/blog/2025/02/getaddrinfo-sucks-everything-else-is-much-worse)。
!!! tip "获取 C 库解析 API 的延迟" {#libc-dns-api-latency}
bcc 提供的基于 eBPF 的 [gethostlatency](https://github.com/iovisor/bcc/blob/master/tools/gethostlatency.py) 工具可以用来获取使用 C 运行时库的 DNS 解析延迟:
```console
$ sudo gethostlatency
TIME PID COMM LATms HOST
02:59:28 10680 ThreadPoolForeg 166.831 main.vscode-cdn.net
```
有关 eBPF 的介绍,可参考[问题调试部分](../debug.md#ebpf)。
不同的 C 运行时库对 DNS 会采取不同的解析方式。以下介绍 Linux 下最流行的两种 C 运行时库:glibc 和 musl。
glibc¶
glibc 会使用一套复杂的逻辑来决定如何解析用户提供的域名。其 getaddrinfo() 的内部实现调用了 gaih_inet() 函数执行实际的解析工作。简单来讲,这个函数会:
- 尝试从 nscd 缓存中获取结果(如果编译期启用了相关支持)
- 如果 nscd 缓存没有结果,那么就根据
/etc/nsswitch.conf文件中的配置,依次使用不同的 NSS(Name Service Switch)模块来解析域名
在 gaih_inet() 完成后,getaddrinfo() 会根据 RFC 3484(以及其继任者 RFC 6724)的规则,对返回的结果进行排序,然后返回给用户。
nscd¶
nscd(Name Service Cache Daemon)是 glibc 提供的用于缓存 DNS、用户信息等结果的服务。如果你在 Debian 下尝试对使用 glibc DNS 查询的程序 strace 的话,你会发现 glibc 会尝试连接 /var/run/nscd/socket:
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3) = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3) = 0
尽管 Debian 的 glibc 仍然还有 nscd 的支持,但是其他一些发行版,例如 Fedora、Arch Linux 等都移除了 nscd 的支持,因为:
- nscd bug 较多,不太稳定。
- nscd 除了缓存 DNS 以外的部分(缓存用户信息等)已经被 sssd(System Security Services Daemon)代替了。
- nscd 强绑定了 glibc,并且不适用于容器化场景(你需要把
/var/run/nscd/socket给 bind mount 进容器,有些太疯狂了)。 - 本地运行的 DNS 缓存服务(例如 systemd-resolved、dnsmasq 等)已经可以很好地完成 DNS 缓存的功能。
因此这里也不推荐使用 nscd。
如果需要清理 nscd 的缓存,可以使用 nscd -i 命令。
NSS¶
NSS 模块是 glibc 提供的一套插件机制,用于从不同的数据源获取名称解析结果。相关模块的配置在 /etc/nsswitch.conf 文件中。glibc 会根据这个配置加载 NSS 模块(/lib/libnss_xxx.so,xxx 为模块名,如 files),然后调用模块中的接口来获取名称解析结果。
以下是 Debian 13 容器的默认配置:
passwd: files
group: files
shadow: files
gshadow: files
hosts: files dns
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis
这里与 DNS 相关的配置是 hosts 一行,以上配置表示:
files模块会解析/etc/hosts文件的内容,查看是否能够解析。- 如果
files模块没有解析出结果,那么就使用dns模块进行 DNS 查询(使用/etc/resolv.conf作为配置)。
另一种非常常见的配置是安装了 systemd-resolved 的场景。那么 hosts 可能会变成这样:
其中 myhostname 负责解析本机的主机名,resolve 模块则会通过 systemd-resolved 的 Unix socket(/run/systemd/resolve/io.systemd.Resolve)来进行解析(详情可阅读我们对 Varlink 的介绍)。
[!UNAVAIL=return] 表示,除非(!)resolve 模块不可用(例如 systemd-resolved 没有运行),否则就直接返回,不再继续使用后面的 dns 模块。这样设置下,如果 systemd-resolved 出现故障,那么系统仍然可以回退到直接使用 DNS 服务器进行解析。而如果只是域名不存在,那么就不会继续使用 dns 模块,避免了不必要的 DNS 查询。
可以使用 getent 测试 NSS 的解析结果,例如 getent hosts example.com、getent passwd 等。同时可以使用 -s 参数来指定使用的 NSS 模块,用于调试,例如:
就(一般来说)会返回空,因为其只会用 files 模块来解析 example.com,如果 /etc/hosts 中没有相关的记录,那么就不会有结果。
!!! note "NSS 的返回状态" {#nss-return-status}
NSS 模块可能会返回以下几种状态:
- `SUCCESS`:解析成功。
- `NOTFOUND`:没有找到对应的记录。
- `UNAVAIL`:模块(永久)不可用。
- `TRYAGAIN`:模块(暂时)不可用,可以重试。
默认配置相当于 `[SUCCESS=return !SUCCESS=continue]`。除了 `return` 和 `continue` 之外,还有 `merge`:
```text
group: files [SUCCESS=merge] sss
```
这样的话,如果某用户在本地(`files`)属于组 A,在 sssd(`sss`)中属于组 B,那么最终该用户就会同时属于组 A 和组 B。
!!! note "为什么解析本机还需要 myhostname 模块?" {#nss-myhostname-local-resolution}
一个约定俗成的做法是,将主机名放在 `/etc/hostname` 文件,而在 `/etc/hosts` 中添加相关的映射:
```text
127.0.0.1 myhost
```
不过,如果 `/etc/hosts` 里面忘写了/忘改了对应的条目,那么就可能会出现非预期的行为。例如,如果忘记添加 `localhost`,那么有些程序就可能会因为无法解析 `localhost` 而出现问题。
systemd-hostnamed 服务则负责管理系统的主机名——静态的主机名(static hostname)仍然在 `/etc/hostname` 中,用户可读的主机名(pretty hostname,比如说 "Xiao Ming's Computer" 或者 "我的电脑" 这种有空格、特殊字符,甚至汉字的名字)等存储在 `/etc/machine-info` 中,同时其也会记录从网络(例如 DHCP)获取的主机名(transient hostname)。而 `myhostname` 模块就是 systemd-hostnamed 提供的 NSS 模块,确保系统主机名总是可以被正确解析,请看下面的例子:
```console
$ getent -s myhostname hosts localhost
::1 localhost
$ # hostnamed 能获取网络接口的地址
$ getent -s myhostname hosts myhost
fd36:cccc:bbbb:aaaa:aaaa:aaaa:aaaa:aaaa myhost
2001:da8:d800:aaaa:aaaa:aaaa:aaaa:aaaa myhost
fe80::aaaa:aaaa:aaaa:aaaa:aaaa myhost
fe80::bbbb:bbbb:bbbb:bbbb:bbbb myhost
$ getent -s myhostname hosts 127.0.0.1
127.0.0.1 localhost
$ # hostnamed 中,127.0.0.2 对应主机名,127.0.0.1 对应 localhost
$ getent -s myhostname hosts 127.0.0.2
127.0.0.2 myhost
```
!!! note "glibc、NSS 与静态链接" {#glibc-nss-static-linking}
如果有尝试对访问网络(使用了 NSS 的)C 程序进行静态链接(`-static`)的话,那么你可能会看到:
```
/usr/bin/ld: /tmp/cchbUcHT.o: in function `main':
example.c:(.text+0x2a): warning: Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
```
这是因为 NSS 是动态加载(`dlopen`)的,如果静态链接之后扔到别的机器上,那么对应的 NSS 模块可能就不存在或者不兼容,从而导致程序无法运行。
地址排序与 gai.conf¶
glibc 的 getaddrinfo() 默认根据 RFC 3484 的规则对返回的结果进行排序,不过用户也可以在 /etc/gai.conf 文件中自定义排序规则。
RFC 3484 的排序包含两者:源地址选择(source address selection)和目的地址选择(destination address selection)。这里只涉及目的地址选择。目的地址选择具体的规则可以阅读 RFC 的第 6 节。其中需要了解的是 Policy Table(第 2.1 节),它是一个最长匹配的前缀表,对每个在表中的前缀定义了优先级(Precedence)和标签(Label),这些值会影响排序结果。gai.conf 配置的其实就是这个表。
简而言之,RFC 3484 规定的地址选择顺序是:
- 避免不可用的地址、避免不匹配的 scope、避免 deprecated 的地址;
- 优先选择具有相同 Label 的源地址和目的地址;
- 优先选择 Precedence 较高的目的地址(数值越大则优先级越高);
- 其他规则,如原生 IPv4 地址优先于 6to4 / Teredo、最长前缀匹配原则等。
最常见需要修改 gai.conf 的情况是希望优先使用 IPv4 地址。在进行目的地址选择时,IPv4 地址会映射到 ::ffff:0:0/96 前缀(例如 1.1.1.1 会映射到 ::ffff:101:101),而默认情况它的优先级是 10,比其他的 IPv6 地址都要低。因此如果希望优先使用 IPv4 地址,可以添加如下配置:
!!! tip "如果不希望 glibc 发送 AAAA 请求……" {#disable-aaaa-queries}
glibc 的 `getaddrinfo()` 在参数为 `AF_UNSPEC` 时,总是会同时发送 A 和 AAAA 请求(即使设置了 `net.ipv6.conf.all.disable_ipv6`)。同样,上面的 `gai.conf` 配置也无法阻止 glibc 发送 AAAA 请求(只是在获取到两者之后优先选择 IPv4)。
在 glibc 2.36 之后,glibc 提供了 `no-aaaa` 选项,可以在 `resolv.conf` 中添加。而对更老版本的机器,则可能需要考虑使用 [nss-dns4only](https://github.com/ossobv/nss-dns4only) 这种第三方的 NSS 模块来实现。
resolv.conf¶
glibc 在实际发 DNS 请求前会读取 /etc/resolv.conf。其最多支持 MAXNS(默认为 3)个 nameserver 配置。如果配置了多个 nameserver,那么 glibc 会依次尝试这些服务器。
此外,一些可能有帮助的配置包括:
-
对查询非完整域名(例如主机名)的场景,glibc 会依次将
search列表中的域名附加到查询的域名后面进行查询。例如,假设配置了search example.com,那么查询myhost的时候,会优先搜索myhost.example.com。!!! tip "没有点(dot)的公开域名" {#single-label-public-domains}
其实反直觉的是,有非常少量(有解析)的域名是没有点的,比如说 `bd`(孟加拉国的顶级域): ```console $ dig bd A +short 203.112.194.232 ```这个行为也可以被
options ndots:n配置项控制,默认值是 1,表示只有查询的域名中没有点的时候,才会使用search列表进行搜索。 -
默认情况下,glibc 会对每个
nameserver等待 5 秒(options timeout:n),尝试 2 次(options attempts:n)。所以如果你发现有什么东西刚好会卡住 5 秒或者 5 秒的倍数,那么检查一下 DNS 可能会有帮助,特别是在写了多个nameserver,而第一个nameserver有问题的情况下。 -
如果你配置了 systemd-resolved,那么
resolv.conf有可能会是这样:选项中的
edns0为 RFC 2671 中定义的扩展 DNS 功能,允许在 DNS 包中添加额外信息,包括能够接收的 UDP 响应的最大大小——以避免回退到 TCP 查询的额外网络开销;而trust-ad则与 DNSSEC 相关,表示信任 DNS 响应中与 DNSSEC 校验有关的 AD 标志。
musl¶
musl 追求简洁、可移植(一大好处是:静态链接变得极其方便),其和 glibc 在 DNS 解析方面有非常大的区别:
- musl 不使用 nscd、NSS,也不会读取
/etc/gai.conf。其固定使用/etc/hosts和/etc/resolv.conf作为解析的配置来源。 - 对于
/etc/resolv.conf中有多个nameserver的情况,musl 会并发请求(最多 3 个nameserver),并取首个返回的结果。这会导致网络压力增大,因此建议在这种情况下配置好本地的 DNS 缓存服务以缓解网络压力,减小 DNS 解析出错的可能。 - 在 musl 1.2.4(2023/5/1)之前,musl 不支持 TCP DNS 查询——这对 DNS 响应会超过 512 字节的场景是致命的。
其他技术区别的整理可参考:Functional differences from glibc。
resolvconf¶
在某些网络配置下,/etc/resolv.conf 可能会需要被多个程序修改,例如在接入网络时,DHCP 客户端会修改 resolv.conf 添加从 DHCP 服务器获取的 DNS 服务器,之后如果打开了 VPN,VPN 客户端也可能会修改 resolv.conf 添加 VPN 提供的 DNS 服务器,可以发现在这个模型下,/etc/resolv.conf 很容易就会被留在一个不正确的状态,导致 DNS 解析失败。
为了解决这种多个程序需要修改 /etc/resolv.conf 的场景,resolvconf 程序提供了一种解决途径:需要调整 DNS 的程序不修改 /etc/resolv.conf,而是调用 resolvconf 程序注册自己的 DNS 服务器信息,由 resolvconf 负责生成最终的 /etc/resolv.conf。以下是一个示意:
# 程序 1 在 eth0 接口上注册 DNS 服务器
cat <<EOF | resolvconf -a eth0
nameserver 192.168.1.1
EOF
# 程序 2 在 vpn0 接口上注册 DNS 服务器
cat <<EOF | resolvconf -a vpn0
nameserver 10.1.1.1
EOF
# 程序 2 退出
resolvconf -d vpn0
不过在目前的 Linux 系统中,resolvconf 已经不多见了:在桌面系统下,NetworkManager 管理整个系统的网络配置,同时也只有它会修改 /etc/resolv.conf;并且现在发行版的趋势是使用 systemd-resolved 来全权管理 DNS(NetworkManager 也可以调用 systemd-resolved 来设置 DNS)。同时 systemd-resolved 也提供了兼容 resolvconf 的接口。
DNS 缓存服务¶
可以注意到,glibc 设置了非常复杂的 DNS 解析逻辑,但是问题也是很明显的:
nsswitch.conf和gai.conf配置文件对容器场景难以适用- nscd 缓存服务不稳定且也不适合容器化
- 如果程序不使用 glibc 的 API 做 DNS 解析,那么这些配置就完全无效了(最典型的例子是使用 Go 语言在关闭了 cgo 的情况下编译的程序)
因此目前来讲,更推荐的做法是:在本地运行一个 DNS 缓存服务器,并且修改 /etc/resolv.conf 等配置将所有的 DNS 请求都发给这个缓存服务器,以统一整个系统的 DNS 解析行为。
systemd-resolved¶
systemd-resolved 是 systemd 的本地 DNS 解析服务,用于提供缓存、DNSSEC、DNS-over-TLS、mDNS/LLMNR 等功能。我们主要关注其缓存功能。目前,Ubuntu 默认使用 systemd-resolved,但是 Debian 不是。resolved 对系统中的应用提供了多种接口,包括其 DBus/Varlink 接口、NSS 模块接口(nss-resolve)以及一个内置的 DNS 服务器(127.0.0.53)——大部分的应用使用的都是后两者。
使用 resolvectl status 可以查看全局以及每个网络接口的 DNS 配置,resolvectl statistics 可以查看查询统计信息,resolvectl query 则可以直接查询 DNS 记录,用于调试。如果需要清空缓存,可以使用 resolvectl flush-caches。
/etc/resolv.conf 的配置¶
resolved 提供了以下几种 resolv.conf 文件,分别对应不同的模式:
/run/systemd/resolve/stub-resolv.conf:由 resolved 动态生成,指向 127.0.0.53,包含search域名搜索域。/run/systemd/resolve/resolv.conf:由 resolved 动态生成,包含实际的上游 DNS 服务器地址。/usr/lib/systemd/resolv.conf:安装systemd-resolved包后就有的文件,指向 127.0.0.53,不包含搜索域。
将 /etc/resolv.conf 软链接到上述文件即可指定对应的模式,一般来说使用的都是 stub 模式(第一种)。如果 /etc/resolv.conf 不是软链接,而且包含其他的 DNS 服务器,那么 resolved 会读取并设置为自己的上游。
缓存行为¶
默认情况下,resolved 会启用 DNS 缓存,根据 DNS 响应的 TTL 信息缓存数据,避免额外的网络开销。
!!! tip "TTL" {#ttl}
每条 DNS 记录都有 TTL 时间(秒为单位),标志下游的服务器可以缓存该记录的时间。例如:
```console
$ dig www.example.com
(省略)
;; ANSWER SECTION:
www.example.com. 237 IN A 104.18.26.120
www.example.com. 237 IN A 104.18.27.120
(省略)
```
这里的 237 就是获取到的 TTL 秒数。显然,如果 TTL 设置较长,那么每次更新之后,其他机器就可能需要更长的时间获取到修改后的记录;而如果 TTL 太短,那么就可能会给 DNS 服务器(特别是权威 DNS 服务器)带来较大的压力。
!!! tip "Negative cache 与 SOA 记录" {#negative-cache-soa}
为了减小网络开销,不仅成功的查询需要缓存,失败的也需要。这被称为 Negative cache。一些常见的情况:
- 某个域名只有 A 记录,但是没有 AAAA 记录(或者反过来)
- 在 `search` 列表中有多个域名,或者 `ndots` 的值比较大,那么就可能会有多个查询尝试,其中一些查询会找不到记录
但是没有记录的话,客户端怎么知道要缓存多长时间呢?这个信息就由 SOA 记录来提供。每个 DNS zone 只有一个 SOA 记录,包含一些基础的参数,其中最后一个参数就是 Negative cache TTL。在查询到由某个 DNS zone 负责,但是不存在记录的域名时,DNS 服务器一般会返回 SOA 记录,类似如下:
```console
$ dig AAAA ipv4.mirrors.ustc.edu.cn # IPv4-only 的域名,没有 AAAA
(省略)
;; AUTHORITY SECTION:
mirrors.ustc.edu.cn. 60 IN SOA ns-a.ustclug.org. lug.ustc.edu.cn. 2026022401 3600 600 604800 60
(省略)
```
这里的 negative cache TTL 就是 60 秒(一般来说,会取 SOA 本体的 TTL 和宣告的 negative TTL 的最小值)。
而如果 DNS 服务器本身查询失败(返回 SERVFAIL),那么也就没有 SOA 记录。[resolved 对此默认缓存 10 秒](https://github.com/systemd/systemd/pull/17814)。
!!! warning "Ubuntu 默认不启用 negative cache" {#ubuntu-negative-cache-disabled}
与 systemd 上游设置不同,Ubuntu 的 systemd-resolved 包的 `Cache` 参数默认值为 `no-negative`,而不是 `yes`。
(TODO)
dnsmasq¶
!!! warning "dnsmasq 与 Docker 默认 bridge 网络的行为" {#dnsmasq-docker-bridge-behavior}
Docker 的默认 bridge 网络不会使用其内置的 DNS 服务器,而是直接使用主机的 `/etc/resolv.conf` 配置放进容器中。假如 Docker 发现 `nameserver` 全都是本地地址,那么就会 fallback 到 8.8.8.8/8.8.4.4 上,绕过 dnsmasq 的缓存功能(Docker 对 systemd-resolved 做了特殊处理,可以从 `/run/systemd/resolve/resolv.conf` 获取到实际上游的 DNS 地址并设置,但是缓存也就失效了)。
因此,如果希望 Docker 容器使用到缓存功能,那么请考虑以下方法之一:
- 不使用默认 bridge 网络,使用 `docker network create` 创建自定义的 bridge 网络。
- 让 dnsmasq 同时在 `docker0` 上监听,并在 `/etc/resolv.conf` 中配置 `nameserver` 为 `docker0` 上的地址。
服务端¶
递归服务器¶
权威服务器¶
主流的权威 DNS 服务端软件包括 BIND、Knot DNS、PowerDNS 和 Unbound 等。
USTCLUG 的域名(即 ustclug.org,以及一些其他域名)使用 BIND 9 作为权威 DNS 服务器。