你在 /etc/hosts 里加了一个名字,连上 VPN,以为这条记录会对所有人可见。事实并非如此,而意外通常伴随第二个症状:这个名字在你自己机器上能解析,在同事机器上却失败,看起来像 VPN 的 DNS 服务坏了,实际上却是 hosts 文件在做它本来该做的事。hosts 文件是本地名称解析配置,不是区域更新,正常的 VPN 连接不会把它上传。

hosts 是本地覆盖,不是区域更新

这个文件是一张映射表,名称服务库在询问 DNS 服务器之前或代替询问时会查它。像 10.40.0.20 build.corp.example 这样的一行会改变那一台机器上应用得出的结论。它不创建记录,不经过隧道,隧道所指向的 DNS 服务器根本不知道它存在。

所以失败是不对称的。你的客户端从文件得到答案。另一个客户端去问 VPN 的 DNS 服务器,服务器什么也不返回,应用于是报告名字不存在。两台机器都行为正确,只是其中一台持有另一台从未见过的覆盖。

两条命令,两个不同组件

终止争论最快的办法,是用两条命令把差别摆出来,两条都在带有 hosts 条目的客户端上运行:

getent ahosts build.corp.example
dig @10.40.0.53 build.corp.example A

第一条询问系统自身的查询路径,在典型的 glibc 系统上它会先读本地文件再问 DNS。第二条直接把 DNS 查询发给那个地址上的服务器,完全不看本地文件。当第一条返回你的条目、第二条返回 NXDOMAIN 时,你就证明了这条记录不存在于 DNS,而你的机器是在用一个本地覆盖作答。

顺序是可配置的,在就某个发行版下结论之前值得先查一下。看 /etc/nsswitch.conf 里的 hosts: 行;hosts: files dns 会先查文件。不要只为改变顺序就把那个示例照搬到正在工作的配置上,因为真实系统往往还会列出示例里没有的额外来源和条件服务条目。

systemd-resolved 让测试变得模糊

在使用 systemd-resolved 的系统上,本地 stub 也会提供来自 hosts 数据库的名称。因此查询 127.0.0.53 并不是对远端服务器的独立测试,尽管它看起来像一次 DNS 查询、行为也像。你可以用下面的方法确认解析器知道什么、会去问哪些服务器:

resolvectl query build.corp.example
resolvectl status

status 的输出会按链路列出 DNS 服务器,你由此判断 VPN 链路到底有没有拿到解析器。这个问题与 hosts 条目无关,而当私有名称对所有人而不是某一个人失败时,真正值得问的就是它。

应用还可以再加一层。带自己 DNS-over-HTTPS 设置的浏览器、带自己解析器配置的容器,或在进程整个生命周期内缓存查询的运行时,都可能给出与系统不同的答案。与其假设一条命令能描述机器上所有程序,不如直接测试你关心的那个应用。

VPN 上真正共享名字的是什么

如果一个私有名称要对所有人有效,它需要一个所有人解析器都能到达的归属者。干净的答案是:把记录发布到托管区域,并让该区域能通过预期的 VPN DNS 路径被解析。这样记录就有了单一事实来源、审计轨迹和生命周期,没人需要记住哪台笔记本上带着本地覆盖。

分发 hosts 文件是一种正当的替代方案,但那属于终端管理,而不是名称服务。如果你选择它,文件必须由管理机群的东西来部署、版本化和移除,设计文档里也应该这样描述。「VPN 会处理」不是对这个流程的描述。

动态 DNS 注册是第三种机制,有自己的权限、更新认证和记录生存期。它既不会从 hosts 条目产生,也不会仅仅因为客户端连上就产生,除非服务器和客户端两边都做了配置。一条自己冒出来的记录,说明有人启用了安全更新、并且有客户端在自我注册,这种配置属于设计记录,而不属于排障帖。如果没人说得清这个设置归谁,就把自动记录视为未经核实,并以托管区域作为事实来源。

解析出来的名字不是路由

名称解析与可达性是两回事,而过时的本地映射正是混淆二者的经典方式。指向已迁移地址的 hosts 条目,会在 DNS 修正很久之后仍继续选中过时主机,症状看起来就像某个解析正常的名字出现了路由故障。

所以当报告说「名字能解析但服务不可达」时,把系统查询与一次显式的 VPN DNS 服务器查询对比,再把你拿到的地址与服务实际使用的地址核对。systemd-resolved 参考记录了它会查询的本地来源,那正是这次对比需要的地图。

然后通过它的归属者修正这个覆盖。从你自己的 hosts 文件里删掉一行,对十二台从部署脚本收到同一行的笔记本毫无作用,而这才是人们真正遇到的那一版问题。

相关文章