系列导航:Kubernetes 重建迁移教程系列。服务名、命名空间和域名均为通用示例。

把 Nacos 配置中的固定 ClusterIP 改成 Service DNS,只是解决了“配置地址不稳定”的问题。如果 Feign、Gateway 和 RestTemplate 仍通过 Nacos 获取实例列表,注册中心故障仍可能让所有内部调用以“没有可用服务”失败。

本文讨论下一阶段:保留 Nacos 配置中心,把每条调用链的服务发现交给 Kubernetes Service。

先拆开 Nacos 的两个职责

flowchart LR
  A["应用"] --> B["Nacos 配置中心"]
  A --> C["Nacos 服务发现"]
  C --> D["Pod 实例列表"]
  E["目标架构"] --> F["Nacos 继续提供配置"]
  E --> G["Service DNS 提供服务入口"]
  G --> H["EndpointSlice 中的 Ready Pod"]

迁移服务发现不等于同时移除 Nacos 配置中心。把两个变更放在同一个窗口,配置加载、实例注册和调用路由会同时变化,回滚会非常困难。

什么时候适合使用 Service DNS

适合:

  • 调用方和被调用方位于同一个 Kubernetes 集群。
  • 服务已经有稳定的 Service 名和 namespace。
  • Service selector、readiness 和 EndpointSlice 可靠。
  • 不依赖 Nacos metadata 做复杂权重、区域或版本路由。

需要谨慎:

  • 跨集群、跨机房或 Kubernetes 外部实例。
  • 同一服务需要按租户、版本或标签选择实例。
  • 客户端需要感知每个实例才能做特殊负载均衡。
  • 长连接协议,或对每个实例使用固定地址,例如部分 Kafka listener。

Kubernetes Service 的目标就是让客户端不需要跟踪易变 Pod 实例,并通过 EndpointSlice 持续维护后端。Kubernetes Service。

第 1 步:盘点所有服务发现入口

在代码和配置仓库中扫描:

rg -n '@FeignClient|lb://|LoadBalancerClient|DiscoveryClient|NacosDiscovery|spring.cloud.nacos.discovery' .

整理成表:

caller,client_type,service_id,target_namespace,criticality,owner,status
gateway,Gateway,rbac-api,app-test,critical,team-a,pending
order-api,Feign,inventory-api,app-test,normal,team-b,pending

不要一扫描到多少就改多少。先选一个只读、低风险、容易验证的调用链。

第 2 步:证明目标 Service 真实可用

kubectl -n app-test get service rbac-api -o yaml
kubectl -n app-test get endpointslice \
  -l kubernetes.io/service-name=rbac-api -o wide
kubectl -n app-test get pod -l app.kubernetes.io/name=rbac-api -o wide

要求:

  • selector 精确匹配目标 Pod。
  • EndpointSlice 只有 Ready Pod。
  • port 和 targetPort 正确。
  • 至少有两个副本时分布在不同节点。

从调用方 Pod 测试:

kubectl -n gateway exec <gateway-pod> -- \
  getent hosts rbac-api.app-test.svc.cluster.local

kubectl -n gateway exec <gateway-pod> -- \
  curl -fsS http://rbac-api.app-test.svc.cluster.local:8082/<health-path>

这一步不通过时,不要修改 Feign 或 Gateway。

第 3 步:先迁移一个 Feign Client

原来只使用服务名:

@FeignClient(name = "inventory-api")
public interface InventoryClient {
    @GetMapping("/v1/inventory/{id}")
    InventoryDTO find(@PathVariable String id);
}

Spring Cloud OpenFeign 支持通过配置为 Feign Client 提供固定 URL;提供 URL 后,请求不再通过 LoadBalancer 按服务名发现实例。当前版本的具体配置键应以项目所用 Release Train 的官方 OpenFeign 文档为准。

spring:
  cloud:
    openfeign:
      client:
        config:
          inventory-api:
            url: http://inventory-api.app-test.svc.cluster.local:8090
            connectTimeout: 3000
            readTimeout: 5000

Kubernetes Service 本身负责把连接转发到 Ready Endpoint,因此这里不需要客户端再从 Nacos 获取 Pod IP。

第 4 步:为切换保留开关

在 Nacos 配置或 ConfigMap 中保留两套值:

clients.inventory-api.discovery-mode=nacos
clients.inventory-api.kubernetes-url=http://inventory-api.app-test.svc.cluster.local:8090

应用根据明确开关做出选择,而不是在 DNS 失败后悄悄回退到 Nacos。隐式双路径会造成:

  • 同一次故障中不同实例走不同链路。
  • 指标难以判断哪个入口生效。
  • Nacos 问题被自动回退掩盖。

回滚只需要恢复开关并滚动调用方。

第 5 步:灰度一个调用方 Pod

创建 canary Deployment,副本为 1,并只修改它的配置:

kubectl -n <caller-namespace> apply -f caller-canary.yaml
kubectl -n <caller-namespace> rollout status deployment/<caller>-canary --timeout=10m

通过临时 Service、Header 或内部直连将测试请求发给 canary。观察:

kubectl -n <caller-namespace> logs deployment/<caller>-canary --since=15m \
  | grep -Ei 'inventory-api|available server|timeout|refused'

同时验证目标 Service Endpoint 变化时 canary 能继续调用:

kubectl -n app-test rollout restart deployment/inventory-api
kubectl -n app-test rollout status deployment/inventory-api --timeout=10m

第 6 步:迁移 Gateway 路由

原路由:

uri: lb://rbac-api

候选路由:

uri: http://rbac-api.app-test.svc.cluster.local:8082

先复制一条测试 Path 或测试 Host,不直接替换全部生产路由。验证:

  • Path rewrite。
  • Authorization Header。
  • Cookie 和 CORS。
  • 超时和响应体。
  • 目标 Pod 滚动时的连接连续性。

如果 Gateway 仍报 Load balancer does not have available server,说明实际生效的 route 仍是 lb://,或配置没有刷新。

第 7 步:迁移普通 HTTP Client

把基础 URL 放在配置中:

clients.file-storage.base-url=http://file-storage.app-test.svc.cluster.local:8686

客户端必须同时设置:

  • connect timeout。
  • read timeout。
  • 连接池和并发上限。
  • 只对幂等请求设置有限重试。
  • 对可选依赖的明确降级策略。

Service DNS 解决的是服务发现问题,不会自动解决慢依赖和线程堆积。

第 8 步:扩大到完整 Deployment

灰度稳定后:

kubectl -n <caller-namespace> set env deployment/<caller> \
  DISCOVERY_MODE=kubernetes

kubectl -n <caller-namespace> rollout status deployment/<caller> --timeout=10m

检查全部 Pod 的配置来源和日志,避免配置只在新 Pod 上生效。

第 9 步:停止这一服务的 Nacos 注册

只有所有调用方都切换后,才考虑关闭目标服务的 Nacos discovery 注册:

spring:
  cloud:
    nacos:
      discovery:
        register-enabled: false

属性名会随 Spring Cloud Alibaba 版本变化,必须按项目依赖核对。关闭前从代码仓库和 Nacos 订阅关系确认没有遗漏调用方。

Nacos Config 仍可继续工作;不要误删 spring.config.import 或配置 namespace。

第 10 步:模拟 Nacos 服务发现不可用

在隔离环境验证:

  1. 调用方已经完成配置加载。
  2. 暂时阻断其到 Nacos Naming 端口的访问。
  3. 持续请求已经迁移的调用链。
  4. 重启目标业务 Pod,观察 EndpointSlice 更新。

预期:已迁移调用链仍可通过 Service DNS 调用;需要动态配置的功能是否受影响,要单独记录。

回滚

停止扩大范围
→ 恢复 discovery-mode=nacos
→ 滚动调用方
→ 检查 Nacos 中目标服务实例
→ 验证 lb:// 调用

如果已经关闭目标服务注册,应先恢复注册并确认实例健康,再切回调用方。

完成定义

  • 已迁移调用链不再创建 Nacos LoadBalancer Client。
  • Feign/Gateway 生效 URL 为 Service DNS。
  • 目标 Pod 滚动时请求无持续失败。
  • Nacos Naming 暂时不可用时,已迁移调用链仍能工作。
  • Nacos Config 加载与动态刷新仍正常。
  • 所有调用方迁移完成后才关闭目标服务注册。
  • 每条调用链都有独立回滚开关和验证记录。

正确的迁移单位不是“整个 Nacos”,而是一条可验证、可回滚的服务调用链。