Headless Service服务发现需用net.LookupIP("ip", fqdn)查完整FQDN,获Pod IP列表;须缓存DNS结果、探测端口健康、轮询可用IP,并备fallback机制,否则易连空或失败。

Go 程序在 Kubernetes 里用 Headless Service 做服务发现,不是“创建”它的问题,而是“怎么查、查什么、查完怎么用”——Kubernetes 本身早就在跑 CoreDNS,my-svc.default.svc.cluster.local 这类域名天然可解析;真正卡住的,是 Go 代码默认调用方式根本拿不到 Pod IP 列表。
为什么 net.LookupHost 查不到 Headless Service 的所有 Pod IP
net.LookupHost 只查 CNAME 或主机名映射,不请求 A/AAAA 记录。而 Headless Service 不生成聚合 A 记录,CoreDNS 对它的响应是:每个就绪 Pod 返回一条独立的 A 或 AAAA 记录。所以 net.LookupHost("my-svc.default.svc.cluster.local") 大概率返回空或 no such host。
- 必须改用
net.LookupIP("ip", fqdn),且第一个参数固定为"ip"(不是"ip4"或"ip6"),否则双栈环境下会漏掉 IPv6 记录 - 域名必须是完整 FQDN:
"my-svc.default.svc.cluster.local";短名如"my-svc"依赖系统 resolver 的ndots行为,Go 的纯 Go 解析器(PreferGo: true)不支持自动补全 -
net.LookupIP返回的是去重后的[]net.IP,但 Kubernetes 不保证 Pod IP 全局唯一(例如 HostNetwork 场景),业务层需自行校验 IP 是否重复或无效
如何确保 DNS 解析走系统 resolver 而不是纯 Go 实现
Go 默认用 libc resolver(如 glibc 的 getaddrinfo),能读取 /etc/resolv.conf 中的 search 和 ndots,自动补全短域名。但一旦设置 &net.Resolver{PreferGo: true},就会切到纯 Go 解析器,失去这些能力。
- 生产环境强烈建议保持默认(
PreferGo: false),靠系统 resolver + CoreDNS 行为一致性 - 若必须用
PreferGo: true(例如交叉编译无 libc 场景),就得手动拼全域名,或自己实现类似search的补全逻辑 - 验证方式:在 Pod 里执行
cat /etc/resolv.conf,确认search行包含default.svc.cluster.local svc.cluster.local cluster.local
查到 IP 后怎么避免发请求到已终止的 Pod
net.LookupIP 返回的是 DNS 查询那一刻的快照,没有健康状态感知。Pod 可能在 100ms 内被删掉,而 DNS 缓存还没过期。
立即学习“go语言免费学习笔记(深入)”;
- 必须加 TTL 缓存:比如每次查完缓存 30 秒,避免高频刷 DNS;不要无脑每请求都查
- 每次选 IP 前做轻量探测:用
net.DialTimeout("tcp", ip+":port", 500*time.Millisecond)检查端口是否可连,比 HTTP 探针更快更可靠 - 跳过失败 IP,从剩余列表中轮询或随机选一个;别用
rand.Perm全量 shuffle,避免反复建连开销 - 极端情况下(如 CoreDNS 故障),应 fallback 到本地文件缓存(如
/etc/service-discovery/fallback.json),不能让整个服务不可用
Headless Service 本身配置极简(clusterIP: None),难点全在客户端:FQDN、LookupIP、缓存策略、健康探测这四点漏掉任一,都会导致流量打空或连接拒绝。最常被忽略的是“以为查到了 IP 就万事大吉”,其实那只是起点。


















