0
点赞
收藏
分享

微信扫一扫

k8s 服务注册与发现(二)Kubernetes内部域名解析原理


Kubernetes内部域名解析原理

同一集群同一命名空间下

在 Kubernetes 中,比如服务 a 访问服务 b,对于同一个 Namespace下,可以直接在 pod 中,通过 curl b 来访问。对于跨 Namespace 的情况,服务名后边对应 Namespace即可。比如 curl b.default。那么,使用者这里边会有几个问题:

①:服务名是什么?
②:为什么同一个 Namespace 下,直接访问服务名即可?不同 Namespace 下,需要带上 Namespace 才行?
③:为什么内部的域名可以做解析,原理是什么?

DNS 如何解析,依赖容器内 resolv 文件的配置

cat /etc/resolv.conf

nameserver 10.233.0.3
search default.svc.cluster.local svc.cluster.local cluster.local

这个文件中,配置的 DNS Server,一般就是 K8S 中,kubedns 的 Service 的 ClusterIP,这个IP是虚拟IP,无法ping,但可以访问。

[root@node4 user1]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP 270d
kubernetes-dashboard ClusterIP 10.233.22.223 <none> 443/TCP 124d

所以,所有域名的解析,其实都要经过 kubedns 的虚拟IP 10.233.0.3 进行解析,不论是 Kubernetes 内部域名还是外部的域名。

Kubernetes 中,域名的全称,必须是 service-name.namespace.svc.cluster.local 这种模式,服务名,就是Kubernetes中 Service 的名称,所以,当我们执行下面的命令时:

curl b

必须得有一个 Service 名称为 b,这是前提。
在容器内,会根据 /etc/resolve.conf 进行解析流程。选择 nameserver 10.233.0.3 进行解析,然后,用字符串 “b”,依次带入 /etc/resolve.conf 中的 search 域,进行DNS查找,分别是:

// search 内容类似如下(不同的pod,第一个域会有所不同)
search default.svc.cluster.local svc.cluster.local cluster.local

b.default.svc.cluster.local -> b.svc.cluster.local -> b.cluster.local ,直到找到为止。

同一集群不同命名空间下

DNS 查询可能因为执行查询的 Pod 所在的名字空间而返回不同的结果。 不指定名字空间的 DNS 查询会被限制在 Pod 所在的名字空间内。 要访问其他名字空间中的 Service,需要在 DNS 查询中指定名字空间。

例如,假定名字空间 ​​test​​​ 中存在一个 Pod,​​prod​​​ 名字空间中存在一个服务 ​​data​​。

Pod 查询 ​​data​​​ 时没有返回结果,因为使用的是 Pod 的名字空间 ​​test​​。

Pod 查询 ​​data.prod​​ 时则会返回预期的结果,因为查询中指定了名字空间。

DNS 查询可以使用 Pod 中的 ​​/etc/resolv.conf​​​ 展开。kubelet 会为每个 Pod 生成此文件。例如,对 ​​data​​​ 的查询可能被展开为 ​​data.test.svc.cluster.local​​​。 ​​search​​​ 选项的取值会被用来展开查询。要进一步了解 DNS 查询,可参阅 ​​resolv.conf​​。

nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

概括起来,名字空间 ​​test​​​ 中的 Pod 可以成功地解析 ​​data.prod​​​ 或者 ​​data.prod.svc.cluster.local​​。

DNS 记录

哪些对象会获得 DNS 记录呢?

  1. Services
  2. Pods

以下各节详细介绍已支持的 DNS 记录类型和布局。 其它布局、名称或者查询即使碰巧可以工作,也应视为实现细节, 将来很可能被更改而且不会因此发出警告。 有关最新规范请查看 ​​Kubernetes 基于 DNS 的服务发现​​。

Services

A/AAAA 记录

“普通” Service(除了无头 Service)会以 ​​my-svc.my-namespace.svc.cluster-domain.example​​ 这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于 Service 的 IP 协议族。 该名称会解析成对应 Service 的集群 IP。

“无头(Headless)” Service (没有集群 IP)也会以 ​​my-svc.my-namespace.svc.cluster-domain.example​​ 这种名字的形式被指派一个 DNS A 或 AAAA 记录, 具体取决于 Service 的 IP 协议族。 与普通 Service 不同,这一记录会被解析成对应 Service 所选择的 Pod IP 的集合。 客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。

SRV 记录

Kubernetes 根据普通 Service 或 ​​Headless Service​​​ 中的命名端口创建 SRV 记录。每个命名端口, SRV 记录格式为 ​​_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example​​​。 普通 Service,该记录会被解析成端口号和域名:​​my-svc.my-namespace.svc.cluster-domain.example​​​。 无头 Service,该记录会被解析成多个结果,及该服务的每个后端 Pod 各一个 SRV 记录, 其中包含 Pod 端口号和格式为 ​​auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example​​ 的域名。

Pods

A/AAAA 记录

一般而言,Pod 会对应如下 DNS 名字解析:

pod-ip-address.my-namespace.pod.cluster-domain.example

例如,对于一个位于 ​​default​​​ 名字空间,IP 地址为 172.17.0.3 的 Pod, 如果集群的域名为 ​​cluster.local​​,则 Pod 会对应 DNS 名称:

​172-17-0-3.default.pod.cluster.local​​.

通过 Service 暴露出来的所有 Pod 都会有如下 DNS 解析名称可用:

​pod-ip-address.service-name.my-namespace.svc.cluster-domain.example​​.

Pod 的 hostname 和 subdomain 字段

当前,创建 Pod 时其主机名取自 Pod 的 ​​metadata.name​​ 值。

Pod 规约中包含一个可选的 ​​hostname​​​ 字段,可以用来指定 Pod 的主机名。 当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。 举个例子,给定一个 ​​hostname​​​ 设置为 “​​my-host​​​” 的 Pod, 该 Pod 的主机名将被设置为 “​​my-host​​”。

Pod 规约还有一个可选的 ​​subdomain​​​ 字段,可以用来指定 Pod 的子域名。 举个例子,某 Pod 的 ​​hostname​​​ 设置为 “​​foo​​​”,​​subdomain​​​ 设置为 “​​bar​​​”, 在名字空间 “​​my-namespace​​​” 中对应的完全限定域名(FQDN)为 “​​foo.bar.my-namespace.svc.cluster-domain.example​​”。

示例:

apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo # 实际上不需要指定端口号
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
---
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name:

如果某无头 Service 与某 Pod 在同一个名字空间中,且它们具有相同的子域名, 集群的 DNS 服务器也会为该 Pod 的全限定主机名返回 A 记录或 AAAA 记录。 例如,在同一个名字空间中,给定一个主机名为 “busybox-1”、 子域名设置为 “default-subdomain” 的 Pod,和一个名称为 “​​default-subdomain​​​” 的无头 Service,Pod 将看到自己的 FQDN 为 “​​busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example​​​”。 DNS 会为此名字提供一个 A 记录或 AAAA 记录,指向该 Pod 的 IP。 “​​busybox1​​​” 和 “​​busybox2​​” 这两个 Pod 分别具有它们自己的 A 或 AAAA 记录。

Endpoints 对象可以为任何端点地址及其 IP 指定 ​​hostname​​。

说明: 由于不是为 Pod 名称创建 A 或 AAAA 记录的,因此 Pod 的 A 或 AAAA 需要 ​​hostname​​​。 没有设置 ​​hostname​​​ 但设置了 ​​subdomain​​​ 的 Pod 只会为 无头 Service 创建 A 或 AAAA 记录(​​default-subdomain.my-namespace.svc.cluster-domain.example​​​) 指向 Pod 的 IP 地址。 另外,除非在服务上设置了 ​​publishNotReadyAddresses=True​​,否则只有 Pod 进入就绪状态 才会有与之对应的记录。

Pod 的 setHostnameAsFQDN 字段

特性状态:​Kubernetes v1.22 [stable]​

当 Pod 配置为具有全限定域名 (FQDN) 时,其主机名是短主机名。 例如,如果你有一个具有完全限定域名 ​​busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example​​​ 的 Pod, 则默认情况下,该 Pod 内的 ​​hostname​​​ 命令返回 ​​busybox-1​​​,而 ​​hostname --fqdn​​ 命令返回 FQDN。

当你在 Pod 规约中设置了 ​​setHostnameAsFQDN: true​​​ 时,kubelet 会将 Pod 的全限定域名(FQDN)作为该 Pod 的主机名记录到 Pod 所在名字空间。 在这种情况下,​​hostname​​​ 和 ​​hostname --fqdn​​ 都会返回 Pod 的全限定域名。

说明:

在 Linux 中,内核的主机名字段(​​struct utsname​​​ 的 ​​nodename​​ 字段)限定 最多 64 个字符。

如果 Pod 启用这一特性,而其 FQDN 超出 64 字符,Pod 的启动会失败。 Pod 会一直出于 ​​Pending​​​ 状态(通过 ​​kubectl​​​ 所看到的 ​​ContainerCreating​​​), 并产生错误事件,例如 “Failed to construct FQDN from Pod hostname and cluster domain, FQDN ​​long-FQDN​​​ is too long (64 characters is the max, 70 characters requested).” (无法基于 Pod 主机名和集群域名构造 FQDN,FQDN ​​long-FQDN​​​ 过长,至多 64 字符,请求字符数为 70)。 对于这种场景而言,改善用户体验的一种方式是创建一个 ​​准入 Webhook 控制器​​, 在用户创建顶层对象(如 Deployment)的时候控制 FQDN 的长度。

Kubernetes DNS 策略

在Kubernetes 中,有4种 DNS 策略,从 Kubernetes 源码中看:

const (
// DNSClusterFirstWithHostNet indicates that the pod should use cluster DNS
// first, if it is available, then fall back on the default
// (as determined by kubelet) DNS settings.
DNSClusterFirstWithHostNet DNSPolicy = "ClusterFirstWithHostNet"

// DNSClusterFirst indicates that the pod should use cluster DNS
// first unless hostNetwork is true, if it is available, then
// fall back on the default (as determined by kubelet) DNS settings.
DNSClusterFirst DNSPolicy = "ClusterFirst"

// DNSDefault indicates that the pod should use the default (as
// determined by kubelet) DNS settings.
DNSDefault DNSPolicy = "Default"

// DNSNone indicates that the pod should use empty DNS settings. DNS
// parameters such as nameservers and search paths should be defined via
// DNSConfig.
DNSNone DNSPolicy = "None"
)

这几种DNS策略,需要在Pod,或者Deployment、RC等资源中,设置 dnsPolicy 即可,以 Pod 为例:

apiVersion: v1
kind: Pod
metadata:
labels:
name: cadvisor-nodexxxx
hostip: 192.168.x.x
name: cadvisor-nodexxxx
namespace: monitoring
spec:
containers:
- args:
- --profiling
- --housekeeping_interval=10s
- --storage_duration=1m0s
image: google/cadvisor:latest
name: cadvisor-nodexxxx
ports:
- containerPort: 8080
name: http
protocol: TCP
resources: {}
securityContext:
privileged: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
nodeName: nodexxxx

具体来说:

None

表示空的DNS设置
这种方式一般用于想要自定义 DNS 配置的场景,而且,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。

Default

有人说 Default 的方式,是使用宿主机的方式,这种说法并不准确。
这种方式,其实是,让 kubelet 来决定使用何种 DNS 策略。而 kubelet 默认的方式,就是使用宿主机的 /etc/resolv.conf(可能这就是有人说使用宿主机的DNS策略的方式吧),但是,kubelet 是可以灵活来配置使用什么文件来进行DNS策略的,我们完全可以使用 kubelet 的参数:–resolv-conf=/etc/resolv.conf 来决定你的DNS解析文件地址。

ClusterFirst

这种方式,表示 POD 内的 DNS 使用集群中配置的 DNS 服务,简单来说,就是使用 Kubernetes 中 kubedns 或 coredns 服务进行域名解析。如果解析不成功,才会使用宿主机的 DNS 配置进行解析。

ClusterFirstWithHostNet

在某些场景下,我们的 POD 是用 HOST 模式启动的(HOST模式,是共享宿主机网络的),一旦用 HOST 模式,表示这个 POD 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行DNS查询,但如果你想使用了 HOST 模式,还继续使用 Kubernetes 的DNS服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet。

Pod 的 DNS 配置

特性状态:​Kubernetes v1.14 [stable]​

Pod 的 DNS 配置可让用户对 Pod 的 DNS 设置进行更多控制。

​dnsConfig​​​ 字段是可选的,它可以与任何 ​​dnsPolicy​​​ 设置一起使用。 但是,当 Pod 的 ​​dnsPolicy​​​ 设置为 “​​None​​​” 时,必须指定 ​​dnsConfig​​ 字段。

用户可以在 ​​dnsConfig​​ 字段中指定以下属性:

  • ​nameservers​​​:将用作于 Pod 的 DNS 服务器的 IP 地址列表。 最多可以指定 3 个 IP 地址。当 Pod 的​​dnsPolicy​​​ 设置为 “​​None​​” 时, 列表必须至少包含一个 IP 地址,否则此属性是可选的。 所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。
  • ​searches​​:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。 指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。 重复的域名将被删除。Kubernetes 最多允许 6 个搜索域。
  • ​options​​​:可选的对象列表,其中每个对象可能具有​​name​​​ 属性(必需)和​​value​​ 属性(可选)。 此属性中的内容将合并到从指定的 DNS 策略生成的选项。 重复的条目将被删除。

以下是具有自定义 DNS 设置的 Pod 示例:

apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name:

创建上面的 Pod 后,容器 ​​test​​​ 会在其 ​​/etc/resolv.conf​​ 文件中获取以下内容:

nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0

对于 IPv6 设置,搜索路径和名称服务器应按以下方式设置:

kubectl exec -it dns-example -- cat

输出类似于:

nameserver 2001:db8:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5

扩展 DNS 配置

特性状态:​Kubernetes 1.22 [alpha]​

对于 Pod DNS 配置,Kubernetes 默认允许最多 6 个 搜索域( Search Domain) 以及一个最多 256 个字符的搜索域列表。

如果启用 kube-apiserver 和 kubelet 的特性门控 ​​ExpandedDNSConfig​​,Kubernetes 将可以有最多 32 个 搜索域以及一个最多 2048 个字符的搜索域列表。

So Questions

访问外部域名走 search 域吗

这个答案,不能说肯定也不能说否定,看情况,可以说,大部分情况要走 search 域。

我们以请求 youku.com 为例,通过抓包的方式,看一看在某个容器中访问 youku.com,进行的DNS查找的过程,都产生了什么样的数据包。注意:我们要抓DNS容器的包,就得先进入到DNS容器的网络中(而不是发起DNS请求的那个容器)。

由于DNS容器往往不具备bash,所以无法通过 docker exec 的方式进入容器内抓包,我们采用其他的方式:

// 1、找到容器ID,并打印它的NS ID
docker inspect --format "{{.State.Pid}}" 16938de418ac
// 2、进入此容器的网络Namespace
nsenter -n -t 54438
// 3、抓DNS包
tcpdump -i eth0 udp dst port 53|grep youku.com

在其他的容器中,进行 youku.com 域名查找

nslookup  youku.com 172.22.121.65

注意:nslookup命令的最后指定DNS服务容器的IP,是因为,如果不指定,且DNS服务的容器存在多个的话,那么DNS请求,可能会均分到所有DNS服务的容器上,我们如果只抓某单个DNS服务容器抓到的包,可能就不全了,指定IP后,DNS的请求,就必然只会打到单个的DNS容器。抓包的数据才完整。

可以看到类似如下结果:

17:01:28.732260 IP 172.20.92.100.36326 > nodexxxx.domain: 4394+ A? youku.com.default.svc.cluster.local. (50)
17:01:28.733158 IP 172.20.92.100.49846 > nodexxxx.domain: 60286+ A? youku.com.svc.cluster.local. (45)
17:01:28.733888 IP 172.20.92.100.51933 > nodexxxx.domain: 63077+ A? youku.com.cluster.local. (41)
17:01:28.734588 IP 172.20.92.100.33401 > nodexxxx.domain: 27896+ A? youku.com. (27)
17:01:28.734758 IP nodexxxx.34138 > 192.168.x.x.domain: 27896+ A? youku.com. (27)

我们可以看到,在真正解析 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。我们之前没有提及这个项,也是希望再次能引起读者重视。

[root@xxxx-67f54c6dff-h4zxq /]# cat /etc/resolv.conf 
nameserver 10.233.0.3
search cicd.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

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个的情况:

// 对域名 a.b.c.d.ccccc 进行DNS解析请求 
[root@xxxxx-67f54c6dff-h4zxq /]# nslookup a.b.c.d.ccccc 172.22.121.65
Server: 172.22.121.65
Address: 172.22.121.65#53

** server can't find a.b.c.d.ccccc: NXDOMAIN

// 抓包数据如下:
18:08:11.013497 IP 172.20.92.100.33387 > node011094.domain: 28844+ A? a.b.c.d.ccccc.cicd.svc.cluster.local. (54)
18:08:11.014337 IP 172.20.92.100.33952 > node011094.domain: 57782+ A? a.b.c.d.ccccc.svc.cluster.local. (49)
18:08:11.015079 IP 172.20.92.100.45984 > node011094.domain: 55144+ A? a.b.c.d.ccccc.cluster.local. (45)
18:08:11.015747 IP 172.20.92.100.54589 > node011094.domain: 22860+ A? a.b.c.d.ccccc. (31)
18:08:11.015970 IP node011094.36383 > 192.168.x.x.domain: 22860+ A? a.b.c.d.ccccc. (31)

// 结论:
// 点数少于5个,先走search域,最后将其视为绝对域名进行查询

域名中点数>=5个的情况:

// 对域名 a.b.c.d.e.ccccc 进行DNS解析请求
[root@xxxxx-67f54c6dff-h4zxq /]# nslookup a.b.c.d.e.ccccc 172.22.121.65
Server: 172.22.121.65
Address: 172.22.121.65#53

** server can't find a.b.c.d.e.ccccc: NXDOMAIN

// 抓包数据如下:
18:10:14.514595 IP 172.20.92.100.34423 > node011094.domain: 61170+ A? a.b.c.d.e.ccccc. (33)
18:10:14.514856 IP node011094.58522 > 192.168.x.x.domain: 61170+ A? a.b.c.d.e.ccccc. (33)
18:10:14.515880 IP 172.20.92.100.49328 > node011094.domain: 267+ A? a.b.c.d.e.ccccc.cicd.svc.cluster.local. (56)
18:10:14.516678 IP 172.20.92.100.35651 > node011094.domain: 54181+ A? a.b.c.d.e.ccccc.svc.cluster.local. (51)
18:10:14.517356 IP 172.20.92.100.33259 > node011094.domain: 53022+ A? a.b.c.d.e.ccccc.cluster.local. (47)

// 结论:
// 点数>=5个,直接视为绝对域名进行查找,只有当查询不到的时候,才继续走 search 域。

如何优化 DNS 请求浪费的情况

优化方式1:使用全限定域名

其实最直接,最有效的优化方式,就是使用 “fully qualified name”,简单来说,使用“完全限定域名”(也叫绝对域名),你访问的域名,必须要以 “.” 为后缀,这样就会避免走 search 域进行匹配,我们抓包再试一次:

// 注意:youku.com 后边有一个点 .
nslookup youku.com. 172.22.121.65

在DNS服务容器上抓到的包如下:

16:57:07.628112 IP 172.20.92.100.36772 > nodexxxx.domain: 46851+ [1au] A? youku.com. (38)
16:57:07.628339 IP nodexxxx.47350 > 192.168.x.x.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最好)。

apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsConfig:
options:
- name: ndots
value: "1"


举报

相关推荐

0 条评论