一次 k3s 拉镜像失败的 DNS Debug:强制使用 Go Resolver 修复

背景

在一台 rancher 主机上运行 k3s,安装 cert-manager 时遇到网络问题。

环境大致如下:

Kubernetes: k3s
Runtime: k3s 内置 containerd
节点 IP: 10.10.5.30
系统 DNS: 127.0.0.53,systemd-resolved
上游 DNS: 10.10.0.1
网络出口: 路由器侧 TUN / fake-ip 代理

现象是,直接curl访问网站是正常的

curl https://github.com/...

但拉取构建脚本失败

root@rancher:~# kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml
Unable to connect to the server: dial tcp: lookup github.com: Try again
r

拉取镜像失败

root@rancher:~#  k3s crictl pull quay.io/jetstack/cert-manager-controller:v1.20.2
E0618 04:47:52.374034    3136 log.go:32] "PullImage from image service failed" err="rpc error: code = Unknown desc = failed to pull and unpack image \"quay.io/jetstack/cert-manager-controller:v1.20.2\": failed to resolve reference \"quay.io/jetstack/cert-manager-controller:v1.20.2\": failed to do request: Head \"https://quay.io/v2/jetstack/cert-manager-controller/manifests/v1.20.2\": dial tcp: lookup quay.io: Try again" image="quay.io/jetstack/cert-manager-controller:v1.20.2"
FATA[0005] pulling image: failed to pull and unpack image "quay.io/jetstack/cert-manager-controller:v1.20.2": failed to resolve reference "quay.io/jetstack/cert-manager-controller:v1.20.2": failed to do request: Head "https://quay.io/v2/jetstack/cert-manager-controller/manifests/v1.20.2": dial tcp: lookup quay.io: Try again

问题现象

手动拉镜像时报错:

failed to pull and unpack image "quay.io/jetstack/cert-manager-controller:v1.20.2":
failed to resolve reference "quay.io/jetstack/cert-manager-controller:v1.20.2":
failed to do request:
Head "https://quay.io/v2/jetstack/cert-manager-controller/manifests/v1.20.2":
dial tcp: lookup quay.io: Try again

同时 k3s 日志里,CoreDNS Pod 也无法启动,因为 pause 镜像拉不下来:

failed to get sandbox image "registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-pause:3.6":
dial tcp: lookup registry.cn-hangzhou.aliyuncs.com: Try again

结论:问题发生在 DNS lookup 阶段,不是镜像仓库、TLS、认证或 cert-manager 配置问题。


抓包分析

抓 DNS 包:

sudo tcpdump -ni any -vvv port 53

然后执行:

sudo k3s crictl pull quay.io/jetstack/cert-manager-controller:v1.20.2

抓包结果类似:

lo    In  IP 127.0.0.1.33481 > 127.0.0.53.53: AAAA? quay.io.
ens18 Out IP 10.10.5.30.55586 > 10.10.0.1.53: AAAA? quay.io.
ens18 In  IP 10.10.0.1.53 > 10.10.5.30.55586: 0/1/0
lo    In  IP 127.0.0.53.53 > 127.0.0.1.33481: ServFail

在这里插入图片描述

链路是:

k3s/containerd
  -> 127.0.0.53
  -> systemd-resolved
  -> 10.10.0.1
  -> AAAA? quay.io
  -> SERVFAIL
  -> containerd 报 lookup quay.io: Try again

关键点:只看到了 AAAA 查询,没有看到 A 查询。

对比 Go Resolver 和 cgo Resolver

kubectl / k3s / containerd 都是 Go 生态程序。Go 有两类 DNS resolver:

netdns=go   使用 Go 纯 DNS resolver
netdns=cgo  使用 libc/getaddrinfo

测试 Go resolver:

GODEBUG=netdns=go+2 kubectl apply -f https://gith1ub.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml

抓包结果:

A? gith1ub.com
AAAA? gith1ub.com

测试 cgo resolver:

GODEBUG=netdns=cgo+2 kubectl apply -f https://gith1ub.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml

抓包结果:

AAAA? gith1ub.com

结论:

Go 纯 resolver 正常,会查 A 和 AAAA
cgo/libc resolver 异常,只查 AAAA

因此问题集中在:

cgo / libc / getaddrinfo / systemd-resolved 这条 DNS 解析链路

根因判断

k3s/containerd 默认 DNS 解析路径触发了 cgo/libc 行为:

k3s/containerd
  -> libc/getaddrinfo
  -> /etc/resolv.conf
  -> 127.0.0.53
  -> systemd-resolved
  -> 10.10.0.1

这条链路只发起 AAAA 查询。

而 AAAA 查询返回 SERVFAIL 后,containerd 直接判定 DNS lookup 失败,没有继续正常使用 IPv4 A 记录。

所以最终表现为:

lookup quay.io: Try again

修复方案:强制 k3s 使用 Go DNS resolver

创建 systemd override:

sudo mkdir -p /etc/systemd/system/k3s.service.d

sudo tee /etc/systemd/system/k3s.service.d/dns.conf >/dev/null <<'EOF'
[Service]
Environment="GODEBUG=netdns=go"
EOF

重新加载 systemd:

sudo systemctl daemon-reload

重启 k3s:

sudo systemctl restart k3s

确认环境变量生效:

sudo systemctl show k3s --property=Environment

应该看到:

Environment=GODEBUG=netdns=go

测试镜像拉取:

sudo k3s crictl pull registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-pause:3.6
sudo k3s crictl pull quay.io/jetstack/cert-manager-controller:v1.20.2

同时抓包验证:

sudo tcpdump -ni any -vvv port 53

预期可以看到:

A? quay.io
AAAA? quay.io

不再是只查 AAAA。


最终结论

这次问题不是路由器代理不可用,也不是 k3s 不支持 fake-ip,而是:

k3s/containerd 的 DNS resolver 走到 cgo/libc 路径时,只发起 AAAA 查询;
AAAA 查询经过 127.0.0.53 -> 10.10.0.1 后返回 SERVFAIL;
containerd 因此报 lookup Try again,镜像拉取失败。

最终修复:

给 k3s systemd 服务设置 GODEBUG=netdns=go
强制 k3s/containerd 使用 Go 纯 DNS resolver

排查命令汇总

# 查看 k3s 日志
sudo journalctl -u k3s -f

# 手动拉镜像
sudo k3s crictl pull quay.io/jetstack/cert-manager-controller:v1.20.2

# 抓 DNS 包
sudo tcpdump -ni any -vvv port 53

# 查看系统 DNS
cat /etc/resolv.conf
resolvectl status

# 查看 NSS 配置
grep '^hosts:' /etc/nsswitch.conf

# 查看 gai 配置
grep -v '^#' /etc/gai.conf | grep -v '^$'

# 测试 Go resolver
GODEBUG=netdns=go+2 kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml

# 测试 cgo resolver
GODEBUG=netdns=cgo+2 kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml

# 给 k3s 设置 Go resolver
sudo mkdir -p /etc/systemd/system/k3s.service.d

sudo tee /etc/systemd/system/k3s.service.d/dns.conf >/dev/null <<'EOF'
[Service]
Environment="GODEBUG=netdns=go"
EOF

sudo systemctl daemon-reload
sudo systemctl restart k3s

# 验证环境变量
sudo systemctl show k3s --property=Environment

经验教训

  1. curl 正常,不代表 k3s/containerd 正常。
  2. docker pull 正常,不代表 k3s 内置 containerd 正常。
  3. lookup xxx: Try again 优先排查 DNS。
  4. 禁用 IPv6 不等于禁止 AAAA 查询。
  5. Go 程序的 netdns=gonetdns=cgo 行为可能完全不同。
  6. 抓包比猜配置有效。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐