CoreDNS — 名字如何变成 IP
一句话总结
Pod 查询名称时,查询会发往 kubelet 写入的 /etc/resolv.conf 中的 nameserver,也就是 kube-dns Service 的 ClusterIP。它后面是 CoreDNS Pod,而 CoreDNS 要回答什么、怎么回答,由 coredns ConfigMap 中的 Corefile 决定。解析受阻时,要从前往后按顺序确认这条链。
为什么需要它
Service 的 ClusterIP 删掉再重建就会变化。Pod IP 变化得更频繁。如果应用把 IP 写死在配置里,每次重新部署都会出问题,所以 Kubernetes 把名称作为稳定的地址,并在集群内提供了从名称到 IP 的通路。这条通路就是集群 DNS,目前的实现是 CoreDNS。
问题在于,这条通路由多个部分组成。Pod 的 resolv.conf、kube-dns Service、EndpointSlice、CoreDNS Pod、Corefile、上游 DNS——只要其中任何一环出了偏差,就会出现“找不到名称”这同一个症状。症状相同,如果想靠猜来定位原因,时间就全耗光了。这就是官方文档 调试 DNS 解析 规定顺序的原因。
工作原理
Pod 一侧——resolv.conf 与搜索顺序
根据 Service 与 Pod 的 DNS 文档,kubelet 会为每个 Pod 写入 resolv.conf。样子如下。
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
search 这一行决定了名称解析的顺序。test 命名空间中的 Pod 查询 data 时,首先会尝试展开为 data.test.svc.cluster.local。因此,同一命名空间中的 Service 可以用短名称找到,而其他命名空间(prod)中的 Service 则必须像 data.prod 这样带上命名空间。按文档的说法,“未指定命名空间的 DNS 查询,限定在 Pod 所在的命名空间”。ndots:5 表示名称中的点少于五个时,要先附加搜索域,所以为了查找一个外部域名,要先付出敲击三个集群域名的代价。
使用哪个 nameserver,由 Pod spec 的 dnsPolicy 决定。这是个容易混淆的部分,以至于文档专门写了“Default 不是默认值”。
| dnsPolicy | 行为 |
|---|---|
ClusterFirst |
未指定时的默认值。非集群域名的查询转发给上游 |
ClusterFirstWithHostNet |
hostNetwork: true 的 Pod 要使用集群 DNS,必须明确指定它 |
Default |
原样继承节点的名称解析设置 |
None |
忽略 Kubernetes 的设置,全部通过 dnsConfig 自行指定 |
服务器一侧——kube-dns Service 与 Corefile
DNS 服务器是 kube-system 命名空间中的 CoreDNS Pod,前面有一个名为 kube-dns 的 ClusterIP Service(53/UDP、53/TCP)。名称不叫 coredns 而叫 kube-dns,是为了与原来的 kube-dns 向后兼容,Pod 标签 k8s-app=kube-dns 也是出于同样的原因保留下来。调试时用这个标签来查找 Pod。
CoreDNS 是由插件组合而成的服务器,配置文件就是 Corefile。自定义 DNS 服务 文档展示的默认 Corefile 如下。
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
逐行了解其含义,就能把日志和症状联系起来。errors 把错误记录到 stdout;health 通过 8080 端口报告状态,lameduck 5s 会在终止前的 5 秒内标记为不健康以撤走流量。ready 在所有插件都准备好时,从 8181 端口返回 200。kubernetes 插件依据 Service 和 Pod IP 回答集群域名的查询,ttl 30 是响应的 TTL(按文档,默认 5 秒,最大 3600 秒)。forward . /etc/resolv.conf 把集群域名之外的查询转交给 CoreDNS Pod 的 resolv.conf 中写的上游。cache 30 是响应缓存,loop 是检测到转发循环时让进程停止的安全装置,reload 是 Corefile 变化时自动重新读取的插件,loadbalance 是打乱 A、AAAA、MX 记录顺序的轮询。修改 ConfigMap 之后,文档建议预留约 2 分钟等待其生效。
只把特定域名发往其他服务器的 stub domain 也放在同一个文件里。如文档中的例子,在 consul.local:53 服务器块里放入 forward . 10.150.0.1,只有以 .consul.local 结尾的名称才会去那台服务器。
调试流程——从前往后
按顺序转述官方流程如下。
- 启动
dnsutilsPod(文档中的registry.k8s.io/e2e-test-images/agnhost镜像),执行nslookup kubernetes.default。如果有回答,说明 DNS 正常。 - 如果失败,先查看该 Pod 的
/etc/resolv.conf。确认 search 路径和 nameserver 是否是上面的样子。 - 在
kube-system中用-l k8s-app=kube-dns查看 CoreDNS Pod 是否 Running。如果没有,说明插件没有被部署。 - 用同一个标签查看日志。正常的日志中会打印
plugin/reload: Running configuration。 - 查看
kube-dnsService 是否存在,以及带有kubernetes.io/service-name=kube-dns标签的 EndpointSlice 中是否有地址。如果端点为空,说明 Service 虽然存在,但后面没有 Pod。 - 想知道查询是否到达了 CoreDNS,就在 Corefile 中加入
log插件,看日志中是否打印出查询行。 - 如果日志中有
SERVFAIL,就怀疑权限。CoreDNS 必须能够 list 和 watch Service 与 EndpointSlice,如果system:corednsClusterRole 中缺少discovery.k8s.io的endpointslices,就会出现这个错误。 - 最后确认命名空间。其他命名空间的 Service 必须以
<service>.<namespace>来查询。
文档中的“已知问题”也值得记住。像 Ubuntu 这样使用 systemd-resolved 的发行版,会把 /etc/resolv.conf 换成存根文件,可能产生转发循环,这时要用 kubelet 的 --resolv-conf 指向 /run/systemd/resolve/resolv.conf(kubeadm 会自动检测这一点)。另外,glibc 最多只读取 3 个 nameserver,而 Kubernetes 要占用其中一个。
在现场相遇的样子
日志里满是 SERVFAIL,Pod 却完好无损。文档举的例子正是这个——Pod 是 Running,Service 也存在,但响应是 SERVFAIL,多半是 CoreDNS 没有读取 EndpointSlice 的权限。用 kubectl describe clusterrole system:coredns 查看 endpointslices.discovery.k8s.io 上是否有 list 和 watch,没有的话就修改 ClusterRole。
只有 hostNetwork 的 Pod 找不到 Service 名称。使用节点网络的 Pod 如果不指定 dnsPolicy,ClusterFirst 会表现得像 Default,使用节点的 resolv.conf。这就是文档要求明确指定 ClusterFirstWithHostNet 的原因,部署日志收集器或节点代理时经常漏掉这一点。
下一项测验要确认什么
测验会考查 base 与 overlay 的关系、两种补丁方式、kubectl top 失败的原因以及 metrics-server 读取的端点、dnsPolicy 的默认值,以及 SERVFAIL 日志所指向的原因。与其背命令,不如能回答“缺了哪一环”。