下面介绍 Kubernetes 的服务发现原理与实现路径。前提是同一集群内的 Pod 能互相通信,并且跨主机通信也能正常进行。这些能力来自底层网络方案,如 CNI 网桥、VXLAN/host-gw 等,但在此不赘述具体实现细节。

在 Pod 全部互通的基础上,我们可以通过访问 Pod IP 来调用 Pod 上的资源。不过 Pod 的 IP 并非固定;并且在一组 Pod 实例之间通常需要负载均衡。因此,Kubernetes 的 Service 对象应运而生,用于解决这类问题。
集群内通信与 Endpoints
Service 的核心目标是实现集群内的通信能力。先创建一个普通的 Deployment,示例应用会返回自身的 hostname,且所有 Pod 带有 app=hostnaMes 的标签。
随后为这些 Pod 创建一个普通的 Service。通过 labelSelector 选择带有相应标签的 Pod,被选中的 Pod 就会构成 Endpoints。可以通过如下方式查看 Endpoints:
~/cloud/k8s kubectl get ep hostnaMes NAME ENDPOINTS hostnaMes 172.28.21.66:9376,172.28.29.52:9376,172.28.70.13:9376
当某个 Pod 出现故障、未处于 Running 状态或 ReadinessProbe 未通过时,Endpoints 列表会发生相应变动。
ClusterIP 与默认行为
在前述基础上,默认创建的 Service 类型为 ClusterIP。查看之前创建的 Service,可以看到其 ClusterIP 地址,例如 10.212.8.127,此时集群内通过该地址即可访问 Endpoints 列表中的任意 Pod。
在命令行中可以验证:
sh-4.2# curl 10.212.8.127 hostnaMes-8548b869d7-9qk6b
再次访问相同 Service 地址,得到的 Pod 可能不同,说明 ClusterIP 模式下对请求进行了轮询分发(Round Robin)。
此时,Service 还会对外提供一个 A 记录,形如 service-name.namespace.svc.cluster.local,指向 ClusterIP:
sh-4.2# nslookup hostnaMes.coops-dev.svc.cluster.local
Server: 10.212.0.2 Address: 10.212.0.2#53 Name: hostnaMes.coops-dev.svc.cluster.local Address: 10.212.8.127
通过该 A 记录访问,效果与直接访问 ClusterIP 相同:
sh-4.2# curl hostnaMes.coops-dev.svc.cluster.local hostnaMes-8548b869d7-wzksp
那么 Pod 的 A 记录是什么呢?可以查看:
sh-4.2# nslookup 172.28.21.66 66.21.28.172.in-addr.arpa Name = 172-28-21-66.hostnaMes.coops-dev.svc.cluster.local. Handleless Service
默认情况下,ClusterIP 是 Kubernetes 自动分配的,也可以手动设置。当将 ClusterIP 设置为 None 时,就变成了 Headless Service。
Headless Service 通常与 StatefulSet 配合使用。StatefulSet 通过为每个 Pod 指定固定的名称,提供了一个不可变的网络标识,因此不再仅仅通过虚拟 IP 进行访问,而是直接定位到具体的 Pod。
使用 DNS 直接访问 Pod
在 Headless Service 场景下,可以借助 DNS 形成每个 Pod 的唯一访问入口。每个 Pod 都会有一条 A 记录,形如 pod-name.service-name.namespace.svc.cluster.local,指向 podIP,从而直接访问到该 Pod。
下面给出一个示例,展示 StatefulSet 和 Service 的关系:
— APIVersion: apps/v1 Kind: StatefulSet Metadata: name: hostnaMes spec: serviceName: “hostnaMes” selector: matchLabels: app: hostnaMes replicas: 3 template: metadata: labels: app: hostnaMes spec: containers: – name: hostnaMes image: MIRROR Google containers/serv_hostnaMe ports: – containerPort: 9376 — APIVersion: v1 Kind: Service Metadata: name: hostnaMes spec: selector: app: hostnaMes clusterIP: None ports: – name: default protocol: TCP port: 80 targetPort: 9376
如上所示,StatefulSet 与 Deployment 的差别主要在于 spec.serviceName 的使用。该字段指示控制器在逻辑处理中采用 hostnaMes 这个 Service,来确保 Pod 的唯一可解析性。
应用 apply 之后,可以看到对应的 Pod 逐步生成并进入 Running 状态,Pod 名称会按序号递增,名称不会像 Deployment 那样在重启后变化。这也是 StatefulSet 的一个关键特性:Pod 的名称不可变,借此实现通过固定的 DNS 记录访问具体的 Pod。
接下来查看 Pod 的 A 记录:
sh-4.2# nslookup hostnaMes-0.hostnaMes Server: 10.212.0.2 Address: 10.212.0.2#53 Name: hostnaMes-0.hostnaMes.coops-dev.svc.cluster.local Address: 172.28.3.57
sh-4.2# nslookup hostnaMes-1.hostnaMes Server: 10.212.0.2 Address: 10.212.0.2#53 Name: hostnaMes-1.hostnaMes.coops-dev.svc.cluster.local Address: 172.28.3.58
