K3S 系列文章-5G IoT 閘道器裝置 POD 訪問報錯 DNS 'i/o timeout'分析與解決

東風微鳴發表於2023-02-18

開篇

問題概述

20220606 5G IoT 閘道器裝置同時安裝 K3S Server, 但是 POD 卻無法訪問網際網路地址,檢視 CoreDNS 日誌提示如下:

...
[ERROR] plugin/errors: 2 update.traefik.io. A: read udp 10.42.0.3:38545->8.8.8.8:53: i/o timeout
[ERROR] plugin/errors: 2 update.traefik.io. AAAA: read udp 10.42.0.3:38990->8.8.8.8:53: i/o timeout
...

即 DNS 查詢 forward 到了 8.8.8.8 這個 DNS 伺服器,並且查詢超時。

從而導致需要聯網啟動的 POD 無法正常啟動,頻繁 CrashLoopBackoff, 如下圖:

需要聯網啟動的 POD CrashLoopBackoff

但是透過 Node 直接訪問,卻是可以正常訪問的,如下圖:

透過 Node 可以正常訪問

環境資訊

  1. 硬體:5G IoT 閘道器
  2. 網路:
    1. 網際網路訪問:5G 網路卡:就是一個 usb 網路卡,需要透過撥號程式啟動,程式會呼叫系統的 dhcp/dnsmasq/resolvconf 等
    2. 內網訪問:wlan 網路卡
  3. 軟體:K3S Server v1.21.7+k3s1, dnsmasq 等

分析

網路詳細配置資訊

一步一步檢查分析:

  1. /etc/resolv.conf, 發現配置是 127.0.0.1
  2. netstat 檢視 本地 53 埠確實在執行
  3. 這種情況一般都是本地啟動了 DNS 伺服器或 快取,檢視 dnsmasq 程式是否存在,確實存在
  4. dnsmasq 用的 resolv.conf 配置是 /run/dnsmasq/resolv.conf
$ cat /etc/resolv.conf
# Generated by resolvconf
nameserver 127.0.0.1

$ netstat -anpl|grep 53
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::53                   :::*                    LISTEN      -
udp        0      0 0.0.0.0:53              0.0.0.0:*                           -
udp6       0      0 :::53                   :::*                                -

$ ps -ef|grep dnsmasq
dnsmasq    912     1  0 6 月 06 ?       00:00:00 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -r /run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=...

$ systemctl status dnsmasq.service
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
   Loaded: loaded (/lib/systemd/system/dnsmasq.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2022-06-06 17:21:31 CST; 16h ago
 Main PID: 912 (dnsmasq)
    Tasks: 1 (limit: 4242)
   Memory: 1.1M
   CGroup: /system.slice/dnsmasq.service
           └─912 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -r /run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=...

6 月 06 17:21:31 orangebox-7eb3 dnsmasq[912]: started, version 2.80 cachesize 150
6 月 06 17:21:31 orangebox-7eb3 dnsmasq[912]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify dumpfile
6 月 06 17:21:31 orangebox-7eb3 dnsmasq-dhcp[912]: DHCP, IP range 192.168.51.100 -- 192.168.51.200, lease time 3d
6 月 06 17:21:31 orangebox-7eb3 dnsmasq[912]: read /etc/hosts - 8 addresses
6 月 06 17:21:31 orangebox-7eb3 dnsmasq[912]: no servers found in /run/dnsmasq/resolv.conf, will retry
6 月 06 17:21:31 orangebox-7eb3 dnsmasq[928]: Too few arguments.
6 月 06 17:21:31 orangebox-7eb3 systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
6 月 06 17:22:18 orangebox-7eb3 dnsmasq[912]: reading /run/dnsmasq/resolv.conf
6 月 06 17:22:18 orangebox-7eb3 dnsmasq[912]: using nameserver 222.66.251.8#53
6 月 06 17:22:18 orangebox-7eb3 dnsmasq[912]: using nameserver 116.236.159.8#53

$ cat /run/dnsmasq/resolv.conf
# Generated by resolvconf
nameserver 222.66.251.8
nameserver 116.236.159.8

CoreDNS 分析

這裡很奇怪,就是沒發現哪兒有配置 DNS 8.8.8.8, 但是日誌中卻顯示指向了這個 DNS:

[ERROR] plugin/errors: 2 update.traefik.io. A: read udp 10.42.0.3:38545->8.8.8.8:53: i/o timeout
[ERROR] plugin/errors: 2 update.traefik.io. AAAA: read udp 10.42.0.3:38990->8.8.8.8:53: i/o timeout

先檢視一下 CoreDNS 的配置:(K3S 的 CoreDNS 是透過 manifests 啟動的,位於:/var/lib/rancher/k3s/server/manifests/coredns.yaml 下)

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        hosts /etc/coredns/NodeHosts {
          ttl 60
          reload 15s
          fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

這裡主要有 2 個配置需要關注:

  • forward . /etc/resolv.conf
  • loop

CoreDNS 問題常用分析流程

檢查 DNS Pod 是否正常執行 - 結果:是的;

# kubectl -n kube-system get pods -l k8s-app=kube-dns
NAME                       READY   STATUS    RESTARTS   AGE
coredns-7448499f4d-pbxk6   1/1     Running   1          15h

檢查 DNS 服務是否存在正確的 cluster-ip - 結果:是的:

# kubectl -n kube-system get svc -l k8s-app=kube-dns
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.43.0.10   <none>        53/UDP,53/TCP,9153/TCP   15h

檢查是否能解析域名:
先是內部域名 - 結果:無法解析:

$ kubectl run -it --rm --restart=Never busybox --image=busybox -- nslookup kubernetes.default
Server:         10.43.0.10
Address:        10.43.0.10:53

;; connection timed out; no servers could be reached

再試外部域名 - 結果:無法解析:

$ kubectl run -it --rm --restart=Never busybox --image=busybox -- nslookup www.baidu.com
Server:         10.43.0.10
Address:        10.43.0.10:53

;; connection timed out; no servers could be reached

檢查 resolv.conf 中的 nameserver 配置 - 結果:確實是 8.8.8.8

$ kubectl run -i --restart=Never --rm test-${RANDOM} --image=ubuntu --overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"Default"}}' -- sh -c 'cat /etc/resolv.conf'
nameserver 8.8.8.8
pod "test-7517" deleted

綜上:
應該是 POD 內的 /etc/resolv.conf 被配置為 nameserver 8.8.8.8 導致了此次問題。
但是整個 Node OS 級別並沒有配置 nameserver 8.8.8.8, 所以懷疑是:Kubernetes、Kubelet、CoreDNS 或 CRI 層面有這樣的機制:在 DNS 配置異常時,自動配置其為 nameserver 8.8.8.8

那麼,要解決問題,還是要找到 DNS 配置異常。

容器網路 DNS 服務

我這裡暫時沒有查到 Kubernetes、Kubelet、CoreDNS 或 CRI 的相關 DNS 的具體證據,K3S 的 CRI 是 containerd,但是我在 Docker 的官方文件 看到了這樣的描述:

?️ Reference:
If the container cannot reach any of the IP addresses you specify, Google’s public DNS server 8.8.8.8 is added, so that your container can resolve internet domains.
如果容器無法到達您指定的任何 (DNS) IP 地址,則新增谷歌的公共 DNS 伺服器 8.8.8.8,以便您的容器可以解析 internet 域。

這裡猜測 Kubernetes、Kubelet、CoreDNS 或 CRI 可能也有類似的機制。

從這裡分析可以知道,根因還是 DNS 配置問題,CoreDNS 配置是預設的,那麼最大的可能就是 /etc/resolv.conf 配置為 nameserver 127.0.0.1 導致的此次問題。

根因分析

根因: /etc/resolv.conf 配置為 nameserver 127.0.0.1 導致的此次問題。

CoreDNS 的官方文件明確說明了這種情況:

?️ Reference:

loop | CoreDNS Docs
當 CoreDNS 日誌包含訊息Loop ... detected ...時,這意味著檢測外掛loop在其中一個上游 DNS 伺服器中檢測到無限轉發迴圈。這是一個致命錯誤,因為使用無限迴圈進行操作將消耗記憶體和 CPU,直到主機最終記憶體不足死亡。
轉發環路通常由以下原因引起:
最常見的是,CoreDNS 直接轉發請求給自己。例如,透過127.0.0.1::1127.0.0.53等環回地址
要解決此問題,請檢視 Corefile 中檢測到迴圈的區域的任何轉發。確保他們沒有轉發到一個本地地址或到另一個 DNS 伺服器,這是轉發請求回 CoreDNS。如果 forward 正在使用一個檔案(例如/etc/resolv.conf),請確保該檔案不包含本地地址。

這裡可以看到,我們的 CoreDNS 配置包含:forward . /etc/resolv.conf, 且 Node 上的 /etc/resolv.confnameserver 127.0.0.1. 和上面提到的無限轉發迴圈致命錯誤 匹配。

轉發環路通常由以下原因引起:

最常見的是,CoreDNS 將請求直接轉發到自身。例如,透過環回地址,例如 ,或 127.0.0.1::1127.0.0.53

解決辦法

?️ Reference:

loop | CoreDNS Docs
官方提供了 3 種解決辦法:

  1. kubelet 新增 --resolv-conf 直接指向"真正"的 resolv.conf, 一般是:/run/systemd/resolve/resolv.conf
  2. 禁用 Node 上的本地 DNS 快取
  3. quick dirty 辦法:修改 Corefile, 把 forward . /etc/resolv.conf 替換為 forward . 8.8.8.8 等可以訪問的 DNS 地址

針對上面的辦法,我們逐一分析下:

  1. ✔️ 可行:kubelet 新增 --resolv-conf 直接指向"真正"的 resolv.conf: 如上文所述,我們的"真正"的 resolv.conf為:/run/dnsmasq/resolv.conf
  2. ❌ 不可行:禁用 Node 上的本地 DNS 快取,因為這是基於 5G IoT 閘道器的特殊情況,5G 閘道器程式機制就是如此,要用到 dnsmasq
  3. ❌ 不可行:dirty 的辦法,並且 5G 閘道器獲取到的 DNS 是不固定,隨時變化的,所以我們也無法指定 forward . <固定的 DNS 地址>

綜上,解決辦法如下:
在 K3S service 中新增如下欄位:--resolv-conf /run/dnsmasq/resolv.conf

新增後如下:

[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=notify
EnvironmentFile=/etc/systemd/system/k3s.service.env
KillMode=process
Delegate=yes
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s server --flannel-iface wlan0 --write-kubeconfig-mode "644" --disable-cloud-controller --resolv-conf  /run/dnsmasq/resolv.conf

然後執行如下命令 reload 和 重啟:

systemctl daemon-reload
systemctl stop k3s.service
k3s-killall.sh
systemctl start k3s.service

即可恢復正常。

如果需要在安裝時解決,解決辦法如下:

  • 使用 k3s-ansible 指令碼,group_vars 額外再新增如下--resolv-conf引數:extra_server_args: '--resolv-conf /run/dnsmasq/resolv.conf'
  • 使用 k3s 官方指令碼:參考 K3s Server 配置參考 | Rancher 文件, 新增引數:--resolv-conf /run/dnsmasq/resolv.conf 或使用環境變數:K3S_RESOLV_CONF=/run/dnsmasq/resolv.conf

???

?️參考文件

三人行, 必有我師; 知識共享, 天下為公. 本文由東風微鳴技術部落格 EWhisper.cn 編寫.

相關文章