1. 从一个诡异的通信故障说起
集群里跑着一套微服务,订单服务要调库存服务,库存服务要调商品服务,彼此之间全靠 Service 别名互相访问。某天做灰度发布,新拉起来的一批 Pod 突然报错,日志里清一色是no such host或者连接超时。诡异的地方在于:老 Pod 一切正常,新 Pod 就是找不到隔壁服务。重启、重建、换节点,全试了一遍,问题依旧。这种"同一个集群里,有的 Pod 能通、有的 Pod 不能通"的现象,是 Kubernetes 网络排查里最让人头疼的一类。
这篇记录就是围绕这个场景展开的。核心关键词是Kubernetes、Pod 内服务别名通信、DNS 解析、CoreDNS、Service 发现。我会把当时从现象到根因、从临时绕过到彻底修复的完整过程拆开讲,包括 DNS 解析链路是怎么走的、ndots和search域为什么会让别名解析失败、CoreDNS 的配置怎么查、NetworkPolicy 和 Endpoint 怎么排除。适合正在被集群内服务互调问题折磨的运维和开发同学,也适合刚接触 K8s 网络、想搞明白"为什么我的 Pod 解析不了服务名"的入门者。看完你至少能做到:拿到一个no such host的报错,知道按什么顺序去查,而不是盲目重启。
2. 先搞清楚 Pod 里"服务别名"到底是怎么解析的
2.1 服务别名不是魔法,本质是一次 DNS 查询
很多人以为 Pod 里写inventory-svc就能通,是 Kubernetes 内置了什么"服务发现魔法"。其实不是。当你在 Pod 里执行curl http://inventory-svc:8080时,第一步是 glibc 或 musl 的解析器去查 DNS,把inventory-svc这个名字翻译成一个 ClusterIP,然后才发起 TCP 连接。真正干活的,是集群里那个叫CoreDNS的组件。
CoreDNS 以 Deployment 形式跑在kube-system命名空间下,前面挂一个名为kube-dns的 Service,ClusterIP 通常是10.96.0.10这个网段里的某个地址。每个 Pod 被创建时,kubelet 会把/etc/resolv.conf写进去,内容大致长这样:
nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5这三行就是整个服务发现的入口。nameserver指向 CoreDNS 的 ClusterIP;search定义了域名后缀的补全顺序;ndots:5决定了什么样的名字会先被当成"绝对域名"去查。理解这三行,基本就理解了 80% 的别名解析问题。
2.2 一次inventory-svc查询的完整链路
假设 Pod 在default命名空间,执行getent hosts inventory-svc,解析器会按search列表依次尝试:
inventory-svc.default.svc.cluster.localinventory-svc.svc.cluster.localinventory-svc.cluster.local- 最后才尝试
inventory-svc.(作为绝对域名)
只要其中任意一个返回了 A 记录,解析就成功。CoreDNS 收到查询后,会根据配置的kubernetes插件去 API Server 里查对应的 Service 或 Endpoint,把 ClusterIP 返回去。整个过程在毫秒级完成,正常情况下你根本感知不到。
问题就出在"正常情况下"这四个字。一旦search域不对、ndots被改、CoreDNS 挂了、或者查询被 NetworkPolicy 拦了,这条链路就会断在某一环,表现就是no such host。
2.3 为什么"有的 Pod 能通、有的不能"
这是当时最迷惑我的点。同一个 Deployment,滚动更新出来的新 Pod 解析不了,老 Pod 却好好的。后来发现,问题往往出在Pod 的 DNS 配置差异上。Kubernetes 允许通过dnsPolicy和dnsConfig对单个 Pod 的解析行为做定制:
dnsPolicy: ClusterFirst是默认值,走集群 DNS。dnsPolicy: Default会继承节点上的 resolv.conf,不走 CoreDNS,自然解析不了服务别名。dnsPolicy: None配合dnsConfig手动指定,配错了就直接废掉。dnsConfig里的searches和options会覆盖默认值,改错ndots或search顺序,别名就查不到。
当时那批新 Pod 的 YAML 里,有人为了"优化外部域名解析"加了dnsConfig,把ndots从 5 改成了 1,还把searches清空了。结果就是:外部域名确实快了,但集群内的短别名inventory-svc因为ndots:1被当成绝对域名直接查,search域又没了,CoreDNS 自然返回 NXDOMAIN。老 Pod 没这个配置,所以正常。这就是"同集群不同命"的典型根因。
3. 排查这类问题的标准动作与工具
3.1 第一步永远是看 Pod 里的 resolv.conf
别急着上 tcpdump,先kubectl exec进 Pod,把/etc/resolv.conf打出来:
kubectl exec -it <pod-name> -n <namespace> -- cat /etc/resolv.conf重点核对三件事:nameserver是不是 CoreDNS 的 ClusterIP、search域里有没有svc.cluster.local、ndots是不是被改过。我踩过的坑里,有一半以上在这一步就能定位。如果nameserver指向的是8.8.8.8这种外部地址,那基本可以确定dnsPolicy配错了,Pod 压根没走集群 DNS。
3.2 用 nslookup 和 dig 分层验证
确认 resolv.conf 没问题后,进 Pod 手动查:
# 查短别名 nslookup inventory-svc # 查完整 FQDN nslookup inventory-svc.default.svc.cluster.local # 直接指定 CoreDNS 查,排除 search 域干扰 nslookup inventory-svc.default.svc.cluster.local 10.96.0.10如果短别名失败、FQDN 成功,问题就在search域或ndots;如果 FQDN 也失败,但直接指定 CoreDNS 又成功,那可能是 Pod 的 resolv.conf 里 nameserver 写错了;如果连指定 CoreDNS 都失败,问题就上升到 CoreDNS 本身或网络策略层面了。这个分层验证法能快速缩小范围,比一上来就抓包高效得多。
提示:很多精简镜像里没有
nslookup和dig。可以临时起一个带工具的调试 Pod,用kubectl run tmp --image=busybox --rm -it -- sh,然后nslookup目标服务。busybox 自带 nslookup,够用。
3.3 检查 CoreDNS 自身状态
如果怀疑是 CoreDNS 的问题,先看 Pod 和日志:
kubectl get pods -n kube-system -l k8s-app=kube-dns kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100重点看日志里有没有大量SERVFAIL、NXDOMAIN、i/o timeout。如果 CoreDNS Pod 频繁重启,可能是资源不够或者配置有语法错误。再看 ConfigMap:
kubectl get configmap coredns -n kube-system -o yaml里面的Corefile定义了kubernetes插件、forward上游、cache等。如果kubernetes插件的pods或endpoints配置被改坏,服务解析就会出问题。我遇到过有人为了"减少 API Server 压力"把pods insecure去掉,结果某些依赖 Pod 域名解析的场景直接挂了。
3.4 别忘了 Endpoint 和 NetworkPolicy
DNS 解析成功只是第一步,解析出来的 ClusterIP 后面还得有真实的 Endpoint 才能通。如果 Service 的 selector 写错、或者后端 Pod 没 Ready,Endpoint 列表就是空的,连接会超时而不是no such host。这两个现象要区分开:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
no such host | DNS 解析失败 | resolv.conf、CoreDNS、ndots |
connection refused | 端口不对或进程没起 | Service targetPort、容器端口 |
connection timeout | Endpoint 为空或网络策略拦截 | Endpoint 列表、NetworkPolicy |
| 间歇性失败 | CoreDNS 负载高或缓存问题 | CoreDNS 资源、cache 配置 |
NetworkPolicy 是另一个隐形杀手。如果命名空间里配了默认拒绝入站的策略,又没放行 CoreDNS 的 53 端口,Pod 的 DNS 查询会被直接丢弃,表现也是解析超时。查一下:
kubectl get networkpolicy -n <namespace>确认有没有策略把 UDP/TCP 53 给挡了。
4. 根因定位与修复实操
4.1 还原当时的故障现场
回到开头那个案例。新 Pod 报no such host,老 Pod 正常。按上面的顺序查:
第一步,进新 Pod 看 resolv.conf,发现ndots:1,search域是空的。老 Pod 是ndots:5加完整 search 域。差异找到了。
第二步,在新 Pod 里nslookup inventory-svc,失败;nslookup inventory-svc.default.svc.cluster.local,成功。这印证了:短别名因为ndots:1被当成绝对域名,又没有 search 域补全,所以查不到;FQDN 是完整的,所以能查到。
第三步,翻 Deployment 的 YAML,找到那段"优化"配置:
dnsConfig: options: - name: ndots value: "1" searches: []问题根源确认:有人手动清空了searches并把ndots降到 1,破坏了集群内短别名的解析。
4.2 修复方案与验证
修复很简单,把dnsConfig里破坏性的部分去掉,恢复默认行为:
# 删除 searches: [] 和 ndots 覆盖,或者显式写回正确值 dnsConfig: options: - name: ndots value: "5" searches: - default.svc.cluster.local - svc.cluster.local - cluster.local改完滚动更新,新 Pod 起来后再验证:
kubectl exec -it <new-pod> -- cat /etc/resolv.conf kubectl exec -it <new-pod> -- nslookup inventory-svc短别名解析成功,故障解除。这里有个经验:如果确实需要优化外部域名解析速度,不要动ndots和searches,而是用 FQDN 访问外部域名,或者在 CoreDNS 层面做 forward 优化。动 Pod 级别的 DNS 配置,影响面太大,很容易误伤集群内通信。
4.3 参数背后的取舍逻辑
为什么默认ndots是 5?因为 Kubernetes 希望集群内的短别名优先被解析。ndots的含义是:如果域名里的点数量少于这个值,就先按 search 域补全去查。inventory-svc有 0 个点,小于 5,所以会走 search 列表。如果设成 1,inventory-svc的 0 个点仍然小于 1,理论上还是会走 search——但前提是 search 列表非空。当时的问题是 search 被清空了,所以补全无从谈起,直接查绝对域名失败。
那为什么有人会把ndots调小?因为访问外部域名如api.example.com有 2 个点,默认ndots:5下会先尝试api.example.com.default.svc.cluster.local等一系列无效查询,浪费几次 DNS 往返才查到真实地址。调小ndots能减少这种无效查询。但代价就是集群内短别名可能受影响。这是一个典型的"优化外部、牺牲内部"的取舍,做之前必须评估集群内服务互调的规模。
注意:如果非要调
ndots,建议保留完整的searches列表,并且把值设在 2 到 3 之间,而不是 1。这样既能减少外部域名的无效查询,又能保证集群内短别名(0 个点)仍然走 search 补全。
5. 常见问题速查与避坑心得
5.1 高频问题速查表
| 问题现象 | 根因 | 解决动作 |
|---|---|---|
短别名no such host,FQDN 正常 | ndots或searches被改 | 恢复默认 DNS 配置 |
| 所有域名都解析失败 | dnsPolicy: Default或 nameserver 错 | 改回ClusterFirst |
| 间歇性解析超时 | CoreDNS 负载高或副本不足 | 扩容 CoreDNS、调 resources |
| 解析成功但连接超时 | Endpoint 为空或 NetworkPolicy | 查 Endpoint、放行 53 端口 |
| 跨命名空间别名失败 | 用了短名但不在同命名空间 | 用svc.namespace或 FQDN |
| CoreDNS 日志大量 SERVFAIL | 上游 forward 不可达 | 检查 Corefile 的 forward 配置 |
5.2 跨命名空间访问的坑
短别名inventory-svc只在同命名空间内有效。如果订单服务在order命名空间,库存服务在inventory命名空间,那订单服务里必须写inventory-svc.inventory或者完整 FQDNinventory-svc.inventory.svc.cluster.local。我见过有人图省事,在两个命名空间里各建了一个同名 Service,结果解析到了错误的那个,排查了半天。命名空间隔离是好事,但别名解析不会跨命名空间自动补全,这一点必须记牢。
5.3 独家避坑心得
第一条,永远不要在生产环境随意改 Pod 的dnsConfig。如果确实有外部域名解析性能问题,优先在 CoreDNS 的 Corefile 里做forward和cache优化,而不是动每个 Pod 的解析器配置。Pod 级别的改动影响面不可控,滚动更新时新老 Pod 行为不一致,排查成本极高。
第二条,给 CoreDNS 配好资源限制和副本数。默认 CoreDNS 可能只有 2 个副本,集群规模一大,QPS 上来就容易超时。建议至少 3 个副本,并且配上podAntiAffinity打散到不同节点。资源方面,requests给足 CPU,DNS 是延迟敏感型服务,CPU 被限流会直接导致解析超时。
第三条,用kubectl run起临时调试 Pod 时,记得指定命名空间。默认会落在default,如果你要排查的是order命名空间的服务,调试 Pod 也得在order里,否则短别名解析行为不一样,容易误判。
第四条,CoreDNS 的 cache 有时会"骗"你。改了 Service 的 Endpoint 后,短时间内解析结果可能还是旧的。排查时如果怀疑缓存,可以在 Corefile 里临时调小cache的 TTL,或者直接重启 CoreDNS Pod 清缓存。不过生产环境慎重重启,最好通过配置调整。
5.4 一个容易被忽略的细节:IPv6 与双栈
如果集群开了双栈,Pod 的 resolv.conf 里可能同时有 IPv4 和 IPv6 的 nameserver。某些应用的解析器对 IPv6 处理不好,会先尝试 IPv6 查询、超时后再回退 IPv4,表现为"解析特别慢但最终能通"。这种情况可以在dnsConfig里显式指定nameservers只保留 IPv4 的 CoreDNS 地址,或者检查 CoreDNS 的 Service 是不是双栈暴露的。双栈环境下的 DNS 问题比较隐蔽,日志里往往只看到延迟,看不到报错,需要结合抓包才能确认。
6. 把排查思路固化成习惯
这套排查流程我后来整理成了一个固定动作:一看 resolv.conf,二查短名与 FQDN 对比,三验 CoreDNS 状态,四排 Endpoint 与 NetworkPolicy。四步走下来,绝大多数 Pod 内服务别名通信问题都能定位。真正难的不是技术本身,而是面对"有的通有的不通"这种非确定性现象时,能不能保持分层验证的耐心,而不是一上来就重启大法。
我个人在实际操作中的体会是,Kubernetes 的网络问题,90% 的根因都藏在配置差异里,而不是组件本身坏了。同一个集群里行为不一致,第一反应应该是"这两个 Pod 的配置哪里不一样",而不是"集群是不是出问题了"。把kubectl exec进 Pod 看 resolv.conf 变成肌肉记忆,能省下大量抓包和重启的时间。最后再分享一个小技巧:给关键命名空间配一个常驻的调试 Pod,镜像里预装dig、nslookup、tcpdump、curl,出问题时直接 exec 进去,比临时拉镜像快得多,也不会因为镜像拉取失败耽误排查。