当业务量发生变化时,需要对上游服务进行扩缩容,或者因服务器硬件故障需要更换服务器。如果网关是通过配置来维护上游服务信息,在微服务架构模式下,其带来的维护成本可想而知。再者因不能及时更新这些信息,也会对业务带来一定的影响,还有人为误操作带来的影响也不可忽视,所以网关非常必要通过服务注册中心动态获取最新的服务实例信息。架构图如下所示:
常见的注册中心:Eureka, Etcd, Consul, Nacos, Zookeeper 等
APISIX 要扩展注册中心其实是件非常容易的事情,其基本步骤如下:
apisix/discovery/
目录中添加注册中心客户端的实现;_M.init_worker()
函数以及用于获取服务实例节点列表的 _M.nodes(service_name)
函数;首先,在 apisix/discovery
下创建 eureka
目录;
其次,在 apisix/discovery/eureka
目录中添加 init.lua
;
然后在 init.lua
实现用于初始化的 init_worker
函数以及用于获取服务实例节点列表的 nodes
函数即可:
local _M = { version = 0.1, } function _M.nodes(service_name) ... ... end function _M.init_worker() ... ... end return _M
最后,在 apisix/discovery/eureka
下的 schema.lua
里面提供 YAML 配置的 schema。
APISIX 是通过 upstream.nodes
来配置上游服务的,所以使用注册中心后,通过注册中心获取服务的所有 node 后,赋值给 upstream.nodes
来达到相同的效果。那么 APISIX 是怎么将 Eureka 的数据转成 node 的呢?假如从 Eureka 获取如下数据:
{ "applications": { "application": [ { "name": "USER-SERVICE", # 服务名称 "instance": [ { "instanceId": "192.168.1.100:8761", "hostName": "192.168.1.100", "app": "USER-SERVICE", # 服务名称 "ipAddr": "192.168.1.100", # 实例 IP 地址 "status": "UP", # 状态 "overriddenStatus": "UNKNOWN", # 覆盖状态 "port": { "$": 8761, # 端口 "@enabled": "true" # 开始端口 }, "securePort": { "$": 443, "@enabled": "false" }, "metadata": { "management.port": "8761", "weight": 100 # 权重,需要通过 spring boot 应用的 eureka.instance.metadata-map.weight 进行配置 }, "homePageUrl": "http://192.168.1.100:8761/", "statusPageUrl": "http://192.168.1.100:8761/actuator/info", "healthCheckUrl": "http://192.168.1.100:8761/actuator/health", ... ... } ] } ] } }
解析 instance 数据步骤:
metadata.weight
是否有值,如果没有,则取配置中的 eureka.weight
的值,如果还没有,则取默认值100
;这个例子转成 APISIX nodes 的结果如下:
[ { "host": "192.168.1.100", "port": 8761, "weight": 100, "metadata": { "management.port": "8761" } } ]
首先要在 conf/config.yaml
文件中增加如下配置,添加不同的服务发现客户端,以便在使用过程中动态选择:
discovery: eureka: ...
此名称要与 apisix/discovery/
目录中实现对应注册中心的文件名保持一致。
现已支持注册中心有:Eureka。
在 conf/config.yaml
增加如下格式的配置:
discovery: eureka: host: # it's possible to define multiple eureka hosts addresses of the same eureka cluster. - "http://${username}:${password}@${eureka_host1}:${eureka_port1}" - "http://${username}:${password}@${eureka_host2}:${eureka_port2}" prefix: "/eureka/" fetch_interval: 30 # 从 eureka 中拉取数据的时间间隔,默认 30 秒 weight: 100 # default weight for node timeout: connect: 2000 # 连接 eureka 的超时时间,默认 2000ms send: 2000 # 向 eureka 发送数据的超时时间,默认 2000ms read: 5000 # 从 eureka 读数据的超时时间,默认 5000ms
通过 discovery.eureka.host
配置 eureka 的服务器地址。
如果 eureka 的地址是 http://127.0.0.1:8761/
,并且不需要用户名和密码验证的话,配置如下:
discovery: eureka: host: - "http://127.0.0.1:8761" prefix: "/eureka/"
APISIX 是通过 upstream.discovery_type
选择使用的服务发现,upstream.service_name
与注册中心的服务名进行关联。下面是将 URL 为 “/user/*” 的请求路由到注册中心名为 “USER-SERVICE” 的服务上例子:
:::note
您可以这样从 config.yaml
中获取 admin_key
并存入环境变量:
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
:::
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d ' { "uri": "/user/*", "upstream": { "service_name": "USER-SERVICE", "type": "roundrobin", "discovery_type": "eureka" } }' HTTP/1.1 201 Created Date: Sat, 31 Aug 2019 01:17:15 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Server: APISIX web server {"node":{"value":{"uri":"\/user\/*","upstream": {"service_name": "USER-SERVICE", "type": "roundrobin", "discovery_type": "eureka"}},"createdIndex":61925,"key":"\/apisix\/routes\/1","modifiedIndex":61925}}
因为上游的接口 URL 可能会有冲突,通常会在网关通过前缀来进行区分:
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d ' { "uri": "/a/*", "plugins": { "proxy-rewrite" : { "regex_uri": ["^/a/(.*)", "/${1}"] } }, "upstream": { "service_name": "A-SERVICE", "type": "roundrobin", "discovery_type": "eureka" } }' $ curl http://127.0.0.1:9180/apisix/admin/routes/2 -H "X-API-KEY: $admin_key" -X PUT -i -d ' { "uri": "/b/*", "plugins": { "proxy-rewrite" : { "regex_uri": ["^/b/(.*)", "/${1}"] } }, "upstream": { "service_name": "B-SERVICE", "type": "roundrobin", "discovery_type": "eureka" } }'
假如 A-SERVICE 和 B-SERVICE 都提供了一个 /test
的接口,通过上面的配置,可以通过 /a/test
访问 A-SERVICE 的 /test
接口,通过 /b/test
访问 B-SERVICE 的 /test
接口。
注意:配置 upstream.service_name
后 upstream.nodes
将不再生效,而是使用从注册中心的数据来替换,即使注册中心的数据是空的。
eureka 服务发现也支持在四层中使用,配置方式与七层的类似。
$ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d ' { "remote_addr": "127.0.0.1", "upstream": { "scheme": "tcp", "discovery_type": "eureka", "service_name": "APISIX-EUREKA", "type": "roundrobin" } }' HTTP/1.1 200 OK Date: Fri, 30 Dec 2022 03:52:19 GMT Content-Type: application/json Transfer-Encoding: chunked Connection: keep-alive Server: APISIX/3.0.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: * Access-Control-Max-Age: 3600 X-API-VERSION: v3 {"key":"\/apisix\/stream_routes\/1","value":{"remote_addr":"127.0.0.1","upstream":{"hash_on":"vars","type":"roundrobin","discovery_type":"eureka","scheme":"tcp","pass_host":"pass","service_name":"APISIX-EUREKA"},"id":"1","create_time":1672106762,"update_time":1672372339}}