1. 先理清楚:pod访问域名到底走了哪条链路
1.1 一次让我抓狂的故障现场
前阵子我接手了一个典型的k8s集群问题:业务反馈某个命名空间下所有pod突然无法ping通外部域名,应用日志里全是lookup xxx.com: no such host。第一反应是coredns挂了,但kubectl get pods -n kube-system一看,coredns明明Running,副本数也正常。当时我就意识到,这种“coredns看起来正常但解析就是不通”的故障,往往比coredns直接挂掉更难定位。
类似的场景,我猜做过k8s运维的朋友都遇到过。pod内部执行nslookup kubernetes.default.svc.cluster.local能通,但解析外部域名就是不行;或者同一个集群里,这个节点上的pod正常、那个节点上的pod异常。这类问题的排查链路比较长,从coredns自身、到service转发、再到上游DNS配置,每一环都可能出问题。这篇文章就把我踩过的坑和排查思路完整梳理一遍,顺序很重要,跳步容易浪费时间。
1.2 链路拆解:不是所有“解析失败”都该怪coredns
先把我脑子里那张链路图画出来。一个pod要解析域名,请求走的是这么一条路径:
pod -> /etc/resolv.conf里配置的nameserver -> ClusterIP形式的coredns service -> 某个coredns后端pod -> forward插件 -> 上游DNS(通常是宿主机resolv.conf里的nameserver,或云厂商DNS)。
也就是说,表面上“pod无法解析域名”,实际断了可能发生在四个层面。
第一层是coredns自身。比如pod镜像拉不下来、CrashLoopBackOff、OOMKilled、配置错误导致启动失败。
第二层是service链路。coredns pods是正常的,但kube-dns这个service的selector没匹配到pod,或者EndpointSlice为空,或者kube-proxy没生成对应的转发规则,pod根本访问不到ClusterIP。
第三层是coredns内部处理。Corefile配置了奇怪的上游、forward插件指向了不可达的地址、缓存插件异常,甚至coreDNS版本和k8s版本不匹配。
第四层是网络层。tiller还是calico,NetworkPolicy挡掉了53端口,或者节点DNAT规则、conntrack表满了。
所以,拿到“域名解析失败”这个表象,第一件事不是去改Corefile,而是先判断请求到底断在哪一环。下面我就按我实践验证过的排查顺序,一层一层拆开讲,每一层都有对应的验证命令和判断标准。
2. 第一轮分诊:coredns Pod本身是真病还是假病
2.1 先看pod状态和重启原因
不要凭“Running”三个字就认定coredns健康,我见过太多Running状态却反复刷错误日志的情况。第一步用这两个命令看详细状态:
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide kubectl -n kube-system get pods -l k8s-app=kube-dns -o yaml | grep -A 10 status:重点关注restartCount,如果这个数字在持续增长,说明coredns进程在崩溃重启,只是重启够快、还没来得及进入CrashLoopBackOff。常见原因翻来覆去就几种:
一是OOMKilled,lastState.terminated.reason里写着OOMKilled。coredns默认配置给的limits比较低,在高并发解析场景下很容易触顶。
二是健康检查失败。coredns的pod里一般会配livenessProbe和readinessProbe,都指向/health端口。如果探针失败,pod会被自动重启。
三是启动就报错,常见于Corefile配置了插件但语法不兼容,或者ConfigMap和二进制版本对不上。
我遇到过一个比较隐蔽的情况:coredns pod状态正常,但kubectl logs里不停刷[ERROR] plugin/errors: 2 clientIP:54821: query "example.com. A" denied,这是security插件或acl插件拦截了某些来源的请求,后面会细说。
2.2 日志里最有价值的几种报错
看coredns日志,很多人会直接kubectl logs -f然后被刷屏。实际上最有效的方式是先统计错误类型:
kubectl -n kube-system logs deployment/coredns --tail=200 | grep -E "ERROR|WARN|FATAL" | sort | uniq -c说一下我实际见到过的几类典型错误和对应的方向:
[ERROR] plugin/errors: ... no such host,表示coredns已经收到了查询,但上游返回NXDOMAIN,问题多半不在coredns而在上游DNS或者要解析的域名本身。
i/o timeout,也就是上游DNS连接超时,这类错误十有八九是forward插件配置的上游IP不可达,或者53端口被安全组挡了。
SERVFAIL,上游返回了服务端故障。注意这个和no such host不一样,前者是上游DNS服务器收到了请求但自己挂了或有策略问题,后者是权威回答“这个域名不存在”。
clientIP ... denied,acl或security插件拦截。这类错误在日志里最容易被忽略,因为它不是红字报错,但恰恰意味着请求根本没到forward那一环。
还有一种很容易漏掉的:coredns响应慢但不报错。这种我会直接在coredns pod里看查询耗时,方法后面给。
2.3 资源限制和调度导致的隐性故障
coredns的pod如果被调度到同一台节点,且该节点本身网络异常,会有一种“部分pod解析超时、部分正常”的现象。我遇到过一次:两个coredns副本都跑在同一个计算节点上,这个节点因为负载高导致网络延迟飙升,结果集群里一半pod的域名解析超时。
这种情况从coredns自身看不出任何问题,必须看副本分布和节点状态。推荐用:
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide kubectl top nodes处理思路一般是给coredns加podAntiAffinity,让副本尽量分散到不同节点。另外,coredns对CPU很敏感,limits.cpu设得太低会导致在处理大量并发请求时频繁调度、响应变慢。我一般建议coredns的CPU request给到500m,limits给到1000m以上,内存根据集群规模调整,5000节点以内4G内存足够。
除了资源,还要看节点上的systemd-resolved服务是否吃掉53端口。很多Linux发行版默认开systemd-resolved,占用了127.0.0.53的53端口,如果coredns的forward配置写的是127.0.0.53,那么上游请求可能直接带错源IP,导致DNS放行策略拒绝。这是个非常隐蔽的坑,后面第三节我会具体讲怎么排查。
3. 第二轮分诊:Service链路和endpoint才是隐藏的重灾区
3.1 ClusterIP转发链路排查
coredns pod状态正常、日志也没问题,但pod就是解析不了,那就得盯住service链路。Coredns对外暴露的方式是kube-dns这个service,clusterIP通常是10.96.0.10。pod里的/etc/resolv.conf指向的nameserver就是它。
测试链路是否通的最快方式,是起一个临时的测试pod:
kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- sh进入pod后依次执行:
nslookup kubernetes.default.svc.cluster.local nslookup www.baidu.com如果第一个能通、第二个不通,说明coredns到pod这一段没问题,问题出在上游。如果两个都不通,就用下面的命令检查service:
kubectl -n kube-system get svc kube-dns -o yaml重点看两处:spec.selector是否匹配coredns的pod标签,以及spec.clusterIP是否存在。很多人在搭建coredns时会把selector错写成k8s-app: coredns,但coredns deployment的标签实际是k8s-app: kube-dns,一旦不匹配,EndpointSlice就一直是空的。
3.2 验证Service和Endpoint
service创建了不代表背后有pod在服务,这一步必须看EndpointSlice:
kubectl -n kube-system get endpointslices -l kubernetes.io/service-name=kube-dns正常情况下,这个列表里应该有coredns pod对应的IP和端口53。如果列表为空,说明selector没匹配上,立即检查deployment的labels。
还有一种是EndpointSlice正常,但pod访问ClusterIP不通。这种多半是kube-proxy没为这个service生成转发规则。验证方式分两种模式。
iptables模式下:
iptables -t nat -L | grep 10.96.0.10看KUBE-SERVICES链里有没有对10.96.0.10的DNAT规则。ipvs模式下:
ipvsadm -L -n | grep 10.96.0.10如果规则都在,接着看conntrack表是不是满了,dmesg | grep conntrack会刷大量报错。还要检查节点防火墙是否放行UDP/TCP 53端口,特别是使用云厂商时,安全组里入了站规则但忘了放行53,coredns pod的NodePort模式就会直接被挡在外面。
3.3 kube-proxy转发模式带来的差异
顺手补充一个对比,iptables和ipvs在处理DNS流量时的行为差异很大。iptables模式下,DNS查询走的是UDP 53,服务器响应时如果策略是用MASQUERADE做的源地址伪装,会给每个连接分配一个端口,高并发下conntrack表一旦满了,新的连接就会随机丢包,现象是“有时能解析、有时超时”。
ipvs模式走的是内核态负载均衡,性能和稳定性一般优于iptables,但有个特点:它默认对UDP连接的超时时间很长,如果上游DNS变更了IP,老连接会继续往旧IP转发很久,现象是“改了上游DNS配置后要过很久才生效”。
所以生产环境我长期用的方案是ipvs + 明确的UDP超时参数:
kube-proxy --proxy-mode=ipvs --ipvs-udp-timeout=10s这个10s意味着UDP连接10秒无响应就会被回收,避免DNS请求滞留在失效连接上。
4. 第三轮分诊:Corefile配置与上游转发,配置层最容易埋雷
4.1 forward插件和上游DNS的微妙关系
前两轮都过了,说明coredns现在能正常收到请求。这时候最值得查的就是Corefile。先看当前配置:
kubectl -n kube-system get configmap coredns -o yamlCorefile里最高的关注点就是forward插件。很多人会忽略一个原则:coredns所在的节点能访问哪个DNS,不代表所有pod都能通过它访问。特别是forward指定为127.0.0.53这类本地地址时,只有在coredns pod自身能访问的前提下才通。
我踩过了一个很经典的坑:某次我把coreDNS的forward指向了宿主机网络里的一个内部DNS服务器10.20.0.53,节点上测是通的,因为在节点的网络命名空间里。但coredns跑在pod网络里,后面的flannel/calico网段和节点网段之间还有一层网关策略,这个IP对pod网络来说不可达,于是所有外部域名解析全部超时。处理方式是把forward改为指向节点网络里可达的地址,或者直接用云厂商提供的VPC DNS地址,或者改用/etc/resolv.conf的自动列表。
4.2 配置不当引发的典型问题
Corefile里常见的问题不止forward一个,还有几个我实际遇到过的配置雷:
cache插件设置不当。coredns默认cache插件会缓存外部域名解析结果,TTL以查询结果为准。但如果手滑加了cache 30这种全局TTL,所有域名的缓存都变成30秒,解析量大的时候,上游DNS被频繁打爆,且某些域名IP变更后30秒内所有节点都拿到旧IP,报错五花八门。
hosts插件配置了自定义记录,但没有加fallthrough。hosts插件默认匹配不到就返回NXDOMAIN,如果没有fallthrough指令,请求根本不会继续走到forward,于是出现“部分域名解析失败、部分成功”的诡异现象。比如我在hosts里配置了内部测试域名,结果外部域名也全挂了,就是这个原因。
rewrite规则写错。比如一个简单的rewrite name suffix .example.com .example.io,如果写成了正则还带re标记,匹配逻辑错乱会导致某些域名变成乱码请求,上游自然返回SERVFAIL。
prometheus插件和health插件都绑定了固定端口,如果多副本coredns的metrics端口冲突、导致探针访问失败,pod会被反复重启,这个现象非常隐蔽,因为只会在日志里看到port already in use。
4.3 修改Corefile后的重载机制
最后说一个coredns特有的更新逻辑。修改Corefile后,coredns会自动reload,但不是立即生效。默认情况下,coredns会每30秒检查一次ConfigMap的变化,发现变更后拉起新的配置,期间会有短暂的两到三次请求失败窗口。
另外,在k8s里改ConfigMap有两种常见方式,一种是直接改configmap,另一种是用helm模板渲染。我建议明确用kubectl edit configmap coredns -n kube-system,改完等待30秒再用kubectl -n kube-system rollout restart deployment/coredns强制触发一次平滑滚动更新。为什么还要重启?因为某些插件(比如hosts和rewrite)在reload时不一定能完全加载新配置,尤其跨版本升级后,旧进程可能残留旧配置。
记得在生产集群里,先看coredns的prometheus指标确认reload完成后,再验证解析。验证命令是:
kubectl exec -it -n kube-system deploy/coredns -- nslookup example.com 127.0.0.1在coredns内部直接查询,能排除service转发层的干扰,直接测试Corefile本身是否生效。
5. 第四轮分诊:网络插件和安全策略,绕不开的底层因素
5.1 NetworkPolicy能不能挡DNS
如果说前三轮是“看得见的故障”,那么网络插件和安全策略这层就是最容易漏掉的看不见的故障。很多团队会给业务命名空间配置默认拒绝的NetworkPolicy,只放行特定流量。DNS请求默认走的是TCP/UDP 53到kube-dns的ClusterIP,如果NetworkPolicy里忘了放行这条路径,pod就会陷入“无法解析域名”的状态。
典型配置长这样,只允许该命名空间的pod访问coredns的53端口:
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: your-namespace spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 53我遇到过最坑的一次是:业务命名空间里有一条策略,只放行了到网关网段的流量,忘了放行DNS,结果业务所有pod“间歇性无法解析域名”——因为部分请求命中了NodeLocal DNSCache(后面会说),部分请求绕过缓存的直接打到coredns就被挡住了。表面现象毫无规律,花了大半天才定位到是策略问题。
排查NetworkPolicy的方法是先确认集群用的网络插件是否支持策略,再用kubectl get networkpolicy -A看看有没有可疑策略暂时关闭。实际生产中,我会在业务上线前先把DNS策略固定放行,否则排查成本太高。
5.2 网络插件数据面与MTU、conntrack问题
calico、flannel、cilium这些网络插件在数据面转发时,docker容器默认MTU是1500,但如果宿主机网卡MTU是1400或更低(云厂商常见),数据包会被截断,DNS响应包还没超过MTU的时候不会出问题,一旦响应包超过1500字节就会分片,很多链路对分片处理不好就直接丢弃,现象是“小域名正常、大域名超时”。
排查方式不复杂,但需要对比:
ip link show | grep mtu kubectl get ds -n kube-system | awk '{print $1}'确认网络插件管理的接口MTU和宿主机网卡MTU一致。不一致时,在安装网络插件前就要指定MTU参数。已经跑了很久的集群要调MTU,只能业务低峰期滚动重启所有节点,过程比较痛苦,所以新集群我强烈建议从开始就核对这个值。
conntrack表满则是另一种高频问题,尤其在高并发短连接场景,DNS请求又是UDP,每个连接在conntrack表里占一块空间,表满后新连接无法建立,现象是流量时断时续。看系统日志:
dmesg | grep -i conntrack | tail -20如果有nf_conntrack: table full, dropping packet,基本可以确认了。临时处理是调大net.netfilter.nf_conntrack_max,长期建议加监控,确保用了ipvs模式。
5.3 客户端侧也要看:node本地DNS策略的影响
这里展开说一下NodeLocal DNSCache。为了让pod不走长链路直接访问coredns,很多集群会在每个节点上以daemonset方式部署node-local-dns,把kube-dns的ClusterIP替换成本地节点IP,比如169.254.20.10。这个方案能明显降低DNS延迟,但它本身也是一个故障点。
如果node-local-dns的配置从coredns的Corefile里同步了hosts、cache等插件,但同步脚本出故障,就会出现“pod解析一些域名正常,另一些域名失败”的情况。每次改造DNS架构后,我都建议挑几个不同命名空间的pod交叉验证:
kubectl exec -it <pod> -- cat /etc/resolv.conf看nameserver是不是指向了169.254.20.10这类本地地址。如果指向的是coredns ClusterIP,说明node-local-dns没有接管,排查方向又不一样。
还有节点上的/etc/resolv.conf。coredns的forward如果配置为空,会使用coredns pod所在节点的/etc/resolv.conf作为上游。但节点如果用了systemd-resolved,上游会被改写成127.0.0.53,这个地址对pod网络并不可达,所以会出现“节点上dig正常、pod里解析超时”的典型错位。处理方式是把forward明确写成真实可达的上游DNS地址,比如云厂商的VPC DNS,或者公共DNS(前提是集群出网策略放行)。
6. 排查命令手册与预防措施,送给每一位被域名解析折磨过的同行
6.1 一套清晰的排查顺序
把前面所有思路压缩成一套可以直接抄作业的排查顺序,我建议按这个优先级走:
第一,确认coredns pod状态和Endpoint。kubectl -n kube-system get pods -l k8s-app=kube-dns和kubectl get endpointslices看一眼,这是最基础的信息,不要跳过。
第二,用busybox测试pod内部解析。分三个层面测试:coredns ClusterIP、coredns pod IP、上游DNS地址。每层都测,就能快速锁定断点。
第三,检查Corefile配置是否合理。重点看forward的IP是否可达、有没有fallthrough、cache参数有没有诡异值。
第四,检查NetworkPolicy和网络插件数据面。确认没有拦截DNS流量,MTU和conntrack没问题。
我做一个标准测试命令清单,建议直接存下来:
# 在节点上观察coredns日志统计 kubectl -n kube-system logs -l k8s-app=kube-dns --tail=200 | grep -E "ERROR|SERVFAIL|i/o timeout" | tail -20 # 进入coredns pod直接测试本地解析 kubectl exec -it -n kube-system deploy/coredns -- nslookup example.com 127.0.0.1 # 测试pod到coredns ClusterIP链路 kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- nslookup example.com 10.96.0.10 # 检查本机conntrack和MTU dmesg | grep -i conntrack ip link show | grep -E "mtu|flannel|calico|eth0"6.2 监控与预防建议
故障排查得再多,不如提前把监控做好。coredns自带prometheus插件,只需要在Corefile里启用并配置ServiceMonitor就能采集一系列关键指标。我长期关注的几个指标:
coredns_dns_request_count_total按视角维度看总请求量,如果某个节点请求量为0,说明该节点上没有pod在解析,或者node-local-dns配置错了。
coredns_dns_request_duration_seconds看P99延迟,正常情况下解析延迟应该在几毫秒到几十毫秒,如果P99超过200ms持续一段时间,就要重点看上游DNS和网络延迟。
coredns_dns_response_rcode_count_total按错误码分类统计,如果SERVFAIL占比突然升高,问题基本在上游。
coredns_forward_request_duration_seconds看forward到上游的耗时,这个指标能直接区分是coredns自身慢还是上游慢。
另外,给coredns加一个简单的Liveness探针和详细日志采样,探针失败时快速重启,日志采样则保留最近1000条错误,方便事后复现。
还有个预防细节:把coredns的副本数至少设为2,并加上podAntiAffinity,避免所有副本被调度到同一台节点。部分集群还会用HorizontalPodAutoscaler按CPU和请求量自动扩缩,这个看需要,一般中小集群固定2个副本足够。
6.3 最后再分享一个经验
我实际搞了这么久k8s,最深的感受是:DNS类的故障,80%以上不是coredns的代码问题,而是“周边环境问题”——要么是配置没跟上,要么是网络策略挡路,要么是资源/调度失衡。遇到pod无法解析域名时,一定不要先怀疑coredns本身,先按链路由内到外逐层排除。
个人习惯是:所有和生产相关的k8s集群,DNS相关配置一律走版本管理,Corefile改动用MR方式经过评审再合入;每次对网络配置或网络插件升级前,先抽取几个pod做一轮解析压测,把基线数据留好。这样一旦后续出了解析问题,不看日志就能先对比“是不是改完东西才开始的”。
上面这套排查思路,我在模拟项目和几个生产集群上都验证过,中间所有命令都是可以直接复制跑的。最后提醒一点:如果生产环境持续出现DNS解析抖动,别只盯着coredns本身,花十分钟看一眼节点上的systemd-resolved状态,我见过不止一次是它跟coredns抢53端口导致的。关于这一层,还有一个小技巧:检查节点的53端口占用情况,ss -lunp | grep :53,看到多个进程监听时,要特别小心处理。