一个有点奇怪的 dns 解析 bug

63 阅读2分钟

我发现在 ubuntu 22.04 的 k8s 集群中,如果不禁用掉外部的 dns,比如 8.8.8.8, 集群内部的 dns 解析就会一直失败


root@k8s-ctrl:~/kubespray1-31# cat /etc/resolv.conf
nameserver 169.254.25.10
search default.svc.cluster.local svc.cluster.local cluster.local
#nameserver 8.8.8.8
root@k8s-ctrl:~/kubespray1-31#
root@k8s-ctrl:~/kubespray1-31#
root@k8s-ctrl:~/kubespray1-31#
root@k8s-ctrl:~/kubespray1-31# nslookup kubernetes
;; Got recursion not available from 169.254.25.10
Server:		169.254.25.10
Address:	169.254.25.10#53

Name:	kubernetes.default.svc.cluster.local
Address: 10.233.0.1
;; Got recursion not available from 169.254.25.10

root@k8s-ctrl:~/kubespray1-31#

现在观察到的现象,正是 Linux glibc + NodeLocalDNS fallback 行为 的“微妙交互结果”。
我们来逐层剖析下为什么「注释掉外部 DNS」反而能解析成功:


🧩 现象回顾

/etc/resolv.conf 是:

nameserver 169.254.25.10
# nameserver 8.8.8.8

执行:

nslookup kubernetes

得到:

;; Got recursion not available from 169.254.25.10
Name: kubernetes.default.svc.cluster.local
Address: 10.233.0.1

居然成功解析到了 10.233.0.1


⚙️ 内部原理

1️⃣ Linux resolver 的默认行为

/etc/resolv.conf 中定义多个 nameserver 时:

  • 默认顺序尝试;
  • 若第一个返回 “SERVFAIL / REFUSED / recursion not available”,
    glibc 会直接跳到下一个 nameserver
  • 但在某些情况下(例如返回 NOERROR + 有结果),glibc 会“认为成功”并停止;
  • 如果只有一个 nameserver,则必须使用它,无论返回什么。

2️⃣ NodeLocalDNS 的行为

NodeLocalDNS 监听在 169.254.25.10

  • .cluster.localin-addr.arpa 等域名 → 转发给 CoreDNS;
  • 对其他域名(如 www.baidu.com)→ 转发给外部上游(比如 8.8.8.8);
  • 不提供 recursion 服务(即拒绝递归查询请求)。

当你执行:

nslookup kubernetes

查询流程大概如下:

nslookup → resolver → 169.254.25.10 (NodeLocalDNS)

NodeLocalDNS 收到后:

  • 发现查询 “kubernetes”;

  • 因为 /etc/resolv.conf 中的 search 字段包含:

    default.svc.cluster.local svc.cluster.local cluster.local
    

    → glibc 会依次尝试补全:

    kubernetes.default.svc.cluster.local
    kubernetes.svc.cluster.local
    kubernetes.cluster.local
    kubernetes
    

CoreDNS 能正确回答第一个 → 成功解析。


3️⃣ 那“Got recursion not available”是啥意思?

那其实是 nslookup 的显示逻辑问题:

  • 它在尝试最初的短名 kubernetes 时,NodeLocalDNS 拒绝递归;
  • 但随后 resolver 自动补全 kubernetes.default.svc.cluster.local 并得到成功响应;
  • 所以 nslookup 同时显示两段:
    一段是 recursion not available(短名失败),
    一段是成功结果(补全名成功)。

4️⃣ 为什么注释掉 8.8.8.8 后反而“更稳”?

因为:

  • 当有多个 nameserver 时,glibc 在遇到 recursion not available 后会立刻尝试下一个(8.8.8.8);
  • 而 8.8.8.8 对 kubernetes.default.svc.cluster.local 返回 NXDOMAIN;
  • 最终 resolver 会以 最后一次失败 为准(即 NXDOMAIN);
  • 结果就覆盖掉了 NodeLocalDNS 的成功答复。

所以:
✅ 去掉 8.8.8.8 → resolver 不再 fallback → NodeLocalDNS 的答复被保留并显示成功。

总结: 其实 dns 解析虽然成功了,但是也失败了,所以最后失败了。


✅ 总结对比表

/etc/resolv.conf 配置解析结果原因
nameserver 169.254.25.10``nameserver 8.8.8.8❌ 失败NodeLocalDNS 拒绝 recursion → fallback 到 8.8.8.8 → NXDOMAIN 覆盖结果
nameserver 169.254.25.10✅ 成功glibc 无可退选 → 等待 NodeLocalDNS 补全 .svc.cluster.local 并得到正确答复

💡 建议

你的现象完全符合 NodeLocalDNS 预期逻辑。

最推荐的配置是:

nameserver 169.254.25.10
search default.svc.cluster.local svc.cluster.local cluster.local

✅ Pod 内外 DNS 都可正常工作
✅ NodeLocalDNS 缓存生效
✅ 不会被外部 DNS 覆盖错误返回