服务不通先别抓包:DNS 解析器的坑比内核网络栈多

服务不通先别抓包:DNS 解析器的坑比内核网络栈多

先说结论:ping IP 通、curl 域名不通的时候,问题十有八九不在内核网络栈,而在"名字到 IP"这段解析链上。这时候上 tcpdump 抓 SYN,方向大概率是错的------应用发出的第一个包,往往不是为了连 IP,而是为了问一句:这个域名是什么地址?

一、先看全景:一条会断很多次的链

主机侧把名字解析拆成一条链,任何一环断裂,症状都是同一句:ping IP 通,curl 域名不通。

text 复制代码
# [解析链] 应用视角自上而下;排障时自下而上逐环验证
getaddrinfo("kubernetes.default")
   | NSS(/etc/nsswitch.conf): hosts: files dns myhostname  <-- 顺序在这里定死
   +-- files --> /etc/hosts        命中即返回,不再往下问
   +-- dns  --> /etc/resolv.conf(nameserver 127.0.0.53 = systemd-resolved stub)
                    --> resolved 的真实上游(按接口/VPN/DoT 各自不同)--> UDP/TCP 53

三个要害,后面的坑都从它们长出来:

  • 顺序在 nsswitch.conf 里定死 :hosts: files dns myhostname,files 永远排在 dns 前面
  • /etc/hosts 命中即返回:一条旧记录能让后面所有环节变成摆设
  • resolv.conf 不只有 nameserver:nameserver 最多 3 个,还有 search、timeout、attempts

二、三个工具打架的那一刻,就是分叉点

DNS 排障最容易犯的错,是拿一个工具的结果否定另一个工具。先分清三条命令各走哪段链:

命令 实际走哪段链 等价于
getent hosts github.com 完整 NSS 链(files → dns) 最接近应用真实行为
dig +short github.com resolv.conf 里的 nameserver 绕过 /etc/hosts
dig @1.1.1.1 +short github.com 直连外部上游 连本机链都绕过
bash 复制代码
grep '^hosts:' /etc/nsswitch.conf          # 解析顺序:files 优先于 dns
getent hosts github.com                    # 走完整 NSS 链
dig +short github.com                      # 绕过 /etc/hosts
dig @1.1.1.1 +short github.com             # 直测外部上游
resolvectl status | head -20               # resolved 视角:每接口上游 + 全局

结果两两组合,结论直接查表:

现象 结论
getent 通、dig 不通 /etc/hosts 有旧记录,或 NSS 顺序被动过
dig 通、应用不通 应用读的根本不是这份 resolv.conf(容器/chroot)
dig 不通、dig @1.1.1.1 通 本机 stub 或它的上游配置坏了

这三行是 DNS 排障的半壁江山。剩下半壁,是下面两个具体的坑。

三、127.0.0.53:stub 的三个陷阱

Ubuntu 的 /etc/resolv.conf 通常是指向 /run/systemd/resolve/stub-resolv.conf 的软链,内容只有一行 nameserver 127.0.0.53。真正的上游配置活在 systemd-resolved 里------按接口、按 VPN、甚至 DoT,各自可以不同。

陷阱一:抓包抓不到。 应用到 127.0.0.53 的查询走 loopback,tcpdump -i eth0 port 53 只能看到 resolved 与上游的对话,你关心的应用 → stub 那段根本不在这块网卡上。

陷阱二:软链被替换。 照十年前的教程往 /etc/resolv.conf 手写静态 nameserver,从这一刻起 resolvectl 里改什么都不生效------文件不再是 stub 软链,resolved 被整体绕过。先 ls -l /etc/resolv.conf 确认指向,再谈别的。

陷阱三:跨 netns 不可达。 127.0.0.53 只在本网络命名空间的 lo 上有意义。nsenter 进容器再 dig 127.0.0.53 必然失败------不是 DNS 坏了,是你把宿主机的习惯带进了别人的 netns。

四、容器里:抄来的 resolv.conf,变了味的解析

容器有自己的 netns 和自己的 /etc/resolv.conf,但内容来源和宿主机不是一回事------这是"容器里解析行为和宿主机不一样"的第一嫌疑人。

Docker 默认抄宿主机的 resolv.conf,并过滤回环地址 (127.0.0.53 在容器 netns 里不可达,抄进去也没用),过滤完为空则回退 8.8.8.8。--dns / --add-host 可显式覆盖。

把这个行为串到内网/离线环境走一遍【从业者判断】:

text 复制代码
宿主机 resolv.conf 只有 stub 一个 nameserver
  → 容器抄到空列表(回环被过滤)→ 回退 8.8.8.8
  → 机房防火墙不放行公网 53
  → 容器里所有域名解析超时;宿主机自己一切正常(resolved 真实上游是内网 DNS)

链条上每步机制都是事实,串起来的场景是从业判断。这种故障最冤的地方在于:查网络的人盯着防火墙看一晚上,不会想到问题出在 resolv.conf 的继承规则上。

五、ndots:5:K8s 里每个外部域名都在交税

到了 K8s,解析链完全另起一套:kubelet 给每个 Pod 生成 resolv.conf,nameserver 指向 kube-dns 的 ClusterIP(通常是 service CIDR 的第 10 个地址,如 10.96.0.10),默认 dnsPolicy: ClusterFirst。

bash 复制代码
kubectl run netlab-dig --image=nicolaka/netshoot --rm -it --restart=Never -- bash
text 复制代码
# [Pod 内] cat /etc/resolv.conf
nameserver 10.96.0.10
search netlab.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

ndots:5 的规则:名字点数少于 5 时,先用 search 列表逐个拼后缀查询,全部失败才查名字本身。于是访问 www.example.com(2 个点)实际发了 4 个查询:

text 复制代码
www.example.com.netlab.svc.cluster.local.   → NXDOMAIN
www.example.com.svc.cluster.local.          → NXDOMAIN
www.example.com.cluster.local.              → NXDOMAIN
www.example.com.                            → 真正的结果

nslookup 会把每次失败的 search 尝试都打出来:

bash 复制代码
nslookup www.example.com
# ** server can't find www.example.com.netlab.svc.cluster.local: NXDOMAIN
# (另两个后缀同样 NXDOMAIN,略)
# Address: 93.184.216.34   ← 第 4 次才成功

github.com 只有 1 个点,同样最多先经历 3 次 NXDOMAIN 才轮到绝对名查询。每次外部解析多 3 次往返;CoreDNS 故障或跨数据中心上游慢时,这 3 次失败查询会把外部访问延迟放大数倍,高 QPS 下全是无效负载。

零成本的最优解是结尾加一个点 ------带点是绝对域名,解析器不再拿 search 后缀去试。想在 Pod 里亲眼看到这笔税,注意一个坑:dig 默认不应用 resolv.conf 的 search 列表 (+[no]search 默认 nosearch),直接 dig +short 两种写法都只发一次查询、看不出差异;必须加 +search 才能模拟 glibc getaddrinfo/curl 这类默认走 search 的解析路径:

bash 复制代码
# [Pod 内] dig 的 ndots 默认取自 resolv.conf(这里是 ndots:5)
time dig +search www.example.com    # 先拼 3 次 search 后缀 → 3 次 NXDOMAIN + 1 次命中 = 4 次查询
time dig +search www.example.com.   # 尾点 = 绝对名,跳过 search → 1 次查询
# 尾点 = 绝对名的规则对 getaddrinfo/curl 等应用解析同样生效:
# 应用里写 www.example.com.(带尾点),glibc 同样直接查绝对名,不多交 3 次税

四种缓解,按侵入性排:

手段 做法 适用
FQDN 加点 外部域名一律写 www.example.com. 零成本,很多基准测试里"加个点"就能显著降延迟
调低 ndots dnsConfig.options 改 ndots 外部流量多的特定 Pod
换上游 dnsPolicy: None 自定义 resolv.conf 纯外部型 workload
NodeLocal DNSCache 节点级缓存 减少 53 端口竞争与 iptables DNAT 开销

先别急着骂 ndots:5 反人类。内部短名恰恰受益于 search 机制 ------web、web.netlab 这类短名靠它才能工作;一刀切调低 ndots,伤的是集群内部调用。

六、三份 resolv.conf,三个世界

把三条链摆在一起,很多"玄学"立刻具体:

环境 resolv.conf 来源 nameserver 最高频的坑
宿主机(Ubuntu) stub 软链 → resolved 127.0.0.53 软链被替换;eth0 上抓不到查询
Docker 容器 抄宿主机 + 过滤回环 空则回退 8.8.8.8 与宿主机不一致;内网/离线全挂
K8s Pod kubelet 生成 CoreDNS ClusterIP ndots:5 放大;CoreDNS 挂了

宿主机 dig 通、容器里不通,先想想"应用读的是不是同一份 resolv.conf"------正是第二节"dig 通、应用不通"那行结论的展开。

七、排障决策路径:四步自下而上

bash 复制代码
dig @1.1.1.1 kubernetes.io                                # ① 外部上游本身通不通
dig kubernetes.io                                         # ② 本机 stub/转发链通不通
getent hosts kubernetes.io                                # ③ NSS/hosts 这层对不对
curl -v --max-time 3 http://kubernetes.io 2>&1 | head -5  # ④ 应用视角

①不通是网络/防火墙的事,别查 DNS 了;①通②不通,查本机 stub 和 resolved 上游;②通③不通,查 /etc/hosts 和 nsswitch;③通④不通,应用读的不是这份 resolv.conf,去查容器/chroot。

真要抓包,姿势也得对-------i any 能同时看到 lo 上的 stub 对话和物理网卡上的上游对话,一次抓全:

bash 复制代码
sudo tcpdump -i any -nn port 53

两个进阶武器,用在刀刃上:

  • dig +trace kubernetes.io:从根逐级迭代(根 → TLD → 权威),验证权威侧链路,区分"无答案"与"转发丢"
  • dig +search nginx:按 search 列表扩后缀再查,直接观察 ndots 行为

还有一个便宜但常被忘的判据:answer 里的 TTL 是缓存秒数。"改了记录还是旧结果",先看 TTL 再怀疑 CoreDNS------大概率是缓存没过期。

K8s 侧补一刀,确认 CoreDNS 活着、配置没被动过:

bash 复制代码
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system get cm coredns -o jsonpath='{.data.Corefile}'

八、评论区高频反驳,先答为敬

Q:把 ndots 调成 1 不就完了? 内部短名靠 search 机制才能工作,全局调低等于牺牲内部调用换外部解析。正确姿势是分治:外部占比高的 Pod 用 dnsConfig 单独调,或干脆 dnsPolicy: None。

Q:进了 K8s 直接看 CoreDNS 日志不就行了? 日志只覆盖 CoreDNS 那一段。四步法的①都不通时,那是网络/防火墙问题,CoreDNS 日志一行 relevant 的都不会有。【从业者判断】先花 30 秒定位断点在哪一环再决定去哪看,比上来翻日志快。

Q:这些是 Ubuntu 的行为,我这 RHEL 呢? nsswitch、ndots、search 是 glibc 与 K8s 的通用机制;stub 软链是 Ubuntu 的典型形态。机制同名,默认值和文件形态以你的机器实测为准------本文第一条命令就是干这个的。

九、教训

  1. ping IP 通只证明网络栈没问题:域名不通先查解析链,再考虑抓包
  2. 工具结果打架是信息,不是噪音:getent / dig / dig @upstream 各走一段链,分叉即结论
  3. resolv.conf 是"抄"出来的配置:宿主机、Docker、K8s 三份来源不同,容器里的解析行为从不保证和宿主机一致

现在就做:30 秒自检

任意一台怀疑 DNS 的机器,跑这三条:

bash 复制代码
grep '^hosts:' /etc/nsswitch.conf     # files 排在 dns 前面?
ls -l /etc/resolv.conf                # 软链还在,还是被谁改成了静态文件?
getent hosts github.com               # 通 → 链健康;不通 → 按四步法往下走

有集群的话,进 Pod 跑一次 nslookup www.example.com,数数几次 NXDOMAIN------那是你每个外部请求都在交的税。

留个话头:你上一次"ping 通、curl 域名不通",最后卡在哪一环?评论区对个暗号。

相关推荐
Thneonl1 小时前
一启动就 Exited(137):内存限制背后藏了四个参数
后端·架构
斯维赤1 小时前
从 @Tool 到 Agent 流水线:LangChain4j 进阶教学,一个库打全套
java·后端
对象存储与RustFS1 小时前
RustFS 生产部署实战:Docker / 二进制 / K8s Operator 三条路径怎么选
后端
知守观1 小时前
Spring Boot 2.1.0 停维护5年,10个依赖4个有CVE——2022年老项目的安全体检报告
spring boot·后端
SimonKing1 小时前
SSE项目`nexus-sse`持续优化,不一样的视觉效果
java·后端·程序员
斑鸠喳喳1 小时前
读写锁模式 Read-Write Lock
java·后端
Thneonl1 小时前
etcd 磁盘写满的那 6 分钟:控制面是怎么一步步瘫的
后端·架构
Tim0071 小时前
deepseek harness 导出公司报表实战
后端
明月_清风1 小时前
一个完整的数据平台是怎么工作的?从数据源到数据分析
大数据·后端·数据分析