一、问题来了: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.local、mysql.svc.cluster.local、mysql.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 超时”的焦虑中解脱出来,以后遇到类似问题,心里有底。
Comments