3. 服务发现与注册实战

在微服务架构中,服务实例的 IP 地址和端口会随着扩缩容、滚动发布、节点漂移而不断变化。如果调用方把目标地址写死在配置文件里,任何一次 Pod 重建都会导致调用失败。服务发现要解决的核心问题就是:让调用方通过一个稳定的逻辑名称找到当前健康的实例集合。在 AWS 上,这个能力由 AWS Cloud Map 提供命名空间与服务注册表,而 EKS 侧则通过 Service、EndpointSlice 以及 CoreDNS 完成集群内的名称解析。本节把两者串起来,讲清楚注册、健康检查与 DNS 解析的完整链路。

一、先分清两条解析路径

在 EKS 中访问一个微服务,实际存在两条并行的发现路径,理解它们的边界是实战的前提。

  • 集群内路径:调用方 Pod 通过 CoreDNS 解析 order-svc.default.svc.cluster.local,得到的是 Kubernetes Service 的 ClusterIP(或 Headless Service 的 Pod IP 列表)。这条路径由 kube-proxy / EndpointSlice 维护,与 Cloud Map 无关。
  • 跨 VPC / 混合路径:集群外的 EC2、Lambda、其他 VPC 中的服务,通过 Cloud Map 的私有 DNS 命名空间解析 order-svc.ns.internal,得到的是注册实例的 IP。这条路径由 Cloud Map 维护。

很多团队误以为“用了 Cloud Map 就不需要 EKS Service”,实际上两者职责不同:EKS Service 负责集群内四层负载均衡与 EndpointSlice 维护,Cloud Map 负责把实例信息暴露给集群外消费者。真正需要打通的是“Pod 就绪 → EndpointSlice 更新 → Cloud Map 注册表同步”这条链路。

EKS 服务注册与发现链路:Pod → EndpointSlice → Cloud Map → DNS 解析 EKS 集群(集群内路径) order-svc Pod 10.0.3.21:8080 readiness: Ready order-svc Pod 10.0.3.22:8080 readiness: Ready order-svc Pod 10.0.3.23:8080 readiness: NotReady EndpointSlice order-svc-abc 10.0.3.21 ready 10.0.3.22 ready 10.0.3.23 notReady Service order-svc ClusterIP 10.100.4.7 port 80 → 8080 CoreDNS cluster .local 集群外消费者(跨 VPC 路径) EC2 应用 调用方 Lambda 调用方 AWS Cloud Map 命名空间 ns.internal(私有) 服务 order-svc 实例 10.0.3.21:8080 HEALTHY 实例 10.0.3.22:8080 HEALTHY 实例 10.0.3.23:8080 UNHEALTHY 注册同步 DNS 查询 就绪 Pod 进入 EndpointSlice,并同步注册到 Cloud Map;未就绪实例不参与解析

二、用 Cloud Map 注册 EKS 服务实例

Cloud Map 的核心对象有三个:命名空间(Namespace)、服务(Service)、实例(Instance)。命名空间决定 DNS 后缀,服务决定逻辑名称,实例决定 IP 与端口。创建命名空间时选择“私有”,并关联 EKS 所在的 VPC,这样只有该 VPC 及其关联网络内的资源能解析。

aws servicediscovery create-private-dns-namespace \
  --name ns.internal \
  --vpc vpc-0a1b2c3d4e5f67890

aws servicediscovery create-service \
  --name order-svc \
  --namespace-id ns-xxxxxxxxxxxx \
  --dns-config "NamespaceId=ns-xxxxxxxxxxxx,RoutingPolicy=WEIGHTED,DnsRecords=[{Type=A,TTL=10}]" \
  --health-check-custom-config FailureThreshold=1

这里有两个关键参数值得说明。RoutingPolicy 选 WEIGHTED 时,Cloud Map 返回全部健康实例的 A 记录,由客户端自行选择;选 MULTIVALUE 时返回随机顺序的多条记录,适合做简单客户端负载均衡。TTL 设为 10 秒是一个折中值:太短会增加 DNS 查询压力,太长会导致实例下线后调用方仍拿到旧 IP。对于频繁扩缩容的服务,建议 10~30 秒。

实例注册有两种方式。第一种是手动注册,适合 EC2 等静态资源:

aws servicediscovery register-instance \
  --service-id srv-xxxxxxxxxxxx \
  --instance-id order-svc-10.0.3.21 \
  --attributes AWS_INSTANCE_IPV4=10.0.3.21,AWS_INSTANCE_PORT=8080

第二种是自动注册,也是 EKS 场景更常用的方式。可以通过 AWS Cloud Map Controller(基于 ACK 或社区控制器)监听 EndpointSlice 变化,把 ready 的地址自动写入 Cloud Map,Pod 被删除时自动注销。这样注册表始终与集群真实状态一致,避免手工维护带来的漂移。

三、健康检查:谁来判断实例是否可用

健康检查在整条链路中有两层,必须区分清楚。

  • Kubernetes 层:Pod 的 readinessProbe 决定它是否进入 EndpointSlice。未就绪的 Pod 不会被 Service 转发流量,也不会被同步到 Cloud Map。
  • Cloud Map 层:如果实例配置了 HealthCheckCustomConfig,Cloud Map 会维护一个 HEALTHY / UNHEALTHY 状态。使用自定义健康检查时,需要由外部进程调用 UpdateInstanceCustomHealthStatus 上报状态。

一个常见的配置示例是给 order-svc 的 Deployment 加上就绪探针:

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
  failureThreshold: 3

当 /healthz 连续 3 次返回非 200,Pod 被标记为 NotReady,EndpointSlice 中该地址的 ready 变为 false,Cloud Map 控制器随即把它从注册表移除或标记为 UNHEALTHY。此时集群外消费者通过 DNS 查询就只会拿到健康实例的 IP。判断链路是否生效,可以依次检查:kubectl get endpointslice -l kubernetes.io/service-name=order-svc 看 ready 字段,再用 aws servicediscovery list-instances --service-id srv-xxx 看 Cloud Map 中的实例状态,两者应当一致。

四、DNS 解析与验证

Cloud Map 私有命名空间创建后,会在关联 VPC 中注入一条 Route 53 私有托管区记录。集群外消费者解析 order-svc.ns.internal 时,会得到当前健康实例的 A 记录集合。验证时不要只看一次解析结果,因为 TTL 和缓存会影响观察:

dig +short order-svc.ns.internal
# 10.0.3.21
# 10.0.3.22

# 缩容后再次解析,确认旧 IP 已消失
kubectl scale deploy order-svc --replicas=1
sleep 15
dig +short order-svc.ns.internal
# 10.0.3.21

如果解析结果与预期不符,按以下顺序排查:确认调用方所在 VPC 已关联该命名空间;确认安全组允许 53 端口 UDP/TCP 出站;确认 Cloud Map 服务中实例状态为 HEALTHY;确认 EndpointSlice 中目标地址 ready 为 true。多数“解析不到”的问题,根源是 Pod 未就绪导致根本没有注册,而不是 DNS 本身故障。

实践建议:把 Cloud Map 的 TTL 与 Kubernetes 的 readinessProbe 周期对齐考虑。若 TTL 为 30 秒而探针每 10 秒检测一次,最坏情况下调用方可能拿到已下线实例的 IP 长达 30 秒。对延迟敏感的调用方,应在客户端加入重试与熔断,而不是单纯依赖 DNS 的实时性。

五、一个可直接对照的配置清单

配置项推荐值(示例)作用
命名空间类型私有 DNS,关联 EKS VPC限定解析范围,避免公网暴露
RoutingPolicyWEIGHTED 或 MULTIVALUE决定返回单条还是多条记录
DNS TTL10~30 秒平衡解析压力与实例变更时效
readinessProbeperiodSeconds 10,failureThreshold 3控制 Pod 何时进入 EndpointSlice
注册方式Cloud Map Controller 自动同步避免手工注册产生漂移
验证命令endpointslice + list-instances + dig三层交叉确认注册与解析一致