解决方案:
kubectl patch svc coredns -n kube-system --type merge -p '{"spec": {"sessionAffinity": "ClientIP"}}'
原理解析
你观察到的现象------"第一次访问域名不通,第二次必通"------在 Kubernetes 集群中访问 CoreDNS 服务时偶有发生。通过为 coredns Service 设置 sessionAffinity: ClientIP 能够解决问题,其原理涉及 Kube-Proxy 的负载均衡机制 、UDP 协议特性 以及Conntrack 表项管理。下面详细解释。
1. 问题根源:UDP + 多副本负载均衡的不稳定性
Kubernetes 中,coredns Service 通常对应多个 Pod 副本,Kube-Proxy(默认使用 iptables 模式,或可选用 IPVS)会将 DNS 请求(UDP)按某种算法(如轮询、随机)分发到后端 Pod。
- UDP 是无连接协议,没有 TCP 那样的握手和会话保持。每个 DNS 查询是一个独立的 UDP 数据报。
- 当客户端(容器)发出第一个 DNS 查询时,Kube-Proxy 可能将其转发到 Pod A ;几毫秒后若因超时重试,第二次查询可能被转发到 Pod B。
- 若 Pod A 此时处于某种"亚健康"状态(例如刚启动、缓存未预热、网络瞬时抖动),第一个查询就会失败或超时;而 Pod B 是健康的,第二个查询成功。
- 这种现象在多个 CoreDNS 副本间性能不一致,或者 iptables 规则更新、Conntrack 表项冲突时尤为明显。
2. sessionAffinity: ClientIP 的作用
sessionAffinity: ClientIP 是 Service 的一个配置,它指示 Kube-Proxy:对来自同一个源 IP 的客户端,始终将其请求转发到同一个后端 Pod。
kubectl patch svc coredns -n kube-system --type merge -p '{"spec": {"sessionAffinity": "ClientIP"}}'
配置生效后:
- 来自某个容器 IP 的所有 DNS 查询,都会被定向到同一个 CoreDNS Pod(例如 Pod A)。
- 只要这个 Pod 是稳定健康的,后续查询就会一直成功,不会因为轮询切换而产生"第一次失败,第二次成功"的差异。
- 同时,由于固定了后端,Conntrack 表项对该客户端 IP 保持一致,减少了因 UDP NAT 表项竞争或超时导致的丢包。
注意:如果固定的 Pod 本身有问题(如持续宕机),那么来自该客户端的所有请求都会失败。因此此方法适用于 Pod 偶尔短暂抖动或负载均衡"错峰"导致的偶发失败场景。在正常的集群中,CoreDNS 副本通常都是稳定的,此配置能显著降低因切换后端引起的异常。
3. 更深层的技术细节:Conntrack 与 UDP NAT
iptables 模式的 Kube-Proxy 依赖 Linux 内核的 conntrack 模块对 UDP 流量进行 NAT(源地址转换和目的地址转换)。每个 UDP 请求经过 Service 时,conntrack 会创建一条记录(tuple:源IP+端口 → 目标ClusterIP+端口 → 后端PodIP+端口)。
在某些情况下,例如高并发或 conntrack 表满时,第一个 UDP 包可能因为 conntrack 表项未及时建立而被丢弃(内核丢弃或等待)。但第二次重试时,conntrack 表项已经存在,因此报文能正常转发。
- 设置
sessionAffinity: ClientIP后,相同客户端 IP 总是映射到同一个后端 Pod,对应的 NAT 映射关系更加稳定,降低了 conntrack 表项反复创建/删除的频率,从而减少了"首包丢失"的概率。
4. 总结
| 原因 | sessionAffinity: ClientIP 的原理 |
|---|---|
| 多个 CoreDNS 副本间负载均衡切换,导致第一次访问到慢/不健康的 Pod,第二次访问到健康的 Pod | 固定客户端 IP 到同一个 Pod,避免切换 |
| UDP 无会话特性 + conntrack 表项竞争,第一个包可能被丢弃 | 稳定的 NAT 映射关系,减少 conntrack 表项重建,避免首包丢失 |
因此,通过为 CoreDNS 服务配置客户端 IP 亲和性,可以显著提高 DNS 解析的稳定性和成功率,解决"第一次不通,第二次必通"的典型问题。这是生产环境中优化 CoreDNS 可靠性的常见手段之一。