node-local-dns 缓存启动失败: 检测到配置循环

部署 Docker运行CoreDNS 结合 Kubernetes集群 node-local-dns 缓存 ,意外发现有个别节点出现 nodelocaldns 这个 DaemonSet 启动失败:

nodelocaldns-hrgjg  0/1  CrashLoopBackOff  0  3h4m  172.21.45.19  i-2ze3cdvwilmetl7kwybi  <none>  <none>

检查日志:

nodelocaldns 日志检查
kubectl -n kube-system logs nodelocaldns-hrgjg

输出显示,CoreDNS检测到配置循环,所以无法启动: (忽略 ERROR ,这个不影响启动 )

nodelocaldns 日志显示存在配置循环而无法启动
2023/05/31 06:54:07 [INFO] Using Corefile /etc/coredns/Corefile
2023/05/31 06:54:07 [ERROR] Failed to read node-cache coreFile /etc/coredns/Corefile.base - open /etc/coredns/Corefile.base: no such file or directory
2023/05/31 06:54:07 [ERROR] Failed to sync kube-dns config directory /etc/kube-dns, err: lstat /etc/kube-dns: no such file or directory
cluster.local.:53 on 169.254.25.10
in-addr.arpa.:53 on 169.254.25.10
ip6.arpa.:53 on 169.254.25.10
.:53 on 169.254.25.10
[INFO] plugin/reload: Running configuration MD5 = 819df7a9fcc74dbfb7642c4c4f8b46b6
CoreDNS-1.6.7
linux/amd64, go1.11.13,
[FATAL] plugin/loop: Loop (169.254.25.10:51877 -> 169.254.25.10:53) detected for zone ".", see https://coredns.io/plugins/loop#troubleshooting. Query: "HINFO 6045881678422769933.6947314251045742774."

转发循环

根据提示,在CoreDNS官方文档 CoreDNS Troubleshooting 对于这种常见错误有一个详细说明:

  • CoreDNS 日志中包含 Loop ... detected ... 就表示 循环检测插件 在其中一个上游DNS服务器中检测到无限转发循环。也就是说CoreDNS发现upstream DNS就是它自己

  • 无限转发循环是一个致命错误,会导致消耗内存和CPU,直到主机因内存不足挂掉

常见的转发循环原因:

  • CoreDNS将请求直接转发给自己,例如回环地址(127.0.0.1, ::1 或 127.0.0.53)

  • CoreDNS将请求转发给上游服务器,但是上游服务器又将请求转发回CoreDNS

排查思路是检查整个DNS查询链路,确保没有出现循环;此外,要仔细检查 /etc/resolv.conf 确保不包含本地地址

kubernetes集群的转发循环

当Kubernetes中的CoreDNS Pod检测到转发循环,CoreDNS Pod就会出现 CrashLoopBackOff 。这是因为每次CoreDNS检测到循环并退出时,Kubernetes就会尝试重启Pod。

Kubernetes集群中转发循环的常见原因是与主机节点上本地DNS缓存交互(例如 systemd-resolved )。在一些配置中, systemd-reolved 会将回环地址 127.0.0.53 作为名字服务器配置在 /etc/resolv.conf 中。Kubernetes通过Kubelet默认情况下会将这个 /etc/resolv.conf 文件传递给所有使用默认 dnsPolicy 的Pod,这样就会导致Pod无法进行DNS查询(也报错 CoreDNS Pod)。此时CoreDNS使用这个 /etc/resolv.conf 作为转发请求的上游服务器,由于这是一个回环地址,CoreDNS最终会把请求转发给自己。

解决方法如下

方法1

  • 将以下内容添加到 kubelet 的配置yaml:

    resolvConf: <path-to-your-real-resolv-conf-file>
    
  • 也可以在 --resolv-conf 运行参数中传递给kubelet

此时传递的 真实resolv.conf 会被 kubelet 复制到pods内部进行替换

对于 systemd-resolved ,通常 /run/systemd/resolv/resolv.conf 是真实的 resolv.conf (具体取决于发行版)

方法2

  • 关闭host(物理)主机的local DNS cache,这样就会恢复 /etc/resolv.conf 的真实DNS服务器配置

方法3

  • 修改 CoreDNSCorefile ,将配置:

    forward . /etc/resolv.conf
    

修改成真实的上游DNS服务器IP地址,例如:

forward . 8.8.8.8

注意,这个方法只修复CoreDNS问题,而kubelet则继续使用 resolv.conf 来转发到默认dnsPolicy Pods,这样依然存在DNS解析问题

排查

CoreDNS 启动时会从主机复制 /etc/resolv.conf 到容器内部作为配置,同样 nodelocaldns 也采用了这个机制。根据上述 CoreDNS Troubleshooting 首先排查物理主机 /etc/rsolv.conf ,对比发现这个异常节点的 resolv.conf 特殊

存在异常物理主机 /etc/resolv.conf
options timeout:2 attempts:3 rotate single-request-reopen
; generated by /usr/sbin/dhclient-script
nameserver 169.254.25.10

上述配置中 169.254.25.10 是一个特殊的IP地址,实际上是 localip (169.254.0.0/16 is link local address 见 wikipeidia: Local-link address ), k8s-dns-node-cache 使用这个IP地址作为服务进程的本地IP地址。所以你在每个 nodelocaldns ds 运行服务器上检查进程可以看到类似:

root     22126  0.1  0.0 142376 18000 ?        Ssl  11:33   0:29 /node-cache -localip 169.254.25.10 -conf /etc/coredns/Corefile -upstreamsvc coredns

这里不知道谁在物理主机 /etc/resolv.conf 中添加了这个IP作为域名解析服务器,这导致配置映射进容器后,CoreDNS(nodelocaldns)检查发现upstream DNS server的IP就是自己local IP,形成了DNS解析回环。这样CoreDNS(nodelocaldns)就会拒绝启动。

正确的物理主机 /etc/resolv.conf
nameserver 192.168.2.136
nameserver 192.168.2.138

参考