TT Lab
开始
学习 学习路径 课程

CKA — Kubernetes 管理员

CoreDNS — 名字如何变成 IP

在 TT Lab 中继续学习

一句话总结

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 结尾的名称才会去那台服务器。

调试流程——从前往后

按顺序转述官方流程如下。

  1. 启动 dnsutils Pod(文档中的 registry.k8s.io/e2e-test-images/agnhost 镜像),执行 nslookup kubernetes.default。如果有回答,说明 DNS 正常。
  2. 如果失败,先查看该 Pod 的 /etc/resolv.conf。确认 search 路径和 nameserver 是否是上面的样子。
  3. 在 kube-system 中用 -l k8s-app=kube-dns 查看 CoreDNS Pod 是否 Running。如果没有,说明插件没有被部署。
  4. 用同一个标签查看日志。正常的日志中会打印 plugin/reload: Running configuration。
  5. 查看 kube-dns Service 是否存在,以及带有 kubernetes.io/service-name=kube-dns 标签的 EndpointSlice 中是否有地址。如果端点为空,说明 Service 虽然存在,但后面没有 Pod。
  6. 想知道查询是否到达了 CoreDNS,就在 Corefile 中加入 log 插件,看日志中是否打印出查询行。
  7. 如果日志中有 SERVFAIL,就怀疑权限。CoreDNS 必须能够 list 和 watch Service 与 EndpointSlice,如果 system:coredns ClusterRole 中缺少 discovery.k8s.io 的 endpointslices,就会出现这个错误。
  8. 最后确认命名空间。其他命名空间的 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 日志所指向的原因。与其背命令,不如能回答“缺了哪一环”。