在 Linux DNS 查询剖析（第一部分），Linux DNS 查询剖析（第二部分） 和 Linux DNS 查询剖析（第三部分） 中，我们已经介绍了以下内容：nsswitch/etc/hosts/etc/resolv.confping 与 host 查询方式的对比systemd 和对应的 networking 服务ifup 和 ifdowndhclientresolvconfNetworkManagerdnsmasq在第四部分中，我将介绍容器如何完成 DNS 查询。你想的没错，也不是那么简单。1) Docker 和 DNS在 Linux DNS 查询剖析（第三部分） 中，我们介绍了 dnsmasq，其工作方式如下：将 DNS 查询指向到 localhost 地址 127.0.0.1，同时启动一个进程监听 53 端口并处理查询请求。在按上述方式配置 DNS 的主机上，如果运行了一个 Docker 容器，容器内的 /etc/resolv.conf 文件会是怎样的呢？我们来动手试验一下吧。按照默认 Docker 创建流程，可以看到如下的默认输出：$ docker run ubuntu cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN # 127.0.0.53 is the systemd-resolved stub resolver. # run "systemd-resolve --status" to see details about the actual nameservers. search home nameserver 8.8.8.8 nameserver 8.8.4.4奇怪！地址 8.8.8.8 和 8.8.4.4 从何而来呢？当我思考容器内的 /etc/resolv.conf 配置时，我的第一反应是继承主机的 /etc/resolv.conf。但只要稍微进一步分析，就会发现这样并不总是有效的。如果在主机上配置了 dnsmasq，那么 /etc/resolv.conf 文件总会指向 127.0.0.1 这个 回环地址 loopback address 。如果这个地址被容器继承，容器会在其本身的 网络上下文 networking context 中使用；由于容器内并没有运行（在 127.0.0.1 地址的）DNS 服务器，因此 DNS 查询都会失败。“有了！”你可能有了新主意：将 主机的 的 IP 地址用作 DNS 服务器地址，其中这个 IP 地址可以从容器的 默认路由 default route 中获取：root@79a95170e679:/# ip route default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2使用主机 IP 地址真的可行吗？从默认路由中，我们可以找到主机的 IP 地址 172.17.0.1，进而可以通过手动指定 DNS 服务器的方式进行测试（你也可以更新 /etc/resolv.conf 文件并使用 ping 进行测试；但我觉得这里很适合介绍新的 dig 工具及其 @ 参数，后者用于指定需要查询的 DNS 服务器地址）：root@79a95170e679:/# dig @172.17.0.1 google.com | grep -A1 ANSWER.SECTION ;; ANSWER SECTION: google.com. 112 IN A 172.217.23.14但是还有一个问题，这种方式仅适用于主机配置了 dnsmasq 的情况；如果主机没有配置 dnsmasq，主机上并不存在用于查询的 DNS 服务器。在这个问题上，Docker 的解决方案是忽略所有可能的复杂情况，即无论主机中使用什么 DNS 服务器，容器内都使用 Google 的 DNS 服务器 8.8.8.8 和 8.8.4.4 完成 DNS 查询。我的经历：在 2013 年，我遇到了使用 Docker 以来的第一个问题，与 Docker 的这种 DNS 解决方案密切相关。我们公司的网络屏蔽了 8.8.8.8 和 8.8.4.4，导致容器无法解析域名。这就是 Docker 容器的情况，但对于包括 Kubernetes 在内的容器 编排引擎 orchestrators ，情况又有些不同。2) Kubernetes 和 DNS在 Kubernetes 中，最小部署单元是 pod；它是一组相互协作的容器，共享 IP 地址（和其它资源）。Kubernetes 面临的一个额外的挑战是，将 Kubernetes 服务请求（例如，myservice.kubernetes.io）通过对应的 解析器 resolver ，转发到具体服务地址对应的 内网地址 private network 。这里提到的服务地址被称为归属于“ 集群域 cluster domain ”。集群域可由管理员配置，根据配置可以是 cluster.local 或 myorg.badger 等。在 Kubernetes 中，你可以为 pod 指定如下四种 pod 内 DNS 查询的方式。Default在这种（名称容易让人误解）的方式中，pod 与其所在的主机采用相同的 DNS 查询路径，与前面介绍的主机 DNS 查询一致。我们说这种方式的名称容易让人误解，因为该方式并不是默认选项！ClusterFirst 才是默认选项。如果你希望覆盖 /etc/resolv.conf 中的条目，你可以添加到 kubelet 的配置中。ClusterFirst在 ClusterFirst 方式中，遇到 DNS 查询请求会做有选择的转发。根据配置的不同，有以下两种方式：第一种方式配置相对古老但更简明，即采用一个规则：如果请求的域名不是集群域的子域，那么将其转发到 pod 所在的主机。第二种方式相对新一些，你可以在内部 DNS 中配置选择性转发。下面给出示例配置并从 Kubernetes 文档中选取一张图说明流程：apiVersion: v1 kind: ConfigMap metadata: name: kube-dns namespace: kube-system data: stubDomains: | {"acme.local": ["1.2.3.4"]} upstreamNameservers: | ["8.8.8.8", "8.8.4.4"]在 stubDomains 条目中，可以为特定域名指定特定的 DNS 服务器；而 upstreamNameservers 条目则给出，待查询域名不是集群域子域情况下用到的 DNS 服务器。这是通过在一个 pod 中运行我们熟知的 dnsmasq 实现的。剩下两种选项都比较小众：ClusterFirstWithHostNet适用于 pod 使用主机网络的情况，例如绕开 Docker 网络配置，直接使用与 pod 对应主机相同的网络。NoneNone 意味着不改变 DNS，但强制要求你在 pod 规范文件 specification 的 dnsConfig 条目中指定 DNS 配置。CoreDNS 即将到来除了上面提到的那些，一旦 CoreDNS 取代 Kubernetes 中的 kube-dns，情况还会发生变化。CoreDNS 相比 kube-dns 具有可配置性更高、效率更高等优势。如果想了解更多，参考这里。如果你对 OpenShift 的网络感兴趣，我曾写过一篇文章可供你参考。但文章中 OpenShift 的版本是 3.6，可能有些过时。第四部分总结第四部分到此结束，其中我们介绍了：Docker DNS 查询Kubernetes DNS 查询选择性转发（子域不转发）kube-dnsvia: https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/作者：zwischenzugs 译者：pinewall 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在 Linux DNS 查询剖析（第一部分）中，我们介绍了：nsswitch/etc/hosts/etc/resolv.confping 与 host 查询方式的对比而在 Linux DNS 查询剖析（第二部分），我们介绍了：systemd 和对应的 networking 服务ifup 和 ifdowndhclientresolvconf剖析进展如下：（大致）准确的关系图很可惜，故事还没有结束，还有不少东西也会影响 DNS 查询。在第三部分中，我将介绍 NetworkManager 和 dnsmasq，简要说明它们如何影响 DNS 查询。1) NetworkManager在第二部分已经提到，我们现在介绍的内容已经偏离 POSIX 标准，涉及的 DNS 解析管理部分在各个发行版上形式并不统一。在我使用的发行版 （Ubuntu）中，有一个名为 NetworkManager 的 可用 available 服务，它通常作为一些其它软件包的依赖被安装。它实际上是 RedHat 在 2004 年开发的一个服务，用于帮助你管理网络接口。它与 DNS 查询有什么关系呢？让我们安装这个服务并找出答案：$ apt-get install -y network-manager对于 Ubuntu，在软件包安装后，你可以发现一个新的配置文件：$ cat /etc/NetworkManager/NetworkManager.conf [main] plugins=ifupdown,keyfile,ofono dns=dnsmasq [ifupdown] managed=false看到 dns=dnsmasq 了吧？这意味着 NetworkManager 将使用 dnsmasq 管理主机上的 DNS。2) dnsmasqdnsmasq 程序是我们很熟悉的程序：只是 /etc/resolv.conf 之上的又一个间接层。理论上，dnsmasq 有多种用途，但主要被用作 DNS 缓存服务器，缓存到其它 DNS 服务器的请求。dnsmasq 在本地所有网络接口上监听 53 端口（标准的 DNS 端口）。那么 dnsmasq 运行在哪里呢？NetworkManager 的运行情况如下：$ ps -ef | grep NetworkManager root 15048 1 0 16:39 ? 00:00:00 /usr/sbin/NetworkManager --no-daemon但并没有找到 dnsmasq 相关的进程：$ ps -ef | grep dnsmasq $令人迷惑的是，虽然 dnsmasq 被配置用于管理 DNS，但其实并没有安装在系统上！因而，你需要自己安装它。安装之前，让我们查看一下 /etc/resolv.conf 文件的内容：$ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 10.0.2.2 search home可见，并没有被 NetworkManager 修改。如果安装 dnsmasq：$ apt-get install -y dnsmasq然后启动运行 dnsmasq：$ ps -ef | grep dnsmasq dnsmasq 15286 1 0 16:54 ? 00:00:00 /usr/sbin/dnsmasq -x /var/run/dnsmasq/dnsmasq.pid -u dnsmasq -r /var/run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5然后，/etc/resolv.conf 文件内容又改变了！root@linuxdns1:~# cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.0.1 search home运行 netstat 命令，可以看出 dnsmasq 在所有网络接口上监听 53 端口：$ netstat -nlp4 Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 15286/dnsmasq tcp 0 0 10.0.2.15:53 0.0.0.0:* LISTEN 15286/dnsmasq tcp 0 0 172.28.128.11:53 0.0.0.0:* LISTEN 15286/dnsmasq tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1237/sshd udp 0 0 127.0.0.1:53 0.0.0.0:* 15286/dnsmasq udp 0 0 10.0.2.15:53 0.0.0.0:* 15286/dnsmasq udp 0 0 172.28.128.11:53 0.0.0.0:* 15286/dnsmasq udp 0 0 0.0.0.0:68 0.0.0.0:* 10758/dhclient udp 0 0 0.0.0.0:68 0.0.0.0:* 10530/dhclient udp 0 0 0.0.0.0:68 0.0.0.0:* 10185/dhclient3) 分析 dnsmasq在目前的情况下，所有的 DNS 查询都会使用 127.0.0.1:53 这个 DNS 服务器，下一步会发生什么呢？我再次查看 /var/run 目录，可以发现一个线索：resolvconf 目录下 resolv.conf 文件中的配置也相应变更，变更为 dnsmasq 对应的 DNS 服务器：$ cat /var/run/resolvconf/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.0.1 search home同时，出现了一个新的 dnsmasq 目录，也包含一个 resolv.conf 文件：$ cat /run/dnsmasq/resolv.conf nameserver 10.0.2.2（LCTT 译注：这里依次提到了 /var/run 和 /run，使用 Ubuntu 16.04 LTS 验证发现，/var/run 其实是指向 /run/ 的软链接）该文件包含我们从 DHCP 获取的 nameserver。虽然可以推导出这个结论，但如何查看具体的调用逻辑呢？4) 调试 dnsmasq我经常思考 dnsmasq （在整个过程中）的功能定位。幸运的是，如果你将 /etc/dnsmasq.conf 中的一行做如下调整，你可以获取大量 dnsmasq 状态的信息：#log-queries修改为：log-queries然后重启 dnsmasq。接下来，只要运行一个简单的命令：$ ping -c1 bbc.co.uk你就可以在 /var/log/syslog 中找到类似的内容（其中 [...] 表示行首内容与上一行相同）：Jul 3 19:56:07 ubuntu-xenial dnsmasq[15372]: query[A] bbc.co.uk from 127.0.0.1 [...] forwarded bbc.co.uk to 10.0.2.2 [...] reply bbc.co.uk is 151.101.192.81 [...] reply bbc.co.uk is 151.101.0.81 [...] reply bbc.co.uk is 151.101.64.81 [...] reply bbc.co.uk is 151.101.128.81 [...] query[PTR] 81.192.101.151.in-addr.arpa from 127.0.0.1 [...] forwarded 81.192.101.151.in-addr.arpa to 10.0.2.2 [...] reply 151.101.192.81 is NXDOMAIN可以清晰看出 dnsmasq 收到的查询、查询被转发到了哪里以及收到的回复。如果查询被缓存命中（或者说，本地的查询结果还在 存活时间 time-to-live TTL 内，并未过期），日志显示如下：[...] query[A] bbc.co.uk from 127.0.0.1 [...] cached bbc.co.uk is 151.101.64.81 [...] cached bbc.co.uk is 151.101.128.81 [...] cached bbc.co.uk is 151.101.192.81 [...] cached bbc.co.uk is 151.101.0.81 [...] query[PTR] 81.64.101.151.in-addr.arpa from 127.0.0.1如果你想了解缓存中有哪些记录，可以向 dnsmasq 进程 id 发送 USR1 信号，这样 dnsmasq 会将缓存记录导出并写入到相同的日志文件中：$ kill -SIGUSR1 $(cat /run/dnsmasq/dnsmasq.pid)（LCTT 译注：原文中命令执行报错，已变更成最接近且符合作者意图的命令）导出记录对应如下输出：Jul 3 15:08:08 ubuntu-xenial dnsmasq[15697]: time 1530630488 [...] cache size 150, 0/5 cache insertions re-used unexpired cache entries. [...] queries forwarded 2, queries answered locally 0 [...] queries for authoritative zones 0 [...] server 10.0.2.2#53: queries sent 2, retried or failed 0 [...] Host Address Flags Expires [...] linuxdns1 172.28.128.8 4FRI H [...] ip6-localhost ::1 6FRI H [...] ip6-allhosts ff02::3 6FRI H [...] ip6-localnet fe00:: 6FRI H [...] ip6-mcastprefix ff00:: 6FRI H [...] ip6-loopback : 6F I H [...] ip6-allnodes ff02: 6FRI H [...] bbc.co.uk 151.101.64.81 4F Tue Jul 3 15:11:41 2018 [...] bbc.co.uk 151.101.192.81 4F Tue Jul 3 15:11:41 2018 [...] bbc.co.uk 151.101.0.81 4F Tue Jul 3 15:11:41 2018 [...] bbc.co.uk 151.101.128.81 4F Tue Jul 3 15:11:41 2018 [...] 151.101.64.81 4 R NX Tue Jul 3 15:34:17 2018 [...] localhost 127.0.0.1 4FRI H [...] <Root> 19036 8 2 SF I [...] ip6-allrouters ff02::2 6FRI H在上面的输出中，我猜测（并不确认，? 代表我比较无根据的猜测）如下：4 代表 IPv46 代表 IPv6H 代表从 /etc/hosts 中读取 IP 地址I ？ “永生”的 DNS 记录 ？ (例如，没有设置存活时间数值 ？)F ？R ？S ？N ？X（LCTT 译注：查看 dnsmasq 的源代码 cache.c 可知，4 代表 IPV4，6 代表 IPV6，C 代表 CNAME，S 代表 DNSSEC，F 代表 FORWARD，R 代表 REVERSE，I 代表 IMMORTAL，N 代表 NEG，X 代表 NXDOMAIN，H 代表 HOSTS。更具体的含义需要查看代码或相关文档）dnsmasq 的替代品NetworkManager 配置中的 dns 字段并不是只能使用 dnsmasq，可选项包括 none，default，unbound 和 dnssec-triggered 等。使用 none 时，NetworkManager 不会改动 /etc/resolv.conf；使用 default 时，NetworkManager 会根据当前的 活跃连接 active connections 更新 resolv.conf；使用 unbound 时，NetworkManager 会与 unbound 服务通信；dnssec-triggered 与 DNS 安全相关，不在本文讨论范围。第三部分总结第三部分到此结束，其中我们介绍了 NetworkManager 服务及其 dns=dnsmasq 的配置。下面简要罗列一下我们已经介绍过的全部内容：nsswitch/etc/hosts/etc/resolv.conf/run/resolvconf/resolv.confsystemd 及对应的 networking 服务ifup 和 ifdowndhclientresolvconfNetworkManagerdnsmasqvia: https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/作者：ZWISCHENZUGS 译者：pinewall 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在 Linux DNS 查询剖析 - 第一部分 中，我介绍了：nsswitch/etc/hosts/etc/resolv.confping 与 host 查询方式对比并且发现大多数程序选择要查询的 DNS 服务器时会参考 /etc/resolv.conf 配置文件。这种方式在 Linux 上比较普遍 1 。虽然我使用了特定的发行版 Ubuntu，但背后的原理与 Debian 甚至是那些基于 CentOS 的发行版有相通的地方；当然，与更低或更高的 Ubuntu 版本相比，差异还是存在的。也就是说，接下来，你主机上的行为很可能与我描述的不一致。在第二部分中，我将介绍 resolv.conf 的更新机制、systemctl restart networking 命令的运行机制 ，以及 dhclient 是如何参与其中。1) 手动更新 /etc/resolv.conf我们知道 /etc/resolv.conf （有极大的可能性）被用到，故你自然可以通过该文件增加一个 nameserver，那么主机也将会（与已有的 nameserver 一起）使用新加入的 nameserver 吧？你可以尝试如下：$ echo nameserver 10.10.10.10 >> /etc/resolv.conf看上去新的 nameserver 已经加入：# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 10.0.2.3 search home nameserver 10.10.10.10但主机网络服务重启后问题出现了：$ systemctl restart networking $ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 10.0.2.3 search home我们的 10.10.10.10 的 nameserver 不见了！在上一篇文章中我们忽略了这一点，本文进行补充说明。2) resolvconf你在 /etc/resolv.conf 文件中看到 generated by resolvconf 词组了吧？这就是我们的线索。如果深入研究 systemctl restart networking 命令，你会发现它做了很多事情，结束时调用了 /etc/network/if-up.d/000resolvconf 脚本。在该脚本中，可以发现一次对 resolvconf 命令的调用：/sbin/resolvconf -a "${IFACE}.${ADDRFAM}"稍微研究一下 man 手册，发现-a 参数允许我们：Add or overwrite the record IFACE.PROG then run the update scripts if updating is enabled.（增加或覆盖 IFACE.PROG 记录，如果开启更新选项，则运行更新脚本）故而也许我们可以直接调用该命令增加 namserver：echo 'nameserver 10.10.10.10' | /sbin/resolvconf -a enp0s8.inet测试表明确实可以！$ cat /etc/resolv.conf | grep nameserver nameserver 10.0.2.3 nameserver 10.10.10.10是否已经找到答案，这就是 /etc/resolv.conf 更新的逻辑？调用 resolvconf 将 nameserver 添加到某个地方的数据库，然后（“如果配置了更新”，先不管具体什么含义）更新 resolv.conf 文件。并非如此。$ systemctl restart networking root@linuxdns1:/etc# cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 10.0.2.3 search home呃！（网络服务重启后）新增的 nameserver 再次消失了。可见，systemctl restart networking 不仅仅运行了 resolvconf，还在其它地方获取 nameserver 信息。具体是哪里呢？3) ifup/ifdown继续深入研究 systemctl restart networking，发现它完成了一系列工作：cat /lib/systemd/system/networking.service [...] [Service] Type=oneshot EnvironmentFile=-/etc/default/networking ExecStartPre=-/bin/sh -c '[ "$CONFIGURE_INTERFACES" != "no" ] && [ -n "$(ifquery --read-environment --list --exclude=lo)" ] && udevadm settle' ExecStart=/sbin/ifup -a --read-environment ExecStop=/sbin/ifdown -a --read-environment --exclude=lo [...]首先，网络服务的重启实质是运行一个 单触发 oneshot 的脚本，脚本包含如下命令：/sbin/ifdown -a --read-environment --exclude=lo /bin/sh -c '[ "$CONFIGURE_INTERFACES" != "no" ] && [ -n "$(ifquery --read-environment --list --exclude=lo)" ] && udevadm settle' /sbin/ifup -a --read-environment第一行使用 ifdown 关闭全部的网络接口，但 本地回环 local, lo 接口除外。 2 （LCTT 译注：其实这是因为很快就又启动了接口，间隔的时间没有超过 TCP 连接的超时时间，有人在评论中也做了类似回复）第二行用于确认系统已经完成关闭网络接口相关的全部工作，以便下一步使用 ifup 启动接口。这也让我们了解到，网络服务实质运行的就是 ifdown 和 ifup。文档中没有找到 --read-environment 参数的说明，该参数为 systemctl 正常工作所需。很多人以文档不完善为由不喜欢 systemctl。很好。那么 ifup （和其成对出现的 ifdown） 到底做了哪些工作呢？长话短说，它运行了 /etc/network/if-pre-up.d/ 和 /etc/network/if-up.d/ 目录下的全部脚本；期间，这些脚本也可能会调用另外的脚本，依此类推。其中一件工作就是运行了 dhclient，但我还不完全确定具体的机理，也许 udev 参与其中。4) dhclientdhclient 是一个程序，用于与 DHCP 服务器协商对应网络接口应该使用的 IP 地址的详细信息。同时，它也可以获取可用的 DNS 服务器并将其替换到 /etc/resolv.conf 中。让我们开始跟踪并模拟它的行为，但仅在我实验虚拟机的 enp0s3 接口上。事先已经删除 /etc/resolv.conf 文件中的 nameserver 配置：$ sed -i '/nameserver.*/d' /run/resolvconf/resolv.conf $ cat /etc/resolv.conf | grep nameserver $ dhclient -r enp0s3 && dhclient -v enp0s3 Killed old client process Internet Systems Consortium DHCP Client 4.3.3 Copyright 2004-2015 Internet Systems Consortium. All rights reserved. For info, please visit https://www.isc.org/software/dhcp/ Listening on LPF/enp0s8/08:00:27:1c:85:19 Sending on LPF/enp0s8/08:00:27:1c:85:19 Sending on Socket/fallback DHCPDISCOVER on enp0s8 to 255.255.255.255 port 67 interval 3 (xid=0xf2f2513e) DHCPREQUEST of 172.28.128.3 on enp0s8 to 255.255.255.255 port 67 (xid=0x3e51f2f2) DHCPOFFER of 172.28.128.3 from 172.28.128.2 DHCPACK of 172.28.128.3 from 172.28.128.2 bound to 172.28.128.3 -- renewal in 519 seconds. $ cat /etc/resolv.conf | grep nameserver nameserver 10.0.2.3可见这就是 nameserver 的来源。但稍等一下，命令中的 /run/resolvconf/resolv.conf 是哪个文件，不应该是 /etc/resolv.conf 吗？事实上，/etc/resolv.conf 并不一定只是一个普通文本文件。在我的虚拟机上，它是一个软链接，指向位于 /run/resolvconf 目录下的“真实文件”。这也暗示了我们，该文件是在系统启动时生成的；同时，这也是该文件注释告诉我们不要直接修改该文件的原因。（LCTT 译注：在 CentOS 7 中，没有 resolvconf 命令，/etc/resolv.conf 也不是软链接）假如上面命令中 sed 命令直接处理 /etc/resolv.conf 文件，效果是不同的，会有警告消息告知待操作的文件不能是软链接（sed -i 无法很好的处理软链接，它只会创建一个新文件）。（LCTT 译注：CentOS 7 测试时，sed -i 命令操作软链接并没有警告，但确实创建了新文件取代软链接）如果你继续深入查看配置文件 /etc/dhcp/dhclient.conf 的 supersede 部分，你会发现 dhclient 可以覆盖 DHCP 提供的 DNS 服务器。（大致）准确的关系图第二部分的结束语第二部分到此结束。信不信由你，这是一个某种程度上简化的流程版本，但我尽量保留重要和值得了解的部分，让你不会感到无趣。大部分内容都是围绕实际脚本的运行展开的。但我们的工作还没有结束，在第三部分，我们会介绍这些之上的更多层次。让我们简要列出我们已经介绍过的内容：nsswitch/etc/hosts/etc/resolv.conf/run/resolvconf/resolv.confsystemd 和网络服务ifup 和 ifdowndhclientresolvconf事实上，这是相对于 POSIX 标准的，故不限于 Linux （我从上一篇文章的一条极好的回复中了解到这一点） ↩我不明白为何这没有导致我例子中的 vagrant 会话中断 （有谁明白吗？）。 ↩via: https://zwischenzugs.com/2018/06/18/anatomy-of-a-linux-dns-lookup-part-ii/作者：ZWISCHENZUGS 译者：pinewall 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
我经常与虚拟机集群打交道（文1、文2、文3、文4、文5、文6），因此最终花费了大量时间试图掌握 DNS 查询的工作原理。遇到问题时，我只是不求甚解的使用 StackOverflow 上的“解决方案”，而不知道它们为什么有时工作，有时不工作。最终我对此感到了厌倦，决定一并找出所有问题的原因。我没有在网上找到完整的指南，我问过一些同事，他们不知所以然（或许是问题太具体了）。既然如此，我开始自己写这样的手册。结果发现，“Linux 执行一次 DNS 查询”这句话的背后有相当多的工作。“究竟有多难呢？”本系列文章试图将 Linux 主机上程序获取（域名对应的） IP 地址的过程及期间涉及的组件进行分块剖析。如果不理解这些块的协同工作方式，调试解决 dnsmasq、vagrant landrush 和 resolvconf 等相关的问题会让人感到眼花缭乱。同时这也是一份有价值的说明，指出原本很简单的东西是如何随着时间的推移变得相当复杂。在弄清楚 DNS 查询的原理的过程中，我了解了大量不同的技术及其发展历程。我甚至编写了一些自动化脚本，可以让我在虚拟机中进行实验。欢迎读者参与贡献或勘误。请注意，本系列主题并不是“DNS 工作原理”，而是与查询 Linux 主机配置的真实 DNS 服务器（这里假设查询了一台 DNS 服务器，但后面你会看到有时并不需要）相关的内容，以及如何确定使用哪个查询结果，或者如何使用其它方式确定 IP 地址。1) 其实并没有名为“DNS 查询”的系统调用工作方式并非如此首先要了解的一点是，Linux 上并没有一个单独的方法可以完成 DNS 查询工作；没有一个有这样的明确接口的核心 系统调用 system call 。不过，有一个标准 C 库函数调用 getaddrinfo，不少程序使用了该调用；但不是所有程序或应用都使用该调用！让我们看一下两个简单的标准程序：ping 和 host：root@linuxdns1:~# ping -c1 bbc.co.uk | head -1 PING bbc.co.uk (151.101.192.81) 56(84) bytes of data.root@linuxdns1:~# host bbc.co.uk | head -1 bbc.co.uk has address 151.101.192.81对于同一个域名，两个程序得到的 IP 地址是相同的；那么它们是使用同样的方法得到结果的吧？事实并非如此。下面文件给出了我主机上 ping 对应的 DNS 相关的系统调用：root@linuxdns1:~# strace -e trace=open -f ping -c1 google.com open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 open("/lib/x86_64-linux-gnu/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4 open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4 open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4 open("/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 4 open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 4 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 4 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4 open("/lib/x86_64-linux-gnu/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 4 open("/lib/x86_64-linux-gnu/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 4 PING google.com (216.58.204.46) 56(84) bytes of data. open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 4 64 bytes from lhr25s12-in-f14.1e100.net (216.58.204.46): icmp_seq=1 ttl=63 time=13.0 ms [...]下面是 host 对应的系统调用:$ strace -e trace=open -f host google.com [...] [pid 9869] open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory) [pid 9869] open("/usr/share/locale/en/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory) [pid 9869] open("/usr/share/locale/en/LC_MESSAGES/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory) [pid 9869] open("/usr/lib/ssl/openssl.cnf", O_RDONLY) = 6 [pid 9869] open("/usr/lib/x86_64-linux-gnu/openssl-1.0.0/engines/libgost.so", O_RDONLY|O_CLOEXEC) = 6[pid 9869] open("/etc/resolv.conf", O_RDONLY) = 6 google.com has address 216.58.204.46 [...]可以看出 ping 打开了 nsswitch.conf 文件，但 host 没有；但两个程序都打开了 /etc/resolv.conf 文件。下面我们依次查看这两个 .conf 扩展名的文件。2) NSSwitch 与 /etc/nsswitch.conf我们已经确认应用可以自主决定选用哪个 DNS 服务器。很多应用（例如 ping）通过配置文件 /etc/nsswitch.conf （根据具体实现 1 ）参考 NSSwitch 完成选择。NSSwitch 不仅用于 DNS 查询，例如，还用于密码与用户信息查询。NSSwitch 最初是 Solaris OS 的一部分，可以让应用无需硬编码查询所需的文件或服务，而是在其它集中式的、无需应用开发人员管理的配置文件中找到。下面是我的 nsswitch.conf：passwd: compat group: compat shadow: compat gshadow: files hosts: files dns myhostname networks: files protocols: db files services: db files ethers: db files rpc: db files netgroup: nis我们需要关注的是 hosts 行。我们知道 ping 用到 nsswitch.conf 文件，那么我们修改这个文件（的 hosts 行），看看能够如何影响 ping。修改 nsswitch.conf， hosts 行仅保留 files如果你修改 nsswitch.conf，将 hosts 行仅保留 files：hosts: files此时， ping 无法获取 google.com 对应的 IP 地址：$ ping -c1 google.com ping: unknown host google.com但 localhost 的解析不受影响：$ ping -c1 localhost PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.039 ms此外，host 命令正常返回：$ host google.com google.com has address 216.58.206.110毕竟如我们之前看到的那样，host 不受 nsswitch.conf 影响。修改 nsswitch.conf， hosts 行仅保留 dns如果你修改 nsswitch.conf，将 hosts 行仅保留 dns：hosts: dns此时，google.com 的解析恢复正常：$ ping -c1 google.com PING google.com (216.58.198.174) 56(84) bytes of data. 64 bytes from lhr25s10-in-f174.1e100.net (216.58.198.174): icmp_seq=1 ttl=63 time=8.01 ms但 localhost 无法解析：$ ping -c1 localhost ping: unknown host localhost下图给出默认 NSSwitch 中 hosts 行对应的查询逻辑：我的 hosts: 配置是 nsswitch.conf 给出的默认值3) /etc/resolv.conf我们已经知道 host 和 ping 都使用 /etc/resolv.conf 文件。下面给出我主机的 /etc/resolv.conf 文件内容：# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 10.0.2.3先忽略前两行，后面我们会回过头来看这部分（它们很重要，但你还需要一些知识储备）。其中 nameserver 行指定了查询用到的 DNS 服务器。如果将该行注释掉：#nameserver 10.0.2.3再次运行：$ ping -c1 google.com ping: unknown host google.com解析失败了，这是因为没有可用的名字服务器 2 。该文件中还可以使用其它选项。例如，你可以在 resolv.conf 文件中增加如下行：search com然后执行 ping google （不写 .com）$ ping google PING google.com (216.58.204.14) 56(84) bytes of data.程序会自动为你尝试 .com 域。第一部分总结第一部分到此结束，下一部分我们会了解 resolv.conf 文件是如何创建和更新的。下面总结第一部分涵盖的内容：操作系统中并不存在“DNS 查询”这个系统调用不同程序可能采用不同的策略获取名字对应的 IP 地址例如, ping 使用 nsswitch，后者进而使用（或可以使用） /etc/hosts、/etc/resolv.conf 以及主机名得到解析结果/etc/resolv.conf 用于决定：查询什么地址（LCTT 译注：这里可能指 search 带来的影响）使用什么 DNS 服务器执行查询如果你曾认为 DNS 查询很复杂，请跟随这个系列学习吧。ping 实现的变种之多令人惊叹。我 不 希望在这里讨论过多。 ↩另一个需要注意的地方： host 在没有指定 nameserver 的情况下会尝试 127.0.0.1:53。 ↩via: https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/作者：ZWISCHENZUGS 译者：pinewall 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出