一、问题来了:Pod 突然不能解析域名了

你高高兴兴部署了一个应用,Pod 启动正常,日志却一直报错“No route to host”或者“connection timed out”。用 kubectl exec 进 Pod 里 ping www.baidu.com 也超时,但 ping 别的 Pod IP 却通。这种情况十有八九是 DNS 解析出了问题。在 Kubernetes 里,Pod 默认使用 kube-dns(或者 CoreDNS)来解析服务名和外部域名。一旦解析超时,你的应用就会变成“瞎子”,找不到其他服务和外部地址。今天我们就从 Pod 里的 resolv.conf 开始,一路追踪到 kube-dns 的 Service 网络路径,手把手教你定位问题。

二、第一步:检查 Pod 里的 resolv.conf

2.1 看看配置到底对不对

每个 Pod 启动时,Kubernetes 会在容器里自动生成一个 /etc/resolv.conf,里面指定了 DNS 服务器地址、搜索域和选项。最常见的错误是 nameserver 指向了错误的 IP,或者 search 域太冗长导致解析慢。我们来验证一下。

先随便找个出问题的 Pod:

# 假设 namespace 是 myapp,Pod 名字是 my-pod-xxxxx
kubectl -n myapp exec -it my-pod-xxxxx -- cat /etc/resolv.conf

输出大概是这样的:

nameserver 10.96.0.10    # 这就是 kube-dns Service 的 ClusterIP
search myapp.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

这里重点关注三个点:

  • nameserver:如果显示的不是你集群里 kube-dns 的 Service IP,那就说明有问题。比如有时候你手动改了 Pod 的 dnsPolicy 或者用了自定义 DNS。
  • search:默认配置了一个长长的搜索域。当你只写个 mysql 时,它会依次尝试 mysql.myapp.svc.cluster.localmysql.svc.cluster.localmysql.cluster.local。要是搜索域太长,每次解析都试一遍,超时概率会大增。
  • options ndots:5:意思是如果域名里的点少于5个,就先尝试搜索域。这个值默认为5,但很多应用用的域名像 my-svc 没有点,就会触发搜索域查询,浪费大量时间。

2.2 常见坑:ndots 太大导致超时

举个例子:你的 Pod 里通过 curl http://api:8080 访问另一个 Service。因为 api 只有3个字符,不足5个点,系统会先尝试 api.myapp.svc.cluster.local,然后 api.svc.cluster.local,最后才是 api.cluster.local。每次尝试都会发起一次 DNS 查询,如果 kube-dns 响应慢,三次都超时,整个过程就卡住了。

解决办法是调整 ndots 值。可以在 Pod 的 spec 里设置:

apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "1"          # 只要域名包含一个点,就不走搜索域
  containers:
  - name: debug
    image: alpine
    command: ["sleep", "3600"]

这样 api 就不会被追加搜索域,直接查询根域,快很多。但注意:如果你要解析带点的服务名(比如 my-svc.ns1.svc.cluster.local),ndots=1 反而会出错,需要根据实际域名设置。

三、第二步:验证 kube-dns Service 是否可达

3.1 从 Pod 内直接访问 Service

resolv.conf 里写的 nameserver 是一个 Service 的 ClusterIP,默认是 10.96.0.10。这个 IP 背后是一组 kube-dns(或 CoreDNS)的 Pod。我们先从 Pod 里用 nslookup 试试,看能不能直接解析。

进入 Pod:

kubectl -n myapp exec -it my-pod-xxxxx -- nslookup www.baidu.com

如果超时或报错,我们换个思路:手动用 dig 命令指定 nameserver 来查询,跳过 search 域。

# 安装 bind-tools(如果容器里没有 dig)
kubectl -n myapp exec -it my-pod-xxxxx -- sh -c "apk add bind-tools && dig @10.96.0.10 www.baidu.com +time=2 +tries=1"

加上 +time=2 限制超时2秒,能快速看出响应情况。如果一直没返回,说明 Pod 到 Service IP 的网络可能有问题。

3.2 检查 Service 和端点

先确认 kube-dns 这个 Service 是否存在、端点是否健康。

# 查看 kube-system 下的 kube-dns Service
kubectl -n kube-system get svc kube-dns

# 查看对应的端点
kubectl -n kube-system get endpoints kube-dns

输出示例:

NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP   30d

NAME       ENDPOINTS                AGE
kube-dns   10.244.0.5:53,10.244.1.7:53   30d

如果 ENDPOINTS 为空,说明后端的 CoreDNS Pod 没跑起来,或者健康检查失败。这时候需要去看 Pod 状态:

kubectl -n kube-system get pod -l k8s-app=kube-dns

假设有一两个 Pod 处于 CrashLoopBackOff,那 DNS 自然就挂了。常见原因是 CoreDNS 配置错误,比如 ConfigMap 里写了无效的上游 DNS。

3.3 网络连通性测试:从 Pod 到 Endpoint IP

即使 Service 和端点都存在,从 Pod 发出的 UDP 53 包也可能被网络策略或路由表拦截。我们直接 ping 端点 IP 看看:

# 假设端点是 10.244.0.5
kubectl -n myapp exec -it my-pod-xxxxx -- ping -c 3 10.244.0.5

能 ping 通只说明三层可达,UDP 53 端口才是关键。更好的方法是直接用 nc 测试 UDP 端口:

# 安装 netcat
kubectl -n myapp exec -it my-pod-xxxxx -- sh -c "apk add netcat-openbsd && echo '' | nc -u -w 2 10.244.0.5 53"

如果没返回,要么防火墙拦了,要么目标 Pod 没监听UDP 53。再进一步,可以抓包看看。

四、第三步:抓包排查 UDP 53 流量

4.1 Pod 端抓包

在问题 Pod 里用 tcpdump 抓 UDP 53 流量,看请求是否发出、有没有回应。

# 安装 tcpdump
kubectl -n myapp exec -it my-pod-xxxxx -- sh -c "apk add tcpdump && tcpdump -i eth0 -n -s 0 -c 10 port 53"

然后另一个终端发起一次 nslookup,观察抓包输出:

# 另一个终端执行
kubectl -n myapp exec -it my-pod-xxxxx -- nslookup www.baidu.com

如果抓包显示只有请求(> 10.96.0.10.53: ...)没有回复,说明包丢了或后端没响应。如果看到回复,但 nslookup 依然报错,可能是搜索域导致回应内容不对。

4.2 kube-dns Pod 端抓包

同样地,进入一个 kube-dns Pod 抓包,看是否收到请求。

kubectl -n kube-system exec -it coredns-xxxxxxxx -- sh -c "apk add tcpdump && tcpdump -i eth0 -n port 53"

这里注意:很多 CoreDNS 镜像不带 tcpdump,需要你自己打一个带工具的镜像,或者直接节点上抓取 veth 口。生产上建议用临时 debug 容器。

五、第四步:排查 kube-proxy 和 iptables

5.1 Service 的实现原理

Pod 访问 Service IP(如 10.96.0.10)时,真正的流量是被 kube-proxy 通过 iptables 或 IPVS 规则转发到后端 Pod。如果 iptables 规则不对,请求就会丢。

在节点上用 iptables-save 查看规则:

# 在 Pod 所在节点执行
iptables-save | grep -A 10 "KUBE-SVC-.*10.96.0.10"

应该能看到类似这样的链:

-A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-TFXY4Q2Z7QZ3L2LE
...

每个 KUBE-SEP- 对应一个后端 Pod。如果这些规则缺失,请求直接到 Service IP 就会超时。

5.2 常见原因:conntrack 表满或规则冲突

当 DNS 查询并发高时,conntrack 表可能溢出,导致新连接被丢弃。节点上可以通过 cat /proc/sys/net/netfilter/nf_conntrack_max 查看上限,conntrack -S 看当前使用率。如果接近上限,考虑调整内核参数或改用 IPVS 模式。

另外,某些 CNI 插件(如 Calico)可能配置了网络策略,禁止了 Pod 到 kube-dns 的 UDP 53 流量。检查一下:

# 查看是否有 NetworkPolicy 拒绝了 UDP 53
kubectl -n myapp get networkpolicy -o yaml

六、应用场景和技术优缺点

6.1 典型场景

  • 微服务互通:Pod 通过 Service 名称调用其他服务,DNS 解析是第一步。
  • 外部域名解析:Pod 访问数据库、Redis 等外部资源,也需要 DNS。
  • 高并发环境:大量 Pod 同时发起 DNS 查询,可能导致 kube-dns 压力大或 conntrack 表满。

6.2 kube-dns 和 CoreDNS 对比

  • kube-dns:老方案,三个容器组成(kube-dns、dnsmasq、sidecar),维护复杂,性能一般。
  • CoreDNS:后起之秀,单一二进制,插件丰富(如缓存、重试、健康检查),已成为默认。优点是配置灵活、性能高;缺点是新手对插件配置不熟,可能配错导致问题。

6.3 其他注意事项

  • 本地 DNS 缓存:可以在每个节点跑 NodeLocal DNSCache,减少对 kube-dns 的 UDP 请求,降低超时概率。
  • 禁用搜索域:如果应用只使用全限定域名(FQDN),把 search 清空或设置 ndots: 0
  • 超时时间调整:CoreDNS 的 ConfigMap 里可以配置 forward . 8.8.8.8 { force_tcp } 走 TCP,避免 UDP 丢包。TCP 虽然慢一点,但稳定。
  • 网络插件影响:有些 CNI 对 UDP 支持不好,比如 flannel 的 VXLAN 模式可能导致 DNS 包分片丢失。可以尝试开启 MTU 探测。

七、总结

Pod 域名解析超时,从表象看只是一个报错,但背后可能涉及:resolv.conf 配置不合理、搜索域太长、kube-dns Service 端点异常、iptables 规则错误、网络策略拦截、conntrack 表满、UDP 丢包等等。排查步骤应该像剥洋葱一样,先看客户端(Pod 内配置),再看服务端(kube-dns 健康状态),最后看中间网络(Service 转发 + 节点规则)。本文提供的诊断方法不需要高深理论,只需要会几个基础命令(cat、dig、tcpdump、iptables-save),就能一步步定位问题。希望能帮你从“DNS 超时”的焦虑中解脱出来,以后遇到类似问题,心里有底。