写在最前

我们计划实现 Eureka 与 Nacos 之间的双向同步功能。然而,nacos-sync 官方版本默认未提供登录认证机制,这意味着在管理控制台中配置 Eureka 与 Nacos 集群时,包含的敏感信息(如密码)将直接暴露,存在较大的安全风险。为了避免这一隐患,我们参考了上一章节 Eureka 的认证方案,引入 spring-boot-starter-security,为 nacos-sync 增加统一的登录认证功能,从而保障敏感配置和管理接口的安全性。

1. docker 部署

2. kubernetes 部署

2.1 Dockerfile

具体操作可以参考 4. 步骤的代码变更。基本流程分为两步:

  1. 下载官方原始 https://github.com/nacos-group/nacos-sync/releases/download/0.5.0/nacos-sync-0.5.0.tar.gz

    • 获取官方发布的 nacos-sync-*.tar.gz 包。

    • 解压后删除原有的 nacos-sync-service.jar

  2. 重新打包覆盖 JAR

    • 在本地修改或重新编译 nacossync-worker 模块(比如加入 spring-boot-starter-security 依赖)。

    • 通过 4. 步骤的源码重新打包编译,生成包含 Spring Security 认证的 nacos-sync-service.jar,然后使用 tar -cf 命令将新的 JAR 覆盖回原始目录结构,重新打包成 tar.gz。

docker buildx build --push --platform linux/amd64,linux/arm64 -t tanqidi/nacos-sync:0.5.0 .

FROM openjdk:17-jdk

WORKDIR /app

COPY nacos-sync-0.5.0.tar.gz /app/

RUN tar -xf nacos-sync-0.5.0.tar.gz && \
    rm nacos-sync-0.5.0.tar.gz

EXPOSE 8080

# 直接前台启动 jar
CMD ["java", "-jar", "/app/nacos-sync/nacos-sync-server.jar", "--spring.config.location=/app/nacos-sync/conf/application.properties"]

2.2 service

使用nodeport来试验访问

kind: Service
apiVersion: v1
metadata:
  name: nacos-sync
  namespace: bx
  labels:
    app: nacos-sync
  annotations:
    kubesphere.io/creator: admin
spec:
  ports:
    - name: http-8080
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30195
  selector:
    app: nacos-sync
  type: NodePort
  sessionAffinity: None
  externalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  internalTrafficPolicy: Cluster

2.3 configmap

注意要变更成为你的数据库连接地址与账号密码,最重要的是security认证页面的账号密码也改一下。

https://github.com/nacos-group/nacos-sync/blob/0.5.0/nacossync-distribution/bin/nacosSync.sql

kind: ConfigMap
apiVersion: v1
metadata:
  name: nacos-sync-config
  namespace: bx
  annotations:
    kubesphere.io/creator: admin
data:
  application.properties: >-
    server.port=8080

    server.servlet.context-path=/


    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

    spring.jpa.hibernate.ddl-auto=update

    spring.jpa.properties.hibernate.show_sql=false


    spring.cloud.discovery.enabled=false

    spring.main.allow-circular-references=true


    spring.datasource.url=jdbc:mysql://172.31.0.99:3306/nacos_sync?characterEncoding=utf8

    spring.datasource.username=root

    spring.datasource.password=123456

    management.endpoints.web.exposure.include=*

    management.endpoint.health.show-details=always


    # 配置security账号密码

    spring.security.user.name=admin

    spring.security.user.password=35274E93-D57F-4B6A-8B48-659604AC0811

2.4 deployment

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nacos-sync
  namespace: bx
  annotations:
    deployment.kubernetes.io/revision: '1'
    kubesphere.io/creator: admin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nacos-sync
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nacos-sync
      annotations:
        kubesphere.io/creator: admin
        kubesphere.io/imagepullsecrets: '{}'
        logging.kubesphere.io/logsidecar-config: '{}'
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: nacos-sync-config
            items:
              - key: application.properties
                path: application.properties
            defaultMode: 420
      containers:
        - name: nacos-sync
          image: 'tanqidi/nacos-sync:0.5.0'
          ports:
            - name: http-8080
              containerPort: 8080
              protocol: TCP
          resources: {}
          volumeMounts:
            - name: config-volume
              readOnly: true
              mountPath: /app/nacos-sync/conf/application.properties
              subPath: application.properties
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          imagePullPolicy: Always
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      dnsPolicy: ClusterFirst
      securityContext: {}
      schedulerName: default-scheduler
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

3. 镜像成品

我已使用 Docker Buildx 构建了同时支持 amd64arm64 架构的镜像,便于在不同平台运行,可按需优化或扩展。

  • tanqidi/nacos-sync:0.5.0

4. 代码变更

https://github.com/nacos-group/nacos-sync/tree/0.5.0

代码其实还是有点复杂的,需要在 nacossync-worker 模块的 pom.xml 中引入 spring-boot-starter-security 依赖,重新打包生成 JAR 即可启用登录认证功能。

具体代码中我还对其进行了自定义扩展,实现了 Nacos 使用通配符 * 可全量同步到 Eureka,以及 Eureka 使用通配符 * 可全量同步到 Nacos 的能力。

5. 使用方式

5.1 集群配置

在配置 Eureka 时,集群名称设为 eureka,集群类型选择 eureka。集群 IP 列表直接填写在 Kubernetes 中部署的 Eureka 集群的 VIP 地址即可。由于我这里使用的是 NodePort 方式,便于在 Windows 环境下调试。需要注意的是,之前我们已经启用了 账号密码认证模式,因此在连接地址 http:// 中必须携带账号和密码信息。

image-fpYO.png

在接入 Nacos 时,集群名可以自定义,集群类型选择 nacos 即可。Nacos 本身支持账号密码认证,直接填写你的 Nacos 用户名和密码即可。如果对 namespace 有同步需求,可以填写需要同步的 namespace;否则保持默认即可。至于集群地址,只需要配置 Kubernetes 集群内的 VIP:8848 即可。为了方便在 Windows 环境下调试,我这里使用的是 NodePort,实际生产环境推荐使用 VIP。

image-RxaH.png

5.2 服务同步

在服务同步配置中,如果服务名填写 *,则表示会同步所有服务;分组填写的是前面配置的注册中心 namespace;源集群选择 Nacos,目标集群选择 Eureka,即可实现将该 namespace 下的所有服务从 Nacos 同步到 Eureka。

image-qyPy.png

6. 拓展知识

Eureka 体系中,服务注册中心本身只负责维护服务实例的上下线信息,并不会直接参与请求的负载均衡与重试。

当某个服务(如 aaa-service)有多个实例运行时,调用方(如 bbb-service)会从 Eureka 拿到服务实例列表,并在本地缓存。即便某些实例已经下线,Eureka 的控制台上仍可能短时间显示它们,这是由于 心跳超时与剔除周期的延迟机制。在这一时间窗口内,请求仍有可能被分配到下线实例,从而导致调用失败或超时。

不过,真正的 负载均衡与失败重试能力 来自客户端侧的组件:

  • 早期使用 Ribbon(RestTemplate、Feign 默认依赖 Ribbon);

  • 新版 Spring Cloud 使用 Spring Cloud LoadBalancer

  • Feign 调用底层也依赖上述负载均衡器。

当请求落到不可用实例时,这些组件会尝试自动切换到存活的实例,保障调用的可用性。
因此,Eureka 管“谁在线”,负载均衡组件管“怎么调度”,两者配合才能实现服务发现与高可用调用。

写在最后

这部分代码相对复杂,我采用 Cursor 编码的方式进行调整,实现了 Nacos 和 Eureka 双向通配符 * 全量同步 的功能。虽然能够正常运行,但逻辑比较粗糙:每隔 30 秒强制更新一次所有实例,因此无法做到实时同步,会有十几到二十秒的延迟。

为了进一步保证稳定性,我还利用 Kubernetes 定时任务,每隔 5 分钟重启一次对应的 Deployment,以防出现意料之外的问题,使服务始终保持在“新建”的状态。

哈哈目前该组件运行良好,已达到预期效果,稳定在运行。