Apache APISIX 集成 Consul KV 服务发现:consul_kv 模块配置、数据流与调试实战
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
本篇技术指南围绕 Apache APISIX 内置的consul_kv服务发现模块展开,讲解如何让 APISIX 直接从 Consul 的 KV 存储(而非 Consul 的健康检查注册中心)中拉取上游节点,实现 HTTP(L7)与 TCP/UDP(L4)流量的动态负载均衡。读完本文,你将掌握conf/config.yaml中discovery.consul_kv的完整配置、Consul KV 键值模板的注册方式、reload 场景下的 dump 数据兜底机制,以及通过控制面 API 进行内存与文件级调试的方法。
一、背景与适用场景
consul_kv模块主要面向从 nginx-upsync-module(如微博移动端团队)迁移到 APISIX、且以 Consul KV 作为服务发现数据源的用户。其核心思路是:将服务的节点列表以 KV 形式写入 Consul,APISIX 的consul_kv模块定期(或长轮询)拉取这些 KV 数据,动态刷新内存中的上游节点表,从而在节点增删、权重调整时无需重启或 reload APISIX 即可生效。
从源码结构看,该模块位于 apisix/discovery/consul_kv/,由两个文件组成:
- init.lua:核心实现,负责连接 Consul、解析 KV、更新应用表、dump 读写与控制 API;
- schema.lua:配置项校验与默认值定义。
模块版本号为0.3(见 init.lua)。模块本身不在文档中自带数据流示意图,但结合 init.lua 的实现可以还原其 worker 数据流:worker 0 通过ngx.timer.at启动对每个 Consul server 的连接任务并拉取数据,更新本地applications表后通过 apisix/events.lua 的 pubsub 事件广播给其他 worker,其他 worker 注册事件回调后同步更新自身的内存节点表(见 init.lua 的init_worker与 init.lua 的discovery_consul_callback)。
二、discovery 客户端配置
2.1 启用发现模块
APISIX 会在启动时读取conf/config.yaml中的discovery段,并动态加载对应的发现模块:apisix/discovery/init.lua 遍历local_conf.discovery中的每个 key,执行require("apisix.discovery." .. discovery_name),随后在init_worker阶段依次调用各模块的init_worker()。因此只要在配置中写入了consul_kv段,模块就会被自动启用。
2.2 完整配置示例
在conf/config.yaml中加入以下配置(默认值注释来自 schema.lua):
discovery: consul_kv: servers: - "http://127.0.0.1:8500" - "http://127.0.0.1:8600" token: "..." # 若 Consul 集群开启了 ACL 访问控制,需要指定 token prefix: "upstreams" skip_keys: # 如果需要跳过特殊 key - "upstreams/unused_api/" timeout: connect: 1000 # 默认 2000 ms read: 1000 # 默认 2000 ms wait: 60 # 默认 60 sec weight: 1 # 默认 1 fetch_interval: 5 # 默认 3 sec,仅在 keepalive: false 时生效 keepalive: true # 默认 true,使用长轮询方式查询 consul servers default_server: # 可以定义未命中时的默认 server host: "127.0.0.1" port: 20999 metadata: fail_timeout: 1 # 默认 1 ms weight: 1 # 默认 1 max_fails: 1 # 默认 1 dump: # 需要时,注册节点更新后可以 dump 到文件 path: "logs/consul_kv.dump" expire: 2592000 # 单位秒,这里是 30 天也可以只写最少配置,其余全部走默认值:
discovery: consul_kv: servers: - "http://127.0.0.1:8500"2.3 配置项详解与源码校验
结合 schema.lua 与 init.lua 的format_consul_params,各配置项说明如下:
| 配置项 | 类型/默认值 | 说明 |
|---|---|---|
servers | array,必填,minItems = 1 | Consul server 地址列表,格式必须是http://address:port。源码中对每个地址执行http.parse_uri解析,若 scheme 不是http或路径不是根路径(如带/后缀)会直接报错:only support consul http schema address与invalid consul server address(见 init.lua) |
token | string,默认"" | Consul ACL token,会以token参数附加到每次 KV 请求中(见 init.lua) |
prefix | string,默认"upstreams" | KV 的根前缀,实际请求的 consul key 为/kv/.. prefix(见 init.lua) |
skip_keys | array | 需要跳过的 key 列表,命中后该 key 对应的节点不会被加入节点表(见 init.lua 与parse_instance中的判断) |
timeout.connect | integer,默认 2000(ms) | 连接 Consul 的超时 |
timeout.read | integer,默认 2000(ms) | 读取响应的超时 |
timeout.wait | integer,默认 60(s) | 长轮询阻塞等待时间,keepalive: true时作为wait参数传给 Consul(见 init.lua) |
weight | integer,默认 1,最小值 1 | 节点默认权重,当 KV 值中未显式给出weight时使用(见 init.lua 与 init.lua) |
fetch_interval | integer,默认 3(s),最小值 1 | 仅在keepalive: false的短轮询模式下生效,作为ngx.timer_every的周期参数(见 init.lua) |
keepalive | boolean,默认 true | 见下文 2.4 |
default_server/default_service | object | 未命中任何节点时的兜底节点。注意:文档示例中写作default_server,但 schema 与源码实现中的字段名为default_service(见 schema.lua 与 init.lua),实际配置请使用default_service。其metadata子项fail_timeout、weight、max_fails默认值均为 1 |
dump | object | dump 文件相关配置,见第三节 |
需要特别说明default_service的兜底逻辑:当请求某个服务名但内存中没有对应节点时,_M.nodes()会返回default_service作为唯一节点(见 init.lua),并且其weight会被强制设置为全局weight值(见 init.lua)。测试用例 t/discovery/consul_kv.t 中在 20999 端口起了一个返回missing consul_kv services的兜底 server 来验证该行为。
2.4 keepalive 两种拉取模式
keepalive有两个可选值(这也是官方文档明确推荐的取舍点):
true(默认且推荐):使用**长轮询(long pull)**方式查询 Consul server。源码中会设置args.wait = timeout.wait与args.index = 0,并利用 Consul 响应头X-Consul-Index做增量判断——仅当 index 发生变化时才重新解析 body 并更新节点表,同时把最新的 index 带回下一次请求形成阻塞式长轮询(见 init.lua);false(不推荐):使用**短轮询(short pull)**方式,每次拉取后通过ngx.timer_every(fetch_interval, ...)周期性地重新连接 Consul(见 init.lua),此时可通过fetch_interval控制拉取间隔。
无论哪种模式,每次拉取失败后都会以指数退避(retry_delay从 1 秒起每次乘 4)重试连接(见 init.lua),避免对 Consul 造成瞬时风暴。
三、Dump 数据机制:解决 reload 竞态问题
3.1 为什么需要 dump
在线 reload APISIX 时,consul_kv模块从 Consul 加载数据的速度通常慢于 APISIX 从 etcd 加载路由的速度,因此在 Consul 数据加载成功之前的窗口期,请求可能命中如下错误日志:
http_access_phase(): failed to set upstream: no valid upstream node为此模块引入了dump功能:reload 时会先从 dump 文件加载节点数据兜底;当 Consul 中注册的节点发生更新时,又自动把最新上游节点写入 dump 文件。
3.2 dump 配置项
dump: path: "logs/consul_kv.dump" load_on_init: true expire: 2592000三个可选项的语义:
path:dump 文件保存路径。- 支持相对路径,如
logs/consul_kv.dump; - 支持绝对路径,如
/tmp/consul_kv.bin; - 请确保 dump 文件所在父目录已存在;
- 请确保 APISIX 对 dump 文件具备读写权限,例如
chown www:root conf/upstream.d/。
- 支持相对路径,如
load_on_init:默认true。- 为
true时,启动阶段会先尝试从 dump 文件加载数据(不关心文件是否存在),再向 Consul 拉取; - 为
false时,忽略 dump 文件; - 无论
true还是false,都不需要为 APISIX 预先准备 dump 文件。
- 为
expire:单位秒,用于避免加载过期 dump 数据。- 默认
0,表示永不过期; - 官方推荐
2592000,即 30 天(等于 3600 × 24 × 30)。
- 默认
3.3 源码实现
- 启动加载:
init_worker中若配置了dump且load_on_init为真,会调用read_dump_srvs()(见 init.lua)。该函数读取文件、校验 JSON 结构(必须包含services与last_update字段),并用entity.last_update + expire与当前时间比较判断是否过期,过期则忽略(见 init.lua); - 自动写入:每次 Consul 数据更新成功且配置了
dump时,通过ngx_timer_at(0, write_dump_srvs)异步写文件,内容为{services = applications, last_update = ngx.time(), expire = ...}(见 init.lua 与 init.lua)。
对应的完整行为验证可参考测试用例 t/discovery/consul_kv_dump.t。
四、向 Consul KV 注册 HTTP 服务
4.1 Key / Value 模板
服务注册的键值模板如下:
Key: {Prefix}/{Service_Name}/{IP}:{Port} Value: {"weight": <Num>, "max_fails": <Num>, "fail_timeout": <Num>}- Key 默认以
upstreams作为前缀(对应prefix配置项); Service_Name既可以是简单的服务名(如webpages),也可以带多级路径(如webpages/oneteam/hello),多级路径会被解析为不同的服务名;- 节点实例的 IP 与端口拼接成新的 key 段:
<IP>:<Port>。
源码中的解析正则与之一一对应:"(" .. prefix .. "/.*/)([a-zA-Z0-9.]+):([0-9]+)",即从 key 中提取出服务名、IP与端口三部分(见 init.lua 与 init.lua)。
4.2 注册节点示例
以服务名webpages为例,向 Consul 注册两个节点:
curl \ -X PUT \ -d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \ http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.19.5.12:8000 curl \ -X PUT \ -d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \ http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.19.5.13:80004.3 值解析细节
Consul KV 的 Value 在 HTTP API 中以 Base64 编码返回,模块在parse_instance中会先ngx.decode_base64解码,再core.json.decode解析出 JSON,例如:
- Base64 形态:
IHsid2VpZ2h0IjogMTIwLCAibWF4X2ZhaWxzIjogMiwgImZhaWxfdGltZW91dCI6IDJ9 - 原始内容:
{"weight": 120, "max_fails": 2, "fail_timeout": 2}
解析时还会检查metadata.check_status:若值为false或字符串"false",该节点会被视为不健康而跳过(见 init.lua),这一字段可用于把故障节点在 Consul 侧直接摘除。节点的weight优先取 KV 值中的weight,缺省时回退到全局配置的weight(见 init.lua)。
4.4 多 Consul server 场景
当同一个 key 存在于多个 Consul server 时,为避免混淆,实践中建议把完整的 Consul key URL 路径直接作为服务名(即service_name写成http://<host>:<port>/v1/kv/<prefix>/<service>/的完整形式)。这样不同 server 上的同名服务在 APISIX 侧会被区分为不同的服务名,互不干扰。
五、Upstream 配置与使用
5.1 L7(HTTP)场景
下面示例将 URI 为/*的请求路由到名为http://127.0.0.1:8500/v1/kv/upstreams/webpages/的服务,并通过consul_kv发现客户端解析节点。
:::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": "/*", "upstream": { "service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/", "type": "roundrobin", "discovery_type": "consul_kv" } }'返回格式如下:
{ "node": { "value": { "priority": 0, "update_time": 1612755230, "upstream": { "discovery_type": "consul_kv", "service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/", "hash_on": "vars", "type": "roundrobin", "pass_host": "pass" }, "id": "1", "uri": "/*", "create_time": 1612755230, "status": 1 }, "key": "/apisix/routes/1" } }从返回可以看出,discovery_type: "consul_kv"与service_name是触发动态节点解析的关键字段——APISIX 在请求处理阶段会调用_M.nodes(service_name)获取该服务的实时节点列表(见 init.lua),节点权重等属性随之参与负载均衡。更多用法参见 t/discovery/consul_kv.t。
5.2 L4(四层)场景
consul_kv同样支持在 L4(stream)中使用,配置方式与 L7 类似,只需通过 stream_routes 管理接口创建四层路由:
$ 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", "service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/", "type": "roundrobin", "discovery_type": "consul_kv" } }'注意 L4 场景下额外增加了"scheme": "tcp"字段以指明四层协议。对应的测试见 t/discovery/stream/consul_kv.t。
六、调试 API(控制面)
consul_kv模块通过dump_data()与control_api()两个方法向 APISIX 控制面暴露调试接口(见 init.lua)。控制面路由 apisix/control/router.lua 会为所有 discovery 模块自动拼接/v1/discovery/{模块名}前缀并注册路由,因此实际访问路径为/v1/discovery/consul_kv/...。
6.1 内存 Dump API
GET /v1/discovery/consul_kv/dump该接口返回模块当前的完整运行状态:config为配置快照,services为内存中的全部服务与节点列表(对应dump_data()的实现,见 init.lua)。示例:
# curl http://127.0.0.1:9090/v1/discovery/consul_kv/dump | jq { "config": { "fetch_interval": 3, "timeout": { "wait": 60, "connect": 6000, "read": 6000 }, "prefix": "upstreams", "weight": 1, "servers": [ "http://172.19.5.30:8500", "http://172.19.5.31:8500" ], "keepalive": true, "default_service": { "host": "172.19.5.11", "port": 8899, "metadata": { "fail_timeout": 1, "weight": 1, "max_fails": 1 } }, "skip_keys": [ "upstreams/myapi/gateway/apisix/" ] }, "services": { "http://172.19.5.31:8500/v1/kv/upstreams/webpages/": [ { "host": "127.0.0.1", "port": 30513, "weight": 1 }, { "host": "127.0.0.1", "port": 30514, "weight": 1 } ], "http://172.19.5.30:8500/v1/kv/upstreams/1614480/grpc/": [ { "host": "172.19.5.51", "port": 50051, "weight": 1 } ], "http://172.19.5.30:8500/v1/kv/upstreams/webpages/": [ { "host": "127.0.0.1", "port": 30511, "weight": 1 }, { "host": "127.0.0.1", "port": 30512, "weight": 1 } ] } }注意:该 API 默认监听在控制面端口9090(具体端口以conf/config.yaml中deployment.control配置为准),与 Admin API 的9180端口不同。返回内容同时也印证了 4.4 节的做法——以完整 URL 作为服务名时,同一服务在不同 Consul server 上会呈现为两个独立的服务条目。
6.2 Dump 文件查看 API
模块还提供了查看 dump 文件的控制 API,便于确认磁盘上的兜底数据是否最新、是否过期:
GET /v1/discovery/consul_kv/show_dump_file该接口由control_api()注册(见 init.lua),内部直接读取 dump 文件内容返回(见 init.lua)。示例:
curl http://127.0.0.1:9090/v1/discovery/consul_kv/show_dump_file | jq { "services": { "http://172.19.5.31:8500/v1/kv/upstreams/1614480/webpages/": [ { "host": "172.19.5.12", "port": 8000, "weight": 120 }, { "host": "172.19.5.13", "port": 8000, "weight": 120 } ] }, "expire": 0, "last_update": 1615877468 }其中last_update是最近一次写入 dump 的时间戳,expire为配置的过期时长(0 表示永不过期),可据此判断兜底数据是否仍在有效期内。官方文档说明,未来可能会在此基础上增加更多调试 API。
七、测试用例与验证路径
仓库为consul_kv提供了三个层面的测试,可作为上手与排障的参考:
- t/discovery/consul_kv.t:L7 场景的核心测试,覆盖了完整配置(多 server、
prefix、skip_keys、timeout、weight、keepalive、default_service)以及带 ACL token 的配置变体,并起多个本地 mock server(30511~30514 端口)验证节点动态更新与兜底服务(20999 端口)行为; - t/discovery/consul_kv_dump.t:dump 文件的读写、过期与 reload 兜底行为测试;
- t/discovery/stream/consul_kv.t:L4 场景下 stream route 使用
consul_kv发现节点的测试。
八、总结
consul_kv是 APISIX 面向 Consul KV 存储的服务发现方案,适合从 nginx-upsync-module 迁移或习惯以 KV 方式管理节点的团队。其要点可归纳为:
- 配置:在
conf/config.yaml的discovery.consul_kv段配置servers(必填)、prefix、skip_keys、timeout、weight、fetch_interval等,推荐开启keepalive: true长轮询以降低 Consul 压力; - 注册:按
{prefix}/{service_name}/{ip}:{port}的 Key 模板与{"weight","max_fails","fail_timeout"}的 Value 模板写入 KV;多 Consul server 场景建议用完整 URL 作为服务名; - 兜底:配置
dump与default_service,分别解决 reload 竞态导致的no valid upstream node错误和节点未命中时的降级问题; - 调试:通过控制面
9090端口的/v1/discovery/consul_kv/dump与/v1/discovery/consul_kv/show_dump_file两个 API 快速检查内存节点与 dump 文件状态。
更深入的实现细节(长轮询 index 机制、Base64 值解析、事件广播、指数退避重试等)可在 apisix/discovery/consul_kv/init.lua 与 apisix/discovery/consul_kv/schema.lua 中直接阅读源码。
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考