Apache APISIX Kubernetes 服务发现:基于 List-Watch 的 Endpoints 实时感知与多集群配置指南

Apache APISIX Kubernetes 服务发现:基于 List-Watch 的 Endpoints 实时感知与多集群配置指南 Apache APISIX Kubernetes 服务发现基于 List-Watch 的 Endpoints 实时感知与多集群配置指南【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix导读本文围绕 Apache APISIX 的 Kubernetes 服务发现能力展开讲解其如何通过 Kubernetes 官方 List-Watch 机制实时感知 Endpoints 资源变化并写入ngx.shared.DICT随后按 APISIX Discovery Specification 向路由提供节点查询接口。你将掌握单集群与多集群两种模式的完整配置、nodes()查询接口的命名规范、ServiceAccount 的 RBAC 权限与 token 获取方式以及通过控制面 API 在线排障的调试手段从而在云原生微服务架构中让 APISIX 自动跟随 Kubernetes 集群内的服务实例变化。Summary工作原理APISIX 的 Kubernetes 服务发现以 Kubernetes 官方 List-Watch 机制为骨架它持续监听集群中 Endpoints 资源的实时变化把监听结果以序列化形式写入共享内存字典ngx.shared.DICT并按照 APISIX Discovery Specification 提供节点查询接口nodes(service_name)供上游upstream在运行时解析实例列表。整体流程可概括为APISIX 中的 Kubernetes 发现模块通过 informer见 informer_factory.lua向 APIServer 发起 List 请求获取全量 Endpoints并携带resourceVersion进入 Watch 长连接Watch 收到ADDED/MODIFIED/DELETED事件后由回调函数见 init.lua 中的on_endpoint_modified/on_endpoint_deleted把变化写入共享字典业务请求到达时路由中的 upstream 通过nodes(service_name)从共享字典读取最新节点列表完成负载均衡。与把上游节点写死在路由配置中不同使用服务发现后Pod 的扩缩容、故障替换都会自动反映到 APISIX 的转发目标中无需人工维护节点列表。How To Use单集群模式配置Kubernetes 服务发现同时支持单集群与多集群两种模式分别适用于服务分布在单个或多个 Kubernetes 集群的场景。单集群模式完整配置以下为单集群模式的详细配置示例discovery: kubernetes: service: # apiserver schema, options [http, https] schema: https #default https # apiserver host, options [ipv4, ipv6, domain, environment variable] host: ${KUBERNETES_SERVICE_HOST} #default ${KUBERNETES_SERVICE_HOST} # apiserver port, options [port number, environment variable] port: ${KUBERNETES_SERVICE_PORT} #default ${KUBERNETES_SERVICE_PORT} client: # serviceaccount token or token_file token_file: /var/run/secrets/kubernetes.io/serviceaccount/token #token: |- # eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif # 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI default_weight: 50 # weight assigned to each discovered endpoint. default 50, minimum 0 # kubernetes discovery support namespace_selector # you can use one of [equal, not_equal, match, not_match] filter namespace namespace_selector: # only save endpoints with namespace equal default equal: default # only save endpoints with namespace not equal default #not_equal: default # only save endpoints with namespace match one of [default, ^my-[a-z]$] #match: #- default #- ^my-[a-z]$ # only save endpoints with namespace not match one of [default, ^my-[a-z]$ ] #not_match: #- default #- ^my-[a-z]$ # kubernetes discovery support label_selector # for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels label_selector: |- firsta,secondb # reserved lua shared memory size,1m memory can store about 1000 pieces of endpoint shared_size: 1m #default 1m # if watch_endpoint_slices setting true, watch apiserver with endpointslices instead of endpoints watch_endpoint_slices: false #default false单集群最小配置与 Pod 内外两种场景如果 APISIX 本身就运行在 Kubernetes Pod 中只需最简配置即可因为 APIServer 地址KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT环境变量与 ServiceAccount token 文件/var/run/secrets/kubernetes.io/serviceaccount/token都由集群自动注入discovery: kubernetes: { }如果 APISIX 运行在集群外部则需要创建一个指定的 ServiceAccount取出其 token 值后手动填写 apiserver 地址与端口discovery: kubernetes: service: schema: https host: # enter apiserver host value here port: # enter apiserver port value here client: token: # enter serviceaccount token value here #token_file: # enter file path here配置项取值与默认值速查结合配置校验 schemaschema.lua各配置项的约束如下配置项取值约束默认值说明service.schemahttp或httpshttpsAPIServer 访问协议service.hostIPv4 / IPv6 / 域名 /${ENV}环境变量${KUBERNETES_SERVICE_HOST}APIServer 地址service.port合法端口号或${ENV}环境变量${KUBERNETES_SERVICE_PORT}APIServer 端口client.token环境变量引用或 1~4096 位 base64 风格字符串无ServiceAccount token 值client.token_file文件路径最长 500 字符/var/run/secrets/kubernetes.io/serviceaccount/token存放 token 的文件default_weight整数最小 050每个发现节点的权重namespace_selectorequal/not_equal/match/not_match四选一无按命名空间过滤label_selector字符串空按标签过滤shared_size形如1m、2m的字符串1m共享内存大小1m 约可存放 1000 条 endpointwatch_endpoint_slices布尔false是否改用 EndpointSlice 监听配置解析的底层实现细节从源码看配置校验与解析遵循以下规则schema 校验service.schema必须是http或httpshost允许环境变量引用形如${VAR}或合法域名port允许环境变量引用或 1~65535 的端口号见 schema.lua。环境变量展开get_apiserverinit.lua通过read_env识别${VAR}形式的值未设置的环境变量会直接报错终止。token 来源二选一client.token与client.token_file至少配置一个若两者都未配置初始化直接失败。token 值会被去除所有空白字符后用于Authorization: Bearer token请求头当schema为https时 token 不允许为空informer_factory.lua。多集群模式下不做默认值填充多集群配置要求每个集群显式填写service与client字段不会像单集群那样自动回退到环境变量默认值。共享内存与 EndpointSlice 说明shared_size预留的是 Lua 共享内存lua_shared_dict kubernetes ...用于存放发现结果单集群使用kubernetes字典多集群按kubernetes-{id}命名init.luastreamL4模式下还会追加-stream后缀。当watch_endpoint_slices为true时informer 改用discovery.k8s.io/v1的EndpointSlice资源init.lua。从on_endpoint_slices_modified回调可以看到端口命名优先取port.name其次targetPort最后才是port且仅当conditions.ready为真时才计入节点init.lua。单集群模式查询接口nodes()Kubernetes 服务发现按照 APISIX Discovery Specification 提供查询接口函数nodes(service_name)描述nodes()从ngx.shared.DICT中查找service_name对应的节点service_name需匹配模式[namespace]/[name]:[portName]namespaceKubernetes Endpoints 所在的命名空间nameKubernetes Endpoints 的名称portNameKubernetes Endpoints 中的ports.name值若没有ports.name则使用targetPort或port代替。注意只要存在ports.name就不能再使用端口号作为portName返回值例如 Kubernetes Endpoints 资源如下apiVersion: v1 kind: Endpoints metadata: name: plat-dev namespace: default subsets: - addresses: - ip: 10.5.10.109 - ip: 10.5.10.110 ports: - port: 3306 name: port调用nodes(default/plat-dev:port)将返回{ { host10.5.10.109, port 3306, weight 50, }, { host10.5.10.110, port 3306, weight 50, }, }查询接口的源码级实现nodes()在源码中有两层实现单集群single_mode_nodes使用正则^(.*):(.*)$拆分namespace/name与portNameinit.lua多集群multiple_mode_nodes使用^(.*)/(.*/.*):(.*)$拆分出id、namespace/name、portNameinit.lua。两者都会先通过get_stale读取{endpoint_key}#version版本号再用 lrucache 以service_name 版本号为键做二级缓存避免每个请求都解析共享内存中的 JSON。worker 进程完全不直接访问 APIServer只读共享内存这也是多 worker 模型下性能可控的关键。Multi-Cluster Mode多集群模式配置以下为多集群模式的详细配置示例discovery: kubernetes: - id: release # a custom name refer to the cluster, pattern ^[a-z0-9]{1,8} service: # apiserver schema, options [http, https] schema: https #default https # apiserver host, options [ipv4, ipv6, domain, environment variable] host: 1.cluster.com # apiserver port, options [port number, environment variable] port: 6443 client: # serviceaccount token or token_file token_file: /var/run/secrets/kubernetes.io/serviceaccount/token #token: |- # eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif # 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI default_weight: 50 # weight assigned to each discovered endpoint. default 50, minimum 0 # kubernetes discovery support namespace_selector # you can use one of [equal, not_equal, match, not_match] filter namespace namespace_selector: # only save endpoints with namespace equal default equal: default # only save endpoints with namespace not equal default #not_equal: default # only save endpoints with namespace match one of [default, ^my-[a-z]$] #match: #- default #- ^my-[a-z]$ # only save endpoints with namespace not match one of [default, ^my-[a-z]$] #not_match: #- default #- ^my-[a-z]$ # kubernetes discovery support label_selector # for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels label_selector: |- firsta,secondb # reserved lua shared memory size,1m memory can store about 1000 pieces of endpoint shared_size: 1m #default 1m # if watch_endpoint_slices setting true, watch apiserver with endpointslices instead of endpoints watch_endpoint_slices: false #default false注意事项多集群模式下discovery.kubernetes是一个数组service与client字段不会填充默认值需要根据各集群的实际配置逐一填写id为每个集群的自定义标识匹配模式^[a-z0-9]{1,8}且在配置中不可重复重复时初始化会直接报错见 init.lua。多集群模式查询接口函数nodes(service_name)描述nodes()从共享字典中查找service_name对应的节点service_name需匹配模式[id]/[namespace]/[name]:[portName]id服务发现配置中定义的集群标识namespaceKubernetes Endpoints 所在的命名空间nameKubernetes Endpoints 的名称portNameKubernetes Endpoints 中的ports.name值若没有ports.name则使用targetPort或port代替存在ports.name时不能使用端口号返回值若 Kubernetes Endpoints 资源如下apiVersion: v1 kind: Endpoints metadata: name: plat-dev namespace: default subsets: - addresses: - ip: 10.5.10.109 - ip: 10.5.10.110 ports: - port: 3306 name: port调用nodes(release/default/plat-dev:port)将返回{ { host10.5.10.109, port 3306, weight 50, }, { host10.5.10.110, port 3306, weight 50, }, }路由中的使用方式在路由的 upstream 中通过service_namediscovery_type即可引用发现结果遵循 APISIX Discovery Specification 的统一约定$ 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: default/plat-dev:port, type: roundrobin, discovery_type: kubernetes } }注意当配置了upstream.service_name时upstream.nodes将不再生效节点列表完全由服务发现模块从注册中心获取。多集群场景下service_name需带上集群id前缀如release/default/plat-dev:port。namespace_selector 与 label_selector 的过滤机制namespace_selector提供四种互斥的过滤方式一次只能使用一种equal仅保存指定命名空间的 Endpoints例如equal: defaultnot_equal排除指定命名空间例如not_equal: defaultmatch仅保存匹配任一正则的命名空间例如[default, ^my-[a-z]$]not_match排除匹配任一正则的命名空间例如[default, ^my-[a-z]$]。从源码看equal与not_equal会被翻译成 Kubernetes 的fieldSelector如metadata.namespacedefault在 List-Watch 请求层面直接过滤而match与not_match则在回调侧用ngx.re.match做正则匹配init.lua匹配规则要求正则必须完整命中整个命名空间名m[0] namespace。label_selector则直接透传为 List-Watch 请求的labelSelector参数见 informer_factory.lua语法遵循 Kubernetes 标签选择器表达式例如firsta,secondb。QA常见问题Q为什么只支持用 token 访问 Kubernetes APIServerA访问 Kubernetes APIServer 通常有三种认证方式mTLSTokenBasic 认证由于当前 HTTP 客户端lua-resty-http尚不支持 mTLS而 Basic 认证又不被推荐因此目前仅实现了 token 认证方式。QAPISIX 继承了 Nginx 的多进程模型是不是每个 nginx worker 进程都会 List-Watch Kubernetes endpoints 资源A不是。Kubernetes 服务发现只使用特权进程privileged agent来 List-Watch Kubernetes Endpoints 资源并把结果写入ngx.shared.DICTworker 进程只通过查询ngx.shared.DICT获取结果。这一点在源码中有明确体现single_mode_init/multiple_mode_init中通过process.type() ~ privileged agent判断非特权进程直接持有共享字典引用后返回只有特权进程才创建 informer 并启动定时拉取init.lua。QServiceAccount 需要什么权限AServiceAccount 需要集群级别的 endpoints 资源get、list、watch权限声明式定义如下kind: ServiceAccount apiVersion: v1 metadata: name: apisix-test namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: apisix-test rules: - apiGroups: [ ] resources: [ endpoints,endpointslices ] verbs: [ get,list,watch ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: apisix-test roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: apisix-test subjects: - kind: ServiceAccount name: apisix-test namespace: default注意由于配置项watch_endpoint_slices可能开启 EndpointSlice 监听RBAC 中同时授予了endpoints与endpointslices两类资源的权限。Q如何获取 ServiceAccount 的 token 值A假设你的 ServiceAccount 位于命名空间apisix名称为Kubernetes-discovery可按以下步骤获取 token获取 secret 名称执行以下命令输出第一列即为所需的 secret 名称kubectl -n apisix get secrets | grep kubernetes-discovery获取 token 值假设 secret 资源名为kubernetes-discovery-token-c64cv执行以下命令输出即为所需的 ServiceAccount token 值kubectl -n apisix get secret kubernetes-discovery-token-c64cv -o jsonpath{.data.token} | base64 -dDebugging API控制面调试接口Kubernetes 服务发现还提供了控制面 API 用于在线调试其入口定义于 apisix/control/router.lua/v1/discovery/{discovery_type}/dump路由调用发现模块的dump_data()函数。Memory Dump API查看内存中的节点快照要查询/列出 Kubernetes 服务发现已发现的节点可请求/v1/discovery/kubernetes/dump控制面接口GET /v1/discovery/kubernetes/dump响应示例如下{ endpoints: [ { endpoints: [ { value: {\https\:[{\host\:\172.18.164.170\,\port\:6443,\weight\:50},{\host\:\172.18.164.171\,\port\:6443,\weight\:50},{\host\:\172.18.164.172\,\port\:6443,\weight\:50}]}, name: default/kubernetes }, { value: {\metrics\:[{\host\:\172.18.164.170\,\port\:2379,\weight\:50},{\host\:\172.18.164.171\,\port\:2379,\weight\:50},{\host\:\172.18.164.172\,\port\:2379,\weight\:50}]}, name: kube-system/etcd }, { value: {\http-85\:[{\host\:\172.64.89.2\,\port\:85,\weight\:50}]}, name: test-ws/testing } ], id: first } ], config: [ { default_weight: 50, id: first, client: { token: xxx }, service: { host: 172.18.164.170, port: 6443, schema: https }, shared_size: 1m } ] }从响应结构可以看出endpoints数组按集群id组织每个集群下列出共享字典中以namespace/name为键的 JSON 值内部再按端口名分组为host/port/weight三元组config则回显当前生效的发现配置。该接口的数据来自_M.dump_data()init.lua它遍历每个集群的共享字典跳过以#version结尾的版本键后输出实际节点内容非常适合在排查为什么某些节点没有进入负载均衡时快速确认内存快照。源码参考发现模块入口与节点查询 apisix/discovery/kubernetes/init.lua配置 schema 校验 apisix/discovery/kubernetes/schema.luaList-Watch informer 实现 apisix/discovery/kubernetes/informer_factory.lua发现模块统一规范 docs/en/latest/discovery.md控制面调试路由 apisix/control/router.lua其他发现客户端Nacos / Eureka / Consul 等位于 apisix/discovery/ 目录实现模式与 Kubernetes 发现一致可对照阅读【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考