跳到主要内容

与 Netflix Eureka 集成

Netflix Eureka 是一个基于 REST 的服务注册中心,用于跟踪应用实例及其可用性。通过服务发现,APISIX 会定期获取 Eureka 注册表,并根据状态为 UP 的实例构建上游节点。因此,路由可以跟随注册表的变化,而无需在 APISIX 中维护静态节点列表。

本指南将使用 Spring Cloud Netflix 构建一个独立的 Eureka 服务器,并启动两个示例服务。随后,通过 Eureka REST API 注册这些服务,并配置 APISIX 发现这些服务以及在服务实例之间进行负载均衡。

信息

本教程在同一个 Docker 网络中运行 APISIX、Eureka 和示例服务。生产部署应使用高可用的 Eureka 部署,以及每个 APISIX 实例都能访问的地址。

如果所有服务都在 Kubernetes 中运行,通常不需要 Eureka,因为 Kubernetes 已通过 Service 和 DNS 提供服务发现。

前置条件

  • 安装 Docker
  • 安装 cURLjq,用于发送请求和检查响应。
  • 按照快速入门教程,使用 Docker 启动 APISIX。
  • 如果需要使用 ADC 配置 APISIX,请安装 ADC

启动 Eureka

Netflix 建议通过 Spring Cloud Netflix 运行 Eureka 2.x。使用 Spring Cloud Netflix Starter 构建本地镜像,以便服务器依赖项和构建镜像均使用固定版本。

创建项目目录:

mkdir -p eureka-server/src/main/java/com/example/eureka
cd eureka-server

创建 Maven 项目文件:

cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>eureka-server</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.8</version>
<relativePath />
</parent>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2025.1.3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
EOF

创建服务器应用:

cat > src/main/java/com/example/eureka/EurekaServerApplication.java <<'EOF'
package com.example.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
EOF

创建多阶段容器镜像定义。该文件会构建应用,并将构建产物复制到 Java 运行时镜像中:

cat > Dockerfile <<'EOF'
FROM maven:3.9.12-eclipse-temurin-17@sha256:a0603aab698040d9c94259f379ec0487da1678560748d6c7508483034033c53d AS build
WORKDIR /app
COPY pom.xml .
COPY src src
RUN mvn --batch-mode --no-transfer-progress -Dmaven.test.skip=true package

FROM eclipse-temurin:17.0.20_8-jre-jammy@sha256:ec72ba5962b45ae4e7f96bfb5ebf6eeb34a488b967f937c8e14f0aaec688954f
WORKDIR /app
COPY --from=build /app/target/eureka-server-1.0.0.jar eureka-server.jar
EXPOSE 8761
ENTRYPOINT ["java", "-jar", "/app/eureka-server.jar"]
EOF

构建镜像:

docker build -t apisix-eureka-server:2025.1.3 .

在 APISIX 快速入门网络中启动一个独立的 Eureka 服务器。端口 8761 绑定到回环接口,以便在本地访问 API:

docker run -d \
--name eureka \
--network apisix-quickstart-net \
-p 127.0.0.1:8761:8761 \
-e SERVER_PORT=8761 \
-e EUREKA_CLIENT_REGISTER_WITH_EUREKA=false \
-e EUREKA_CLIENT_FETCH_REGISTRY=false \
-e EUREKA_SERVER_RESPONSE_CACHE_UPDATE_INTERVAL_MS=1000 \
apisix-eureka-server:2025.1.3

这些客户端设置会禁用 Eureka 默认启用的对等注册行为。较短的响应缓存更新间隔可确保实例状态变化时,本地教程能够快速体现变化。

验证注册中心 API 是否可用:

curl --retry 30 --retry-delay 1 --retry-all-errors \
-i "http://127.0.0.1:8761/eureka/apps"

收到 HTTP/1.1 200 响应即表示 Eureka 已就绪。

启动示例 Web 服务

在 APISIX 快速入门网络中启动两个 NGINX 服务。每个服务返回不同的响应,以便观察负载均衡效果。

创建 web1.conf

cat > web1.conf <<'EOF'
events {
worker_connections 1024;
}

http {
access_log off;
server {
listen 80;
location / {
return 200 "Application 1 is running";
}
}
}
EOF

创建 web2.conf

cat > web2.conf <<'EOF'
events {
worker_connections 1024;
}

http {
access_log off;
server {
listen 80;
location / {
return 200 "Application 2 is running";
}
}
}
EOF

启动 web1

docker run -d \
--name web1 \
--network apisix-quickstart-net \
-v "$(pwd)/web1.conf:/etc/nginx/nginx.conf:ro" \
nginx:1.30.4-alpine

启动 web2

docker run -d \
--name web2 \
--network apisix-quickstart-net \
-v "$(pwd)/web2.conf:/etc/nginx/nginx.conf:ro" \
nginx:1.30.4-alpine

在 Eureka 中注册服务

保存 APISIX 快速入门网络中服务容器的地址:

export WEB1_IP="$(
docker inspect \
--format '{{(index .NetworkSettings.Networks "apisix-quickstart-net").IPAddress}}' \
web1
)"

export WEB2_IP="$(
docker inspect \
--format '{{(index .NetworkSettings.Networks "apisix-quickstart-net").IPAddress}}' \
web2
)"

web1 注册为 WEB 应用的第一个实例:

curl "http://127.0.0.1:8761/eureka/apps/WEB" -X POST \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"instance": {
"instanceId": "web1",
"hostName": "$WEB1_IP",
"ipAddr": "$WEB1_IP",
"app": "WEB",
"status": "UP",
"port": {
"\$": 80,
"@enabled": true
},
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 3600
},
"dataCenterInfo": {
"name": "MyOwn",
"@class": "com.netflix.appinfo.InstanceInfo\$DefaultDataCenterInfo"
}
}
}
EOF

web2 注册为第二个实例:

curl "http://127.0.0.1:8761/eureka/apps/WEB" -X POST \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"instance": {
"instanceId": "web2",
"hostName": "$WEB2_IP",
"ipAddr": "$WEB2_IP",
"app": "WEB",
"status": "UP",
"port": {
"\$": 80,
"@enabled": true
},
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 3600
},
"dataCenterInfo": {
"name": "MyOwn",
"@class": "com.netflix.appinfo.InstanceInfo\$DefaultDataCenterInfo"
}
}
}
EOF

应用通常使用 Eureka 客户端进行注册并续订租约。本教程直接调用 REST API,以便在不修改示例服务的情况下测试服务发现行为。每个示例注册请求一小时的租约,确保你在完成教程期间实例保持可用。

验证 Eureka 返回的两个实例状态均为 UP

curl "http://127.0.0.1:8761/eureka/apps/WEB" \
-H "Accept: application/json" | \
jq '[.application.instance[] | {
instanceId,
ipAddr,
port: .port["$"],
status
}]'

将 Eureka 连接到 APISIX

将 Eureka 服务器添加到 APISIX 的 config.yaml 配置文件中。以下命令会删除 YAML 文档结束标记、追加服务发现配置,然后恢复结束标记:

docker exec -i apisix-quickstart sh -c \
'sed -i "/^\.\.\.$/d" /usr/local/apisix/conf/config.yaml &&
cat >> /usr/local/apisix/conf/config.yaml' <<'EOF'
discovery:
eureka:
host:
- http://eureka:8761
prefix: /eureka/
fetch_interval: 5
...
EOF

host:APISIX 查询的 Eureka 服务器地址。请求失败时,APISIX 会尝试另一个已配置的地址。

prefix:Eureka 注册中心 API 的基本路径。

fetch_interval:完整获取注册表的间隔时间,单位为秒。默认值为 30;本教程使用 5,以便更快观察到状态变化。

重新加载 APISIX 以使配置更改生效:

docker exec apisix-quickstart apisix reload

在 APISIX 中创建路由

创建一个路由,并配置上游从 Eureka 发现 WEB 应用:

通过 Admin API 创建路由:

curl -i "http://127.0.0.1:9180/apisix/admin/routes/eureka-web-route" -X PUT \
--data-binary @- <<'EOF'
{
"uri": "/eureka/web/*",
"upstream": {
"service_name": "WEB",
"discovery_type": "eureka",
"type": "roundrobin"
}
}
EOF

收到 HTTP/1.1 201 Created 响应即表示路由已成功创建。

验证服务发现

验证处理该请求的 APISIX 工作进程已发现两个服务实例:

curl -fsS "http://127.0.0.1:9090/v1/discovery/eureka/dump" | \
jq --arg web1 "$WEB1_IP" --arg web2 "$WEB2_IP" -e '
([.services.WEB[] | select(.port == 80) | .host] | sort) ==
([$web1, $web2] | sort)
'

当处理该请求的工作进程的节点集合同时包含两个服务地址时,该命令返回 true。等待一个获取间隔并额外留出少量时间,以便其他工作进程完成更新:

sleep 6

向路由发送多次请求:

for _ in $(seq 1 10); do
curl "http://127.0.0.1:9080/eureka/web/"
echo
done

每个请求应返回以下响应之一。你可能会看到两种响应,顺序可能有所不同:

Application 1 is running
Application 2 is running

在 Eureka 中将 web1 设置为 OUT_OF_SERVICE

curl "http://127.0.0.1:8761/eureka/apps/WEB/web1/status?value=OUT_OF_SERVICE" \
-X PUT

轮询 APISIX Control API,直到处理该请求的工作进程所发现的 WEB 节点集合中仅包含 web2。以下命令最多尝试 20 次,每次间隔一秒;如果服务发现未能收敛,则以非零状态退出:

for _ in $(seq 1 20); do
nodes="$(curl --max-time 2 -fsS \
"http://127.0.0.1:9090/v1/discovery/eureka/dump" | \
jq -r '.services.WEB | map("\(.host):\(.port)") | join(",")')" || nodes=""
[ "$nodes" = "$WEB2_IP:80" ] && break
sleep 1
done

[ "$nodes" = "$WEB2_IP:80" ]

命令成功即表示状态变化已到达某个 APISIX 工作进程。每个 APISIX 工作进程都维护自己的 Eureka 服务发现缓存,因此需要再等待一个获取间隔并额外留出少量时间,以便其他工作进程完成更新:

sleep 6

发送多次请求,并验证所有响应均来自 web2

all_web2=true

for _ in $(seq 1 10); do
response="$(curl -fsS "http://127.0.0.1:9080/eureka/web/")" || {
all_web2=false
break
}

if [ "$response" != "Application 2 is running" ]; then
all_web2=false
break
fi

echo "$response"
done

[ "$all_web2" = true ]

每个响应应为:

Application 2 is running

验证完成后,将 web1 恢复为提供服务状态:

curl "http://127.0.0.1:8761/eureka/apps/WEB/web1/status?value=UP" \
-X DELETE

后续步骤

使用服务发现 Control API 端点检查已发现的服务并排查注册表更新问题。有关更多信息,请参阅 Control API 参考

除 Eureka 外,APISIX 还集成了 HashiCorp Consul、Nacos 和其他服务发现平台。