Kubernetes ClusterFirst 下 DNS search 查询行为分析
Last modified:
在 Kubernetes 里,Pod 默认使用 dnsPolicy: ClusterFirst。很多人对它的直觉是:Pod 的 DNS 会优先走集群 DNS,外部域名再转发到上游 DNS。
这个理解大体没错,但容易漏掉一个细节:ClusterFirst 并不等于完全忽略节点上的 /etc/resolv.conf。kubelet 会把节点 resolver 配置作为基础配置读取进来,然后对普通 Pod 重新生成一份 DNS 配置。
其中,nameserver 通常会被替换成集群 DNS Service IP;但节点上的 search 域可能会被合并进 Pod 的 search list。再加上 Kubernetes 默认的 options ndots:5,一个看起来已经是完整域名的外部地址,也可能先被扩展成多个 search 查询。
现象
假设节点上的 /etc/resolv.conf 曾经有类似配置:
|
|
业务在 Pod 里访问一个外部域名:
|
|
抓包或查看 CoreDNS 日志时,可能会看到类似查询:
|
|
如果把节点上的 search example.internal 注释掉,重建 Pod 后,*.example.internal 这类扩展查询消失。这说明扩展查询来自 Pod 内 resolver 的查询名生成逻辑。
但这还不是完整结论。search 本身是正常能力,真正的问题通常发生在下一步:拼接出来的名字本该返回 NXDOMAIN,却被某个 DNS 代理、wildcard 记录或 fake-ip 机制返回了 A 记录。resolver 收到 A 记录后会认为解析成功,后续就不会再查原始域名。
NXDOMAIN 是 DNS 返回码,含义是「这个域名不存在」。对 resolver 来说,这是一个很重要的信号:当前候选名不存在,可以继续尝试 search list 里的下一个候选名,或者最后回到原始查询名。
ClusterFirst 做了什么
Kubernetes 的 DNS 配置由 kubelet 在创建 Pod sandbox 时传给容器运行时。对 ClusterFirst Pod,kubelet 的处理逻辑可以简化成三步:
- 读取 kubelet 配置里的
resolvConf文件,通常是节点/etc/resolv.conf。 - 把 Pod 的
nameserver设置为--cluster-dns指定的集群 DNS。 - 生成 Pod 的 search list:先放 Kubernetes 集群内搜索域,再追加节点 resolv.conf 里的 search 域。
Kubernetes 源码里的关键函数是 generateSearchesForDNSClusterFirst:
|
|
也就是:
|
|
最终 Pod 里可能看到:
|
|
这里要注意:节点里的 nameserver 10.0.0.1 不会直接变成普通 ClusterFirst Pod 的 nameserver。Pod 发 DNS 请求的目标通常还是 CoreDNS。但是节点里的 search example.internal 会影响 Pod 生成哪些查询名。
flowchart TD
NodeConf["Node /etc/resolv.conf\nnameserver 10.0.0.1\nsearch example.internal"]
Kubelet["kubelet DNS Configurer"]
ClusterDNS["clusterDNS\nCoreDNS Service IP"]
PodConf["Pod /etc/resolv.conf\nnameserver CoreDNS\nsearch ns.svc cluster + example.internal\noptions ndots:5"]
NodeConf -->|"parse hostSearch"| Kubelet
ClusterDNS -->|"replace nameserver"| Kubelet
Kubelet -->|"write DNS config for Pod sandbox"| PodConf
search 和 nameserver 不是二选一
这里最容易混淆的是 search 和 nameserver 的关系。
search 决定「要查哪些名字」。
nameserver 决定「把这些查询发给谁」。
所以不是「优先走 search,而不是 nameserver」。准确说是:
- 应用调用 resolver 解析一个名字。
- resolver 根据
search和ndots生成一组候选查询名。 - resolver 把这些候选查询名依次发给
nameserver。
在 ClusterFirst Pod 里,这个 nameserver 通常是 CoreDNS:
|
|
对于非 Kubernetes 集群域名,CoreDNS 默认 Corefile 里通常有:
|
|
这表示 CoreDNS 自己无法处理的查询会继续转发给它配置的上游 resolver。
sequenceDiagram
participant App as App
participant Resolver as Pod resolver
participant CoreDNS as CoreDNS
participant Upstream as Upstream DNS
App->>Resolver: resolve service-a.region.example.com
Resolver->>Resolver: ndots:5, dots=3, expand with search list
Resolver->>CoreDNS: query service-a.region.example.com.app.svc.cluster.local
CoreDNS-->>Resolver: NXDOMAIN
Resolver->>CoreDNS: query service-a.region.example.com.svc.cluster.local
CoreDNS-->>Resolver: NXDOMAIN
Resolver->>CoreDNS: query service-a.region.example.com.cluster.local
CoreDNS-->>Resolver: NXDOMAIN
Resolver->>CoreDNS: query service-a.region.example.com.example.internal
CoreDNS->>Upstream: forward non-cluster query
Upstream-->>CoreDNS: NXDOMAIN or A record
CoreDNS-->>Resolver: response
alt search result is A record
Resolver-->>App: return search-expanded result
else search result is NXDOMAIN
Resolver->>CoreDNS: query service-a.region.example.com
CoreDNS->>Upstream: forward
Upstream-->>CoreDNS: answer
CoreDNS-->>Resolver: answer
Resolver-->>App: resolved original name
end
为什么外部域名也会先 search
原因是 ndots:5。
Linux resolver 的规则是:如果待解析名字里的点数量少于 ndots,就先尝试追加 search list。Kubernetes 默认给 ClusterFirst Pod 设置:
|
|
例如:
|
|
这个名字只有 3 个点,少于 5。resolver 会先把它当作「可能需要 search 补全的名字」,于是先查:
|
|
最后才查原始名字:
|
|
如果节点 /etc/resolv.conf 里没有 search example.internal,那么 Pod search list 里就不会出现 example.internal,对应的扩展查询也不会发生。但只要仍然是 ndots:5,Kubernetes 默认的三个集群 search 域仍然可能先被尝试。
search 什么时候是健康的
search 的典型用途是短主机名补全。例如希望业务只访问:
|
|
resolver 自动补全成:
|
|
这种用法没有问题,前提是 DNS zone 对不存在的名字返回 NXDOMAIN。也就是说,DNS 只应该给真实存在的主机名返回 A 记录;对不存在的补全名,应该明确告诉客户端「没有这个名字」。
健康行为应该是:
|
|
有问题的行为是:
|
|
第二种情况里,resolver 会在 search 扩展阶段提前成功,应用拿到的是拼接域名的结果,而不是原始域名的结果。
可以用 dig 直接确认:
|
|
如果返回:
|
|
说明这个 search 后缀对不存在的名字处理正常,resolver 可以继续尝试后续候选名。如果返回 NOERROR 并带 A 记录,就要继续检查 DNS 代理、fake-ip、fallback 或 wildcard 记录。
怎么验证
先看 Pod 实际 DNS 配置:
|
|
重点看三项:
|
|
再确认 Pod 跑在哪个节点:
|
|
然后看 kubelet 实际使用哪个 resolv.conf:
|
|
如果这里是 /etc/resolv.conf,就去对应节点确认文件内容。注意,已经创建的 Pod 不一定会因为节点 resolv.conf 变化而自动刷新,通常需要重建 Pod。
如果要看 CoreDNS 的转发配置:
|
|
重点看 Corefile 里的 forward 配置。
规避方式
最小影响的方式是在访问外部域名时使用 FQDN,也就是末尾加点:
|
|
末尾的 . 表示这个名字已经是绝对域名,resolver 不再追加 search list。
如果能控制 Pod 配置,也可以降低 ndots:
|
|
这样 service-a.region.example.com 这类包含多个点的外部域名会优先按原始名字查询。
如果问题来自节点上的 search 域,且这个 search 域不应该进入业务 Pod,可以清理节点 /etc/resolv.conf,或者让 kubelet 的 resolvConf 指向一份更干净的 resolver 配置文件。
小结
ClusterFirst 的关键不是「只用集群 DNS,不看节点 DNS 配置」。更准确的描述是:
- Pod 的 nameserver 通常被 kubelet 设置为集群 DNS。
- Pod 的 search list 会包含 Kubernetes 默认搜索域,并可能追加节点 resolv.conf 中的 search 域。
ndots:5会让许多外部域名先经过 search 扩展。- search 生成查询名,nameserver 负责接收这些查询;二者不是互斥关系。
所以,当看到外部域名被扩展成 xxx.<search-domain> 时,排查重点应该放在 Pod 内 /etc/resolv.conf 的 search 和 options ndots 上,而不是只看 nameserver。
参考资料: