4. Service发现与DNS
Service 创建之后,Pod 之间如何通过一个稳定的名字找到它,而不是依赖随时会变的 ClusterIP?答案在集群 DNS。Kubernetes 默认部署 CoreDNS 作为集群 DNS 服务器,它监听 Service 的 ClusterIP,并借助 API Server 的 watch 机制实时感知 Service、Endpoints、Pod 的变化,把服务名解析成可用的地址。理解 CoreDNS 的工作方式,是掌握服务发现的前提。
CoreDNS 的工作原理
CoreDNS 以 Deployment 形式运行在 kube-system 命名空间,通常有 2 个副本,前面挂一个名为 kube-dns 的 ClusterIP Service(名字沿用历史,实际后端是 CoreDNS)。每个 Pod 的 /etc/resolv.conf 由 kubelet 在创建时写入,指向这个 ClusterIP:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
CoreDNS 的核心是插件链。请求进入后依次经过 errors、health、kubernetes、forward、cache 等插件。kubernetes 插件负责集群内部域名,它通过 informer 缓存 Service 与 Endpoints 对象;forward 插件把非集群域名(如 www.example.com)转发给上游 DNS(通常是节点上的 /etc/resolv.conf 或配置的 forward . 8.8.8.8)。cache 插件默认缓存 30 秒,降低 API 压力。
当客户端查询 my-svc.default.svc.cluster.local 时,CoreDNS 在缓存中查找对应 Service。若为普通 ClusterIP Service,返回其 ClusterIP 的 A 记录;若为 Headless Service,则返回所有匹配 Pod 的 A 记录。整个过程不经过 kube-proxy,DNS 解析与流量转发是两条独立路径。
服务 FQDN 的构成
Kubernetes 中 Service 的完整域名(FQDN)遵循固定格式:
<service-name>.<namespace>.svc.<cluster-domain>
例如 web.default.svc.cluster.local。其中 cluster.local 是默认集群域,可在 kubelet 的 --cluster-domain 与 CoreDNS Corefile 中修改。Pod 的 search 域让短名也能解析:在 default 命名空间内,直接写 web 即可,解析器会依次尝试 web.default.svc.cluster.local、web.svc.cluster.local、web.cluster.local。跨命名空间访问必须写 web.prod 或完整 FQDN,否则会落到本命名空间。
search 域和 ndots:5。当查询名中点的数量少于 5 时,解析器会先拼接 search 域再查询,这也是集群内短名能生效的原因。Headless Service 与 SRV 记录
把 Service 的 clusterIP 设为 None,就得到 Headless Service。它不分配虚拟 IP,kube-proxy 也不为其建立转发规则。CoreDNS 对 Headless Service 的处理是:直接返回后端所有就绪 Pod 的 IP 列表(A 记录),客户端拿到全部地址后自行选择。这适合有状态应用,例如 StatefulSet 需要每个 Pod 有稳定网络标识的场景。
Headless Service 还支持 SRV 记录,用于暴露每个 Pod 的端口信息。查询 _http._tcp.web.default.svc.cluster.local 会返回形如:
_http._tcp.web.default.svc.cluster.local. 30 IN SRV 0 100 8080 web-0.web.default.svc.cluster.local.
_http._tcp.web.default.svc.cluster.local. 30 IN SRV 0 100 8080 web-1.web.default.svc.cluster.local.
SRV 记录包含优先级、权重、端口和目标主机名,客户端可据此发现每个实例的地址与端口,而不必硬编码。配合 StatefulSet 的稳定 Pod 名(web-0、web-1),每个实例都有可预测的 FQDN,例如 web-0.web.default.svc.cluster.local。
客户端发现模式
应用侧的服务发现主要有三种模式。第一种是环境变量注入:Pod 启动时,kubelet 会把同命名空间内已存在 Service 的 ClusterIP 和端口写入环境变量,如 WEB_SERVICE_HOST、WEB_SERVICE_PORT。这种方式简单,但要求 Service 必须先于 Pod 创建,且无法感知后续变化,已逐渐被 DNS 取代。
第二种是DNS 解析:客户端直接查询 Service FQDN,拿到 ClusterIP 或 Pod IP 列表。这是最通用的方式,配合客户端库的缓存与重试即可工作。第三种是SRV 查询:客户端查询 SRV 记录,同时获得地址和端口,适合需要动态发现实例列表的场景,如 gRPC 的命名解析器。
下面这张图展示了从客户端发起解析到 CoreDNS 返回结果的完整链路,以及普通 Service 与 Headless Service 在返回记录上的差异。
实际验证与排障要点
验证 DNS 是否正常,最直接的方式是在 Pod 内执行解析命令。例如启动一个临时 Pod:
kubectl run dnstest --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl exec -it dnstest -- nslookup web.default.svc.cluster.local
kubectl exec -it dnstest -- nslookup web-0.web.default.svc.cluster.local
若返回 Address: 10.96.x.x 说明普通 Service 解析正常;若 Headless Service 返回多个 Pod IP,说明 Endpoints 已就绪。查询 SRV 记录可用:
kubectl exec -it dnstest -- nslookup -type=SRV _http._tcp.web.default.svc.cluster.local
常见故障有三类。第一,解析超时或 connection timed out,通常是 CoreDNS Pod 未就绪或 kube-dns Service 的 Endpoints 为空,用 kubectl get pods -n kube-system -l k8s-app=kube-dns 和 kubectl get endpoints kube-dns -n kube-system 检查。第二,短名解析失败,检查 Pod 的 resolv.conf 中 search 域是否包含目标命名空间,跨命名空间必须写全名。第三,Headless Service 返回空记录,说明没有就绪的 Pod 匹配 selector,检查 kubectl get endpoints <svc-name> 是否为空,以及 Pod 的 readinessProbe 是否通过。
cache 插件默认缓存 30 秒。Service 变更后,客户端可能仍拿到旧记录,排障时可用 kubectl exec 进入 CoreDNS Pod 查看日志,或临时调低缓存 TTL 验证。生产环境不建议直接关闭缓存。掌握 CoreDNS 的解析路径、FQDN 规则、Headless Service 与 SRV 记录的差异,以及三种客户端发现模式的适用边界,就能在服务间通信出现问题时快速定位是 DNS 解析、Endpoints 就绪还是客户端配置的问题,而不是盲目重启组件。