追踪 Kubernetes 中的 DNS 查询
在过去的文章中,我们曾 追踪过 Kubernetes 中的网络数据包,这篇文章将追踪 Kubernetes 中的 DNS 查询。
让我们以在 Pod 中解析 Service 完全限定域名(FQDN) foo.bar.svc.cluster.local
为例。
在开始之前,先回顾下 DNS 的解析流程。
DNS 的解析流程
简化版的 DNS 处理流程:
- DNS 客户端(如浏览器、应用程序或者设备)发送域名
example.com
的查询请求。 - DNS 解析器收到请求,查询本地缓存,如果本地有记录且未过期会返回本地的记录。
- 如果本地缓存未命中,DNS 解析器将从 DNS 根服务器开始向下查询,首先是顶级域名(Top Level Domain, TLD) DNS 服务器(这里是
.com
),一直向下直到可以解析example.com
的服务器。 - 能够解析
example.com
的服务器成为权威 DNS 名称服务器(Authoritative DNS name server),解析器访问该服务器并收到 IP 地址等相关信息,然后返回给给客户端。解析完成。
从流程来看非常重要的一项配置就是上游 DNS 服务器,该配置位于 Pod 中。这里 Kubernetes 的集群 DNS 服务器正是扮演上游 DNS 服务器的角色,比如 kube-dns、CoreDNS,二者均实现了 Kubernetes 的基于 DNS 的服务发现规范。对 CoreDNS 感兴趣的,可以参考上一篇文章 浅析 CoreDNS 的工作机制。
Pod DNS 配置
在 解析 kubelet 源码 一文中,我们曾分析了 kubelet 创建 pod 的流程。kubelet 创建 pod sandbox 配置时 ,其中重要的一项配置就是准备 pod 的 DNS 配置(Pod 的 DNS 配置由 pod 的 dnsPolicy
和 dnsConfig
字段进行操作,这里不展开,下面的部分按照 dnsPolicy=ClusterFirst
情况进行说明)。
配置的内容包括如下三个部分:
- DNS 服务器
nameserver
:来自 kubelet 配置(通常位于/var/lib/kubelet/config.yaml
)的clusterDNS
字段 - 搜索域
search
:包含四种域:命名空间域、服务域、集群域,以及节点/etc/resolv.conf
中定义的搜索域。集群域来自 kubelet 配置的clusterDomain
字段,默认为cluster.local
;命名空间域NS.svc.cluster.local
;服务域svc.cluster.local
- 选项
options
:默认为ndots:5
然后 kubelet 调用 CRI 接口创建容器,由 CRI 的实现将 DNS 配置写入到容器文件(默认地址 /etc/resolv.conf
)中,如 Containerd 的 pkg/cri/server/sandbox_run_linux.go#L272。
我们查看命名空间 default
下某个 pod 的 DNS 配置:
cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
这里的 nameserver
正是 Service kube-dns
的 cluster IP 地址,也就是集群的 DNS 服务器,即 dnsPolicy=ClusterFirst
的结果。
search 与 ndots
search
用于指定默认的搜索域。当你在使用不完全限定域名(例如,只提供主机名而没有域名)进行域名解析时,系统会尝试在搜索域中找到匹配的完全限定域名。搜索域按照出现的顺序进行搜索,直到找到匹配的域名或搜索完所有的域名。
ndots
用于指定在进行域名解析时,系统自动添加域名的点号个数阈值。当提供的域名中点号的个数达到或超过这个阈值时,系统会将其视为完全限定域名,而不再使用搜索域进行搜索。默认为 1
,这里将其设置为 5
。
注:
ndots
的值大小会影响 DNS 解析的性能,为了获得较好的性能,建议使用 FQDN 进行服务访问,以及将 ndots 改为更小的值。
Pod DNS 解析
当在 Pod 中执行 DNS 解析时,查询请求被发到本地(pod 中)的 DNS 解析器。这个解析器先在缓存中查询,如果未命中,则会根据 /etc/resolv.conf
中的配置,将请求发到上游的 DNS 服务器,即集群 DNS 服务器 10.96.0.10
完成域名解析。
根据前面的介绍,假如我们要解析的域名是 foo.bar
,会依次进行如下的查询:
- foo.bar.default.svc.cluster.local
- foo.bar.svc.cluster.local(匹配到结果)
当我们使用 foo.bar
、foo.bar.svc
都可以完成解析,但 foo.bar.svc.cluster
不行,因为追加了搜索域后无法匹配到结果。假如请求方与目标服务在同一个命名空间下,只用 foo
也是可以的。