记一次诡异的 K3s DNS 报错排查:当 K3s 遇上飞牛 NAS
Contents
在 Kubernetes 的日常排障中,网络问题往往最耗时间。这次我在飞牛 NAS 上用 Docker 跑了一个 K3s(单节点 master),准备给 CI 跑 argocd-diff-preview。结果 Argo CD 部署完成后一直报 DNS 解析失败,日志里看起来像“在 Pod 里用 localhost 查 DNS”,非常反直觉。
最后定位到的根因也很“隐形”:宿主机目录的 Default ACL 影响了 containerd 为 Pod 生成并挂载的 resolv.conf,导致非 root 容器无法读取 /etc/resolv.conf,从而触发了 libc resolver 的兜底逻辑,盲目向 127.0.0.1/[::1]:53 发起查询。
背景
我本地用 Argo CD 管理和发布应用。这次想引入 argocd-diff-preview 做 CI 检查,把 diff 结果作为 GitHub 评论回写到 PR。
效果大概是这样:

为了跑这类任务,我需要一个轻量的 Kubernetes 集群。正好手头有飞牛 NAS,就在上面用 Docker 起了一个 K3s server(禁用 servicelb),docker-compose 如下:
1 | version: "3.8" |
然后在 CI 里执行 argocd-diff-preview 时就开始报错:
1 | argocd-diff-preview \ |
现象:Pod 里用 [::1]:53 查 DNS
Argo CD 无法正常同步应用。查看 argocd-server 日志,发现大量类似报错:
1 | Failed to resync revoked tokens... dial tcp: lookup argocd-redis on [::1]:53: read udp [::1]:34952->[::1]:53: read: connection refused |
第一眼看到这个日志,我的反应是:为什么 Pod 内部会用 IPv6 的本地环回地址([::1])去查 DNS?
按常规思路我先检查 CoreDNS,但 CoreDNS 运行完全正常。这就更怪了:dnsPolicy: ClusterFirst 的 Pod,理论上应该把查询发给 CoreDNS(例如 10.43.0.10),而不是对着本地 53 端口重试。
排查:从 /etc/resolv.conf 入手
既然外部看不出问题,就直接进入容器确认 DNS 配置:
1 | kubectl -n argocd exec -ti argocd-server-fbbdf5d4d-c8z5q -- /bin/bash |
然后查看 /etc/resolv.conf:
1 | argocd@argocd-server:~$ cat /etc/resolv.conf |
到这里基本就能解释日志了:应用进程读不到 /etc/resolv.conf,就拿不到 nameserver 配置。
在这种情况下,libc resolver 会走兜底逻辑,直接尝试向本地 localhost(127.0.0.1 和 [::1])发 DNS 查询;如果本地没有 DNS 服务监听 53 端口,就会出现 connection refused。
所以这并不是 IPv6 本身的问题,而是容器“看不见”正确的 nameserver。
根因:文件权限末尾的 “+”(ACL)
继续用 ls -l 看权限:
1 | argocd@argocd-server:~$ ls -alh /etc/resolv.conf |
我把这里的内容帖给 AI 之后, 它就提到了 ACL 问题:
注意权限末尾的 +:这表示该文件启用了 ACL(Access Control List)。即使基础权限看起来没问题,ACL 也可能额外收紧“其他用户(other)”的权限。由于 Argo CD 默认以非 root 用户(例如 UID 999)运行,它就会被挡在门外。
/etc/resolv.conf 不是镜像里的文件,而是运行时挂载进来的。为了确认它在宿主机上对应哪个文件,我回到宿主机用 crictl inspect 查了容器挂载信息:
1 | ~ # crictl ps # 找到 argocd-server 容器的 ID |
由于我的 K3s 数据目录在 /vol1/1000/K3sData(NAS 挂载盘)上,顺着这个路径找过去,再用 getfacl 查看 ACL:
1 | root@Host:~# getfacl /vol1/.../sandboxes/.../resolv.conf |
真相大白:/vol1 这类 NAS 挂载盘默认可能带有严格的 Default ACL。当 containerd 在这个目录下为 Pod 动态生成 resolv.conf 时,会继承父目录的 Default ACL,把 other 权限收紧为 ---。Pod 里的非 root 进程就无法读取 /etc/resolv.conf,从而触发“查 localhost DNS”的兜底行为。
解决方案:清理 Default ACL 继承规则
我最终没有改 K3s 配置,也没有去关闭整块盘的 ACL(这通常会影响 Samba 等服务)。更稳妥的做法是:只清理 K3s/containerd 工作目录上导致继承的 Default ACL 规则。
在宿主机执行:
1 | # 1. 清除沙盒目录上导致问题的 Default ACL 遗传规则 |
做完后把 Argo CD 的 Pod 删掉让它重建。新的 Pod 启动后,containerd 生成的 resolv.conf 恢复为正常的可读权限;再进入容器执行 cat /etc/resolv.conf 也不再报 Permission denied。至此,Argo CD 立刻恢复正常,应用也能正常 Sync。
自动持久化:使用 tmpfiles.d (进阶方案)
如果你担心 NAS 系统在重启或通过 Web UI 操作后会重新“遗传”错误的 ACL 规则,可以使用 systemd 的 tmpfiles.d 机制。这是一种比 Crontab 脚本更优雅的“声明式”配置。
在宿主机创建配置文件 /etc/tmpfiles.d/k3s-nas-acl.conf:
1 | # 确保目录及其子目录基础权限为 0755 |
配置完成后,可以执行以下命令立即生效(无需重启):
1 | sudo systemd-tmpfiles --create /etc/tmpfiles.d/k3s-nas-acl.conf |
系统会扫描该路径,并按照配置自动纠正所有不符合要求的 ACL 项。
总结与避坑指南
- 别被
[::1]:53误导:Pod 内查 localhost DNS,优先检查是否能读取/etc/resolv.conf。 - 留意非 root 容器:越来越多组件默认非 root 运行,宿主机生成/挂载的文件必须保证“other 可读”或至少对应用用户可读。
- 小心 NAS 默认 ACL:在 NAS 挂载盘上跑 K3s/Docker 时,
ls -l权限末尾的+往往是权限问题的信号;遇到诡异现象优先用getfacl定位。 - AI 真的太强大了, 问对合适的问题, 能让调试更高效。
希望这次记录能给你一个更快的排查切入点。