TT Lab
开始
学习 学习路径 课程

证书已经续期,浏览器却还显示旧证书

DNS 没有传播,只有缓存和 TTL

在 TT Lab 中继续学习

一句话总结

一个名称变成地址,要依次经过 /etc/hosts → 解析器配置 → 缓存 → 权威服务器,在这期间的任何一处,旧答案都会按 TTL 继续存活。“DNS 改了,为什么还是走旧地址”的答案,几乎总是在这个顺序之中。

为什么需要它

把服务迁移到新服务器,并修改了 A 记录。在我的笔记本上解析出来的是新地址,但一半的客户仍然连在旧服务器上。负责人说“传播(propagation)需要时间”,这句话不是解释,而是听天由命。DNS 里并没有传播这回事。真正存在的只有缓存和 TTL,每个缓存都会把自己收到的答案原样返回,直到 TTL 用完。所以“什么时候结束”这个问题可以准确回答——修改之前那条记录的 TTL 到期,就结束了。

还有反方向的事故。如果在创建名称之前有人先查询了它,“不存在”这个答案也会被缓存。因此即使已经添加了记录,在一段时间内仍然会出现 NXDOMAIN,而这段时间由 zone 的 SOA 决定,不是 A 记录的 TTL。如果不了解这两点,面对 DNS 故障时,能做的就只有等待。

工作原理

程序调用 getaddrinfo() 时,glibc 首先查看 /etc/nsswitch.conf 中的 hosts: 这一行。通常顺序是 files dns,所以只要 /etc/hosts 中有这个名称,就根本不会去询问 DNS。getent hosts <이름>(占位符为名称)完全按这个顺序执行,所以要确认“程序看到的答案”时,getent 比 dig 更准确。dig 是不经过解析器库、直接向 DNS 服务器发问的工具。

一旦转入 DNS,规则由 /etc/resolv.conf 决定。nameserver 最多可以写 MAXNS(目前为 3)个,并按所写顺序依次询问。search 列表和 options ndots:n(默认为 1)决定如何扩展短名称——如果名称中的点数少于 ndots,就会先逐个附加搜索域进行查询,全部失败后再用原始名称查询。按官方文档所述,Kubernetes Pod 的 resolv.conf 中 search <ns>.svc.cluster.local svc.cluster.local cluster.local 之后是 options ndots:5,所以在 Pod 内查询 api.example.com 这种点数少于五个的外部名称时,会先发出附加了搜索域的查询,收到三次 NXDOMAIN 之后,才去查询真正的名称。因此,当 Pod 中的外部 API 格外慢时,头号嫌疑对象就是 ndots。

解析器(缓存)按 RFC 1034 的规定,递归地找到权威服务器并获取答案,然后在每条记录所附带的 TTL 秒数内保存该答案。TTL 由权威服务器的 zone 决定,缓存会一边递减剩余时间一边返回。用 dig 向同一个缓存查询两次,第二次答案的 TTL 变小了,这就是证据。

$ dig @127.0.0.1 -p 5301 www.lab.internal A +noall +answer
www.lab.internal.   120   IN   A   10.0.0.10
$ dig @127.0.0.1 -p 5301 www.lab.internal A +noall +answer     # 3초 뒤
www.lab.internal.   117   IN   A   10.0.0.10

CNAME 的意思是“这个名称是那个名称的别名”。解析器遇到 CNAME 时,会沿着链一直追到末尾,把 CNAME 和最后的 A 放进同一个答案里。链越长,需要缓存的记录越多,而每条记录的 TTL 各自独立计时。

不存在的名称是如何缓存的呢。RFC 2308 规定,当权威服务器返回 NXDOMAIN(名称不存在)或 NODATA(名称存在但没有该类型)时,要在 authority 节中放入 zone 的 SOA,并把它的 TTL 设为 SOA 的 MINIMUM 字段与 SOA 自身 TTL 中较小的值。解析器会在这段时间内记住“不存在”。如果在向 zone 添加记录之前试着查询过,那么添加之后,在这段时间内收到的仍然是不存在的答案。

$ dig @127.0.0.1 -p 5301 nope.lab.internal A +noall +comments +authority
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, ...
;; AUTHORITY SECTION:
lab.internal.   60   IN   SOA   ns1.lab.internal. admin.lab.internal. 2026091101 3600 600 86400 60

在现场相遇的样子

规划迁移工作时,有经验的人只做一件事——在修改的几天前先把 TTL 调低。 如果修改的是 TTL 为 3600 的记录,最坏情况下有一个小时两台服务器会同时接收流量。提前调低到 60(这次变更本身也要经过旧 TTL 的时长才生效),实际迁移一分钟内就能完成。结束之后也别忘了把 TTL 调回去。低 TTL 会降低缓存命中率,增加权威服务器的负载。

在 Kubernetes 中,ndots:5 产生的额外查询会表现为 CoreDNS 的负载和延迟。正统的做法是在外部名称末尾加点,写成绝对名称(api.example.com.),或者用 Pod 的 dnsConfig 降低 ndots。/etc/hosts 也会成为陷阱。如果以前调试时加进去的一行还留在那里,只有那台机器会去别的地方——所以收到“只有这台机器不一样”的反馈时,先执行 getent hosts。

下一项实验要做什么

在 Pod 内用 Python 启动权威服务器和缓存服务器。在 zone 文件中放一条 TTL 为 120 的 A 记录和 SOA(minimum 为 60),用 dig 读取 TTL 递减的过程、CNAME 链以及 NXDOMAIN 的 SOA TTL。然后修改 zone,重现权威服务器给出新答案、缓存却给出旧答案的状态,清空缓存之后,计算最坏情况下的传播时间并写进报告。最后用 +search +ndots=5 在服务器日志中确认短名称会产生什么样的查询。