Kubernetes 中的 DNS
在 Kubernetes 中,服务发现有几种方式:
①:基于环境变量的方式
②:基于内部域名的方式
基本上,使用环境变量的方式很少,主要还是使用内部域名这种服务发现的方式。
其中,基于内部域名的方式,涉及到 Kubernetes 内部域名的解析,而 kubedns,是 Kubernetes 官方的 DNS 解析组件。从 1.11 版本开始,kubeadm 已经使用第三方的 CoreDNS 替换官方的 kubedns 作为 Kubernetes 集群的内部域名解析组件,我们的重点,是 CoreDNS,但是在开始 CoreDNS 之前,需要先了解下 kubedns
Kubernetes 中的域名是如何解析的
在 Kubernetes 中,比如服务 a 访问服务 b,对于同一个 Namespace下,可以直接在 pod 中,通过 curl b 来访问。对于跨 Namespace 的情况,服务名后边对应 Namespace即可。比如 curl b.default。那么,使用者这里边会有几个问题:
①:服务名是什么?
②:为什么同一个 Namespace 下,直接访问服务名即可?不同 Namespace 下,需要带上 Namespace 才行?
③:为什么内部的域名可以做解析,原理是什么?
DNS 如何解析,依赖容器内 resolv 文件的配置
1 | cat /etc/resolv.conf |
这个文件中,配置的 DNS Server,一般就是 K8S 中,kubedns 的 Service 的 ClusterIP,这个IP是虚拟IP,无法ping,但可以访问。
1 | [root@node4 user1]# kubectl get svc -n kube-system |
所以,所有域名的解析,其实都要经过 kubedns 的虚拟IP 10.233.0.3 进行解析,不论是 Kubernetes 内部域名还是外部的域名。
Kubernetes 中,域名的全称,必须是 service-name.namespace.svc.cluster.local 这种模式,服务名,就是Kubernetes中 Service 的名称,所以,当我们执行下面的命令时:
1 | curl b |
必须得有一个 Service 名称为 b,这是前提。
在容器内,会根据 /etc/resolve.conf 进行解析流程。选择 nameserver 10.233.0.3 进行解析,然后,用字符串 “b”,依次带入 /etc/resolve.conf 中的 search 域,进行DNS查找,分别是:
1 | // search 内容类似如下(不同的pod,第一个域会有所不同) |
b.default.svc.cluster.local -> b.svc.cluster.local -> b.cluster.local ,直到找到为止。
所以,我们执行 curl b,或者执行 curl b.default,都可以完成DNS请求,这2个不同的操作,会分别进行不同的DNS查找步骤:
1 | // curl b,可以一次性找到(b +default.svc.cluster.local) |
So Questions
curl b,要比 curl b.default 效率高?
答案是肯定的,因为 curl b.default,多经过了一次 DNS 查询。
当执行 curl b.default,也就使用了带有命名空间的内部域名时,容器的第一个 DNS 请求是
1 | // b.default + default.svc.cluster.local |
当请求不到 DNS 结果时,使用
1 | // b.default + svc.cluster.local |
进行请求,此时才可以得到正确的DNS解析。
访问外部域名走 search 域吗
这个答案,不能说肯定也不能说否定,看情况,可以说,大部分情况要走 search 域。
我们以请求 youku.com 为例,通过抓包的方式,看一看在某个容器中访问 youku.com,进行的DNS查找的过程,都产生了什么样的数据包。注意:我们要抓DNS容器的包,就得先进入到DNS容器的网络中(而不是发起DNS请求的那个容器)。
由于DNS容器往往不具备bash,所以无法通过 docker exec 的方式进入容器内抓包,我们采用其他的方式:
1 | // 1、找到容器ID,并打印它的NS ID |
在其他的容器中,进行 youku.com 域名查找
1 | nslookup youku.com 172.22.121.65 |
注意:nslookup命令的最后指定DNS服务容器的IP,是因为,如果不指定,且DNS服务的容器存在多个的话,那么DNS请求,可能会均分到所有DNS服务的容器上,我们如果只抓某单个DNS服务容器抓到的包,可能就不全了,指定IP后,DNS的请求,就必然只会打到单个的DNS容器。抓包的数据才完整。
可以看到类似如下结果:
1 | 17:01:28.732260 IP 172.20.92.100.36326 > nodexxxx.domain: 4394+ A? youku.com.default.svc.cluster.local. (50) |
我们可以看到,在真正解析 youku.com 之前,经历了 youku.com.default.svc.cluster.local. -> youku.com.svc.cluster.local. -> youku.com.cluster.local. -> youku.com.
这也就意味着有3次DNS请求,是浪费的无意义的请求。
为何会出现DNS请求浪费的情况
这是因为,在 Kubernetes 中,其实 /etc/resolv.conf 这个文件,并不止包含 nameserver 和 search 域,还包含了非常重要的一项:ndots。我们之前没有提及这个项,也是希望再次能引起读者重视。
1 | [root@xxxx-67f54c6dff-h4zxq /]# cat /etc/resolv.conf |
ndots:5,表示:如果查询的域名包含的点“.”,不到5个,那么进行DNS查找,将使用非完全限定名称(或者叫绝对域名),如果你查询的域名包含点数大于等于5,那么DNS查询,默认会使用绝对域名进行查询。举例来说:
如果我们请求的域名是,a.b.c.d.e,这个域名中有4个点,那么容器中进行DNS请求时,会使用非绝对域名进行查找,使用非绝对域名,会按照 /etc/resolv.conf 中的 search 域,走一遍追加匹配:
a.b.c.d.e.cicd.svc.cluster.local. ->
a.b.c.d.e.svc.cluster.local. ->
a.b.c.d.e.cluster.local.
直到找到为止。如果走完了search域还找不到,则使用 a.b.c.d.e. ,作为绝对域名进行DNS查找。
我们通过抓包分析一个具体案例:
域名中点数少于5个的情况:
1 | // 对域名 a.b.c.d.ccccc 进行DNS解析请求 |
域名中点数>=5个的情况:
1 | // 对域名 a.b.c.d.e.ccccc 进行DNS解析请求 |
如何优化 DNS 请求浪费的情况
优化方式1:使用全限定域名
FQDN(全限定域名)的组成:
- 主机名: 指代特定设备或资源的名称。
- 域名: 指代设备或资源所属的组织或机构。
- 顶级域名(TLD): 指代域名分类的最高级别,例如.com、.net、.org、.cn等。
- 根域(.): 代表所有域名的根源,通常省略不写。
- 例子:``www.google.com.`,`mail.example.org.`,`server1.internal.company.net.`
其实最直接,最有效的优化方式,就是使用 “fully qualified name”,简单来说,使用“完全限定域名”(也叫绝对域名),你访问的域名,必须要以 “.” 为后缀,这样就会避免走 search 域进行匹配,我们抓包再试一次:
1 | // 注意:youku.com 后边有一个点 . |
在DNS服务容器上抓到的包如下:
1 | 16:57:07.628112 IP 172.20.92.100.36772 > nodexxxx.domain: 46851+ [1au] A? youku.com. (38) |
并没有多余的DNS请求。
优化方式2:具体应用配置特定的 ndots
其实,往往我们还真不太好用这种绝对域名的方式,有谁请求youku.com的时候,还写成 youku.com. 呢?
在 Kubernetes 中,默认设置了 ndots 值为5,是因为,Kubernetes 认为,内部域名,最长为5,要保证内部域名的请求,优先走集群内部的DNS,而不是将内部域名的DNS解析请求,有打到外网的机会,Kubernetes 设置 ndots 为5是一个比较合理的行为。
如果你需要定制这个长度,最好是为自己的业务,单独配置 ndots 即可(Pod为例,其实配置deployment最好)。
1 | apiVersion: v1 |
