应使用 go 的 net.resolver 直接查询 dns,禁用系统缓存(prefergo: true)、指定 coredns 地址、设超时,并用 lookupsrv 获取 srv 记录;不能依赖 endpoints 因其仅反映就绪状态而非实际解析结果。

怎么用 Go 监控无头服务(Headless Service)的 DNS 解析结果
无头服务不走 kube-proxy,也不生成 ClusterIP,流量直接落到 Pod IP 上——这意味着你不能靠抓 Service 的 iptables 规则或 metrics 来监控“分发行为”,得从 DNS 解析源头入手。Go 标准库 net.Resolver 是最轻量、最可控的选择,但默认会走系统缓存,容易漏掉真实解析过程。
- 必须禁用系统缓存:设置
PreferGo: true,并显式指定 DNS 服务器(比如 CoreDNS 的 ClusterIP + port,通常是10.96.0.10:53) - 解析时用
LookupSRV或LookupHost都行,但推荐LookupSRV,因为无头服务在 DNS 中实际注册的是 SRV 记录(含端口),更贴近真实调度依据 - 注意超时控制:
net.Resolver默认无超时,需包裹在context.WithTimeout里,否则一次失败解析可能卡住整个监控 goroutine - 示例片段:
resolver := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { return net.DialTimeout(network, "10.96.0.10:53", 2*time.Second) }, } addrs, err := resolver.LookupHost(ctx, "my-headless-svc.default.svc.cluster.local")
为什么不能直接轮询 Endpoints 对象来判断流量分发
K8s 的 Endpoints 对象确实反映后端 Pod 列表,但它只表示“就绪状态”,不等于“实际被访问到”。尤其在滚动更新、Pod 启动慢、readinessProbe 延迟通过等场景下,Endpoints 已更新,但 DNS 缓存未刷新、客户端尚未重解析,真实流量仍打在旧 Pod 上。
- Endpoints 变更有延迟:API Server 到 kube-controller-manager 再到 EndpointSlice 同步,通常 1–3 秒;而 DNS TTL 多为 5–30 秒,两者不同步
- 客户端 DNS 缓存不可控:Java 应用默认缓存 30 秒,Go 默认永久缓存(除非显式设
GODEBUG=netdns=go),Node.js 甚至依赖 libc - 真正要监控的是“谁被解析出来了”,不是“谁该被解析”——所以必须走 DNS 查询,而不是 watch Endpoints
如何识别 DNS 轮询是否生效(即流量是否真正在多个 Pod 间分发)
单纯查一次 DNS 返回多个 A 记录没用,关键看这些记录是否被客户端按预期顺序使用。Go 程序自身若做负载请求,需绕过默认 round-robin 行为,主动打散请求目标。
- Go 的
http.Transport默认对同一 host 复用连接,且 DNS 解析只做一次(除非连接断开重试),所以单个 client 实例无法体现轮询效果 - 正确做法:每次请求前手动解析域名,从返回的
[]string中随机选一个 IP 构造http.Request.URL,并设置Hostheader 为原始 service 名(否则后端可能收不到 Host 匹配) - 避免复用连接:设
Transport.MaxIdleConnsPerHost = 1,强制每次新建连接,让每次 DNS 解析都生效 - 验证信号:采集每个 Pod 的 access log 中的 remote_addr,统计 IP 分布比例;如果始终只命中某 1–2 个 Pod,大概率是客户端 DNS 缓存或应用层连接池导致的“伪轮询”
遇到 no such host 或解析结果为空怎么办
这不是 Go 代码写错了,而是典型的 DNS 配置或网络路径问题。K8s 集群内 DNS 解析失败,90% 出现在以下三个环节。
立即学习“go语言免费学习笔记(深入)”;
- 检查 Pod 的
/etc/resolv.conf:确认 nameserver 是 CoreDNS 地址(10.96.0.10),不是宿主机 DNS;search 域是否包含default.svc.cluster.local等 - 确认 CoreDNS 正常运行且日志无
plugin/errors:常见于 Service 没带clusterIP: None,或 Headless Service 的 selector 不匹配任何 Pod(此时 DNS 不会返回任何记录) - 用
nslookup my-headless-svc在同 namespace 的 busybox 容器里手动测:如果它也失败,说明不是 Go 问题,而是集群 DNS 层配置问题 - 临时绕过:在 Go 里 fallback 到直接 list Pods(用 client-go),但仅限调试——这跳过了 DNS 分发逻辑,监控结果失真










