Kubernetes - Services 和 Pods 的 DNS
Table of Contents
Kubernetes 会为 services 和 pods 创建 DNS 记录。你可以通过统一的 DNS 名字代替 IP 地访问 services。
1. 介绍
Kubernetes DNS 集群上的调度 DNS Pod 和 Service,并配置 kubelet 告诉各个容器使用 DNS 的服务 IP 来解析 DNS 名字。
集群中定义的每一个 Service 都会赋予一个 DNS 名字。默认情况下,客户端 Pod 的 DNS 搜索列表包含 Pod 的命名空间和集群默认域。
1.1. Services 的命名空间
DNS 的查询结果可能因为 Pod 空间而返回不同。未指定命名空间的 DNS 查询仅限于 Pod 所在空间。在 DNS 查询中指定命名空间来访问其他空间的服务。
比如说,一个 Pod 在 test
空间。 data
service 在 prod
空间。查询 data
不会返回结果,因为他调用的是 test
空间的 Pod。
改成 data.prod
会返回预期的结果。
DNS 查询会在 Pod 的 /etc/resolv.conf
中扩展。kubelet 会为每一个 Pod 设置这个文件。比如,查询 data
会被扩展成 data.test.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
来成功解析。
1.2. DNS 记录
哪些对象会获得 DNS 记录?
- Services
- Pods
下面各节详细介绍了受支持的记录类型和布局。其它可行的布局、名字或者查询都视为实现细节,可能会在没有警告的情况下更改。 有关最新的规范,查看 Kubernetes 基于 DNS 的服务发现。
2. Services
2.1. A/AAAA 记录
“正常的”(不是 headless)Service 会被分配一个 DNS A 或者 AAAA 记录,值取决于 service 的 IP 集合。名字的格式为:
my-svc.my-namespace.svc.cluster-domain.example
,会被解析为 Service 的集群 IP。
“Headless”(没有集群 IP )Services 也会被分配一个 DNS A 或者 AAAA 记录,值取决于 service 的 IP 集合,名字格式为:
my-svc.my-namespace.svc.cluster-domain.example
,与正常 Service 不同在于,他解析的结果是 Service 的 IP 集合。
客户端要从负责从集合中选择 IP(可能使用轮询策略)。
也就是说,正常 service DNS 解析的结果是一个 IP(由 service 的策略来控制如何从一堆的 endpoint 中选择一个),而 headless service 是把集合返回给客户端,客户端需要自己选择。
2.2. SRV 记录
SRC 记录是为正常或者 headless service 一部分的命名端口创建的。为每一个命名端口,SRV 记录记录的格式为:
my-port-name.my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
。对于一个正常的 service,解析的端口号和域名:
my-svc.my-namespace.svc.cluster-domain.example
。
对于 headless service,会被解析成多个答案,每个支持的 service 都会有一个,并且包含端口号和 Pod 域名的格式为:
auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
3. Pods
3.1. A/AAAA 记录
通常情况下一个 Pod 有如下的 DNS 解析: pod-ip-address.my-namespace.pod.cluster-domain.example
。
比如,如果一个 pod 在 default
空间下, IP 地址是 172.17.0.3,集群域名为 cluster.local
,那么 Pod 的 DNS 名为
172-17-0-3.default.pod.cluster.local
。
3.2. Pod 的主机名和子域字段
当 Pod 被创建的时候, hostname
由 Pod 的 metadata.name
的值来决定。
Pod spec 中有一个可选的 hostname
字段,用于指定 Pod 的主机名。指定之后,他的优先级要比 Pod 的名字要高。
Pod 还有一个可选的 subdomain
字段,用来指定他的子域。比如说, Pod hostname
是 foo
, subdomain
是 bar
,
所在空间为 my-namespace
,他的 FQDN 为: foo.bar.my-namespace.svc.cluster-domain.example
。
如果是 headless service,Pod 的域名会带上序号。比如 busybox-1
busybox-2
。
3.3. Pod 的 setHostnameAsFQDN 字段
FEATURE STATE: Kubernetes v1.22 [stable]
TODO
3.4. Pod 的 DNS 策略
可以对每一个 Pod 指定 DNS 策略。目前 Kubernetes 支持如下的 pod-specific DNS 策略。策略由 Pod 的 dnsPolicy
字段来指定。
default
从 Pod 运行节点继承解析配置,从 关联的讨论 查看更多;ClusterFirst
与集群配置不匹配的 DNS 查询,比如www.kubernetes.io
会转发到节点继承的上游 nameserver。 集群管理员可能配置了额外的存根域和上游 DNS 服务器。关于 DNS 查询如何处理这些场景,查看 相关讨论;ClusterFirstWithHostNet
对于使用 hostNetwork 的运行中 Pod,你需要显式的指定 DNS 策略为ClusterFirstWithHostNet
;None
他允许忽略从 Kubernetes 环境中的 DNS 设置。所有的 DNS 设置都应该使用 Pod Spec 中的dnsConfig
来提供;
举例:
apiVersion: v1 kind: Service metadata: name: default-subdomain spec: selector: name: busybox clusterIP: None ports: - name: foo # Actually, no port is needed. 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: busybox
如果在于 Pod 相同的命名空间中存在 headless 服务并且与子域具有相同的名称。集群的 DNS 服务器也为 Pod 的完全限定域名返回 A
或 AAAA 记录。比如说,给定的 Pod 的 hostname 设置成为 busybox-1
子域设置成为 default-subdomain
,相同的空间下 headless
service 命名为 default-subdomain
,Pod 会看到自己的 FQDN 是 busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
DNS 以该名称提供 A 或者 AAAA 记录,指向 Pod 的 IP。 busybox1
和 busybox2
有独立的 A 或者 AAAA 记录。
Endpoints 对象可以制定任何 endpoint 地址的 hostname
,跟他的 IP 一起。
注意: 由于 A 和 AAAA 不会为 Pod 名字创建, 所以在创建 A 或者 AAAA 记录时
hostname
是必要的。Pod 没有hostname
但是有subdomain
会只会为 headless service 创建 A 或者 AAAA 记录(default-subdomain.my-namespace.svc.cluster-domain.example
), 指向 Pod 的 IP 地址。此外,除非在 Service 上设置了publishNotReadyAddresses=True
,否则 Pod 需要就绪之后才能有记录。
3.5. Pod 的 DNS 配置
FEATURE STATE: Kubernetes v1.14 [stable]
Pod 的 DNS 配置允许用户更多的控制 Pod 的 DNS 设置。
dnsConfig
字段是可选的,他可以和任何的 dnsPolicy
设置。然而,当 Pod 的 dnsPolicy
被设置为 None
, dnsConfig
字段必须要指定。
用户可以在 dnsConfig
字段上指定以下属性:
nameservers
: Pod 使用的 DNS 服务器列表。最多可以指定 3 个 IP 地址。当 Pod 的dnsPolicy
设置为None
,至少需要指定一个 IP 地址,否则属性是可选的。 列出的服务器将与从指定 DNS 策略生成的基本名称服务器合并,并删除重复地址;searches
: 用于在 Pod 中查找主机名的 DNS 搜索域列表。这个属性是可选的。当被指定时,提供的列表会被与基础的搜索域合并。重复的域名会被删除。Kubernetes 最多允许 6 个搜索域;options
: 一个可选的对象列表,其中每个对象可能有一个name
属性(必需)和一个value
属性(可选)。同样会跟已有的 option 合并,重复的会被移除;
以下是有自定义 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: edns0
当上面的 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 设置,搜索路径和 nameserver 可能会被设置为:
kubectl exec -it dns-example -- cat /etc/resolv.conf
输出类似:
nameserver fd00:79:30::a search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.exampleoptions ndots:5
3.6. 扩展 DNS 配置
FEATURE STATE: Kubernetes 1.22 [alpha]
默认情况下,Pod 的 DNS 配置,Kubernetes 允许最多 6 个搜索域并且最多 256 个字符。
如果为 kube-apiserver 和 kubelet 开启了 ExpandedDNSConfig
特性,它会允许最多 32 个搜索域和最多 2048 个字符。
3.7. 功能可用性
Pod 的 DNS 配置和 DNS 策略 None
可用性如下所示:
K8s version | 功能支持 |
---|---|
1.14 | Stable |
1.10 | Beta(on by default) |
1.9 | Alpha |
4. 实践
4.1. /etc/resolv.conf
中的 ndots
如上,每个集群中 Pod 的 /etc/resolv.conf
中的内容大概为
nameserver 10.32.0.10 search <namespace>.svc.cluster.local svc.cluster.local cluster.local options ndots:5
其中 nameserver 和 search 分别表示 DNS 地址和搜索域。
比较关键的是 ndots
的含义。他表示请求域名的中的 .
的数量,当域名的 .
数量小于 5
时,会从 search 从依次填充后
缀再从 DNS 服务器中查找。当大于等于 5 时,会直接对域名本身进行解析,然后再在搜索域列表中搜索。什么意思呢?
假设你要访问 api.github.com
,点数小于 5, 那他的查找顺序为
api.github.com.<namespace>.svc.cluster.local
api.github.com.svc.cluster.local
api.github.com.cluster.local
api.github.com
在这种情况下,访问外部域名会有大量的无效查询导致 dnslookup 变慢,也会增加 CoreDNS 的压力。
优化的办法是改小 ndots 的值, ndots:2
可能是一个好的选择。为什么呢?search 域在 K8s 集群中是为了做服务发现,上面文章内
容里有说明原理。在实际使用集群时,会用到的基本就两种情况:
- 本空间短域名访问
svc:port
- 跨空间短域名访问
svc.namespace:port
所以 ndots 为 2 时,足够覆盖这两种场景。如何实现自动修改呢?
- pod 的 yaml 中
dnsConfig
可以修改 resolv 的内容; - K8s 提供了 adminssion controller,用于在 Pod 的创建中做一些额外的事情。可以利用 adminession controller 的机制, 来自动化完成 dns 的注入。具体可见 自定义 Kubernetes 准入控制器。