共计 5324 个字符,预计需要花费 14 分钟才能阅读完成。
Apache APISIX 提供了 50 多个插件、罕用的几个负载平衡选择器,以及对支流服务发现(如 Nacos 和 DNS)的反对。API 网关和企业外部的业务严密相干,为了满足企业的业务需要,用户通常须要在 Apache APISIX 的根底上增加一些代码,以实现业务所需的性能。如何拓展 Apache APISIX 成了许多用户的独特痛点:在保障 Apache APISIX 安稳运行的前提下,如何增加业务代码,满足理论需要?
本文提供了 Apache APISIX 的拓展指南,旨在为用户提供拓展 Apache APISIX 的一些思路。因为 Apache APISIX 处于高速倒退阶段,版本迭代的频率比拟高,所以本文会以 Apache APISIX 的首个 LTS 版本 v2.10.0 为根底进行阐明。如果你应用的 Apache APISIX 版本低于 2.10.0,可能须要结合实际状况做一些变通。另外,尽管本文只解说了 HTTP 相干的逻辑,然而 TCP 相干的局部,大体上也较为类似。
拓展方向 1:Rewrite 还是 Access?
咱们从申请的生命周期说起:当一个申请进入到 Apache APISIX 时,首先会由 http_access_phase
这个办法进行解决。相熟 OpenResty phases 概念的读者可能会有些纳闷:OpenResty 一共有 6 个 phases,依照执行的先后顺序排列,别离是:rewrite
、access
、before_proxy
、header_filter
、body_filter
和 log
,为什么一上来就是 access
,rewrite
怎么不见了?
Apache APISIX 插件的 phases 概念和 OpenResty 的 phases 概念略有不同。为了晋升 Apache APISIX 的性能,APISIX 插件的 rewrite 办法会在 OpenResty 的 access 阶段中运行。用户仍然可能在插件层面自定义 rewrite
的逻辑,然而在代码层面,rewrite
实际上也是在 access
外面执行。
尽管说 rewrite
的逻辑和 access
的逻辑都在 access phase 外面运行,然而 rewrite
的逻辑还是会比 access
的逻辑先执行。为了防止后续插件执行 rewrite
失败后,没有继续执行 access
,导致 trace 脱漏的问题,必须在rewrite
外面增加 trace 的逻辑。
除了执行的先后顺序外,rewrite
和 access
之间还有一个差异,就是它们之间有一个解决 consumer
的逻辑:
plugin.run_plugin("rewrite", plugins, api_ctx)
if api_ctx.consumer then
...
end
plugin.run_plugin("access", plugins, api_ctx)
consumer
代表一种身份。你能够针对不同的 consumer
进行权限管制,比方应用 consumer-restriction
这个插件实现基于角色的权限管制,也就是大家所说的 RBAC。另外,你也能够给不同的 consumer
设置对应的限流策略。
Apache APISIX 外面的鉴权插件(在插件定义外面有 type = "auth"
),须要在 rewrite
阶段选好 consumer
。这里咱们用 key-auth
插件举个例子:
local _M = {
version = 0.1,
priority = 2500,
type = 'auth',
name = plugin_name,
schema = schema,
consumer_schema = consumer_schema,
}
...
function _M.rewrite(conf, ctx)
...
local consumer_conf = consumer_mod.plugin(plugin_name)
if not consumer_conf then
return 401, {message = "Missing related consumer"}
end
local consumers = lrucache("consumers_key", consumer_conf.conf_version,
create_consume_cache, consumer_conf)
local consumer = consumers[key]
if not consumer then
return 401, {message = "Invalid API key in request"}
end
consumer_mod.attach_consumer(ctx, consumer, consumer_conf)
end
鉴权插件的执行逻辑都是类似的:首先从用户输出中获取某组参数,而后依据参数查找对应的 consumer
,最初连同该插件对应的 consumer_conf
附加到 ctx
中。
综上,对于无需在申请晚期阶段执行,且不须要查找 consumer
的插件,倡议把逻辑写到 access
外面。
拓展方向 2:配置服务发现
在执行完 access
之后,咱们就要跟上游(Upstream)打交道了。通常状况下,上游节点是写死在 Upstream 配置上的。不过也能够从服务发现上获取节点来实现 discovery。
接下来咱们会以 Nacos 为例,讲讲怎么实现。
一个动静获取 Nacos 治理的节点的 Upstream 配置如下:
{
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "test_ns",
"group_name": "test_group"
}
}
咱们能够看到其中三个重要的变量:
discovery_type
: 服务发现的类型,"discovery_type": "nacos"
示意应用 Nacos 实现服务发现。service_name
: 服务名称。discovery_args
: 不同的 discovery 特定的参数,Nacos 的特定参数包含:namespace_id
和group_name
。
Nacos discovery 对应的 Lua 代码位于 discovery/nacos.lua
。关上 nacos.lua
这个文件,咱们能够看到它外面实现了几个所需的办法。
一个 discovery 须要实现至多两个办法:nodes
和 init_worker
。
function _M.nodes(service_name, discovery_args)
local namespace_id = discovery_args and
discovery_args.namespace_id or default_namespace_id
local group_name = discovery_args
and discovery_args.group_name or default_group_name
...
end
function _M.init_worker()
...
end
其中nodes
的函数签名曾经明了地展现获取新节点所用的查问参数:service_name
和 discovery_args
。每次申请时,Apache APISIX 都会用这一组查问最新的节点。该办法返回的是一个数组:
{{host = "xxx", port = 12100, weight = 100, priority = 0, metadata = ...},
# priority 和 metadata 是可选的
...
}
而 init_worker
负责在后盾启动一个 timer,确保本地的节点数据能跟服务发现的数据保持一致。
拓展方向 3:配置负载平衡
获取到一组节点后,咱们要依照负载平衡的规定来决定接下来要先尝试哪个节点。如果罕用的几种负载平衡算法满足不了需要,你也能够本人实现一个负载平衡。
让咱们以起码连接数负载平衡为例。对应的 Lua 代码位于 balancer/least_conn.lua
。关上least_conn.lua
这个文件,咱们能够看到它外面实现了几个所需的办法:new
、get
、after_balance
和 before_retry_next_priority
。
new
负责做一些初始化工作。get
负责执行选中节点的逻辑。after_balance
在上面两种状况下会运行:- 每次重试之前(此时 before\_retry 为 true)
- 最初一次尝试之后
before_retry_next_priority
则是在每次尝试完以后一组同 priority 的节点,筹备尝试下一组之前运行。
function _M.new(up_nodes, upstream)
...
return {
upstream = upstream,
get = function (ctx)
...
end,
after_balance = function (ctx, before_retry)
...
if not before_retry then
if ctx.balancer_tried_servers then
core.tablepool.release("balancer_tried_servers", ctx.balancer_tried_servers)
ctx.balancer_tried_servers = nil
end
return nil
end
if not ctx.balancer_tried_servers then
ctx.balancer_tried_servers = core.tablepool.fetch("balancer_tried_servers", 0, 2)
end
ctx.balancer_tried_servers[server] = true
end,
before_retry_next_priority = function (ctx)
if ctx.balancer_tried_servers then
core.tablepool.release("balancer_tried_servers", ctx.balancer_tried_servers)
ctx.balancer_tried_servers = nil
end
end,
}
end
如果没有外部状态须要保护,能够间接借用固定的模板代码(上述代码中,位于省略号以外的内容)来填充 after_balance
和 before_retry_next_priority
这两个办法。
选中节点之后,咱们也能够通过插件的模式增加额定的逻辑。插件能够实现 before_proxy
办法。该办法会在选中节点之后调用,咱们能够在该办法外面记录以后选中的节点信息,这在 trace 外面会有用。
拓展方向 4:解决响应
咱们能够通过 response-rewrite
插件,在 header_filter
和 body_filter
解决上游返回的响应。前一个办法是批改响应头,后一个办法批改响应体。留神 Apache APISIX 的响应解决是流式的,如果header_filter
外面没有批改响应头,响应头就会被先发送进来,到了body_filter
就没方法批改响应体了。
这意味着如果你后续想要批改 body,然而 header 外面又有 Content-Length 之类跟 body 相干的响应头,那么就要提前在 header_filter
外面把这些头改掉。咱们提供了一个辅助办法:core.response.clear_header_as_body_modified
,只须要在 header_filter
调用它就行。
body_filter
也是流式的,而且还会被屡次调用。所以如果你想要获取残缺的响应体,你须要把每次 body_filter
提供的局部响应体给拼起来。在 Apache APISIX master 分支上,咱们提供了一个叫做 core.response.hold_body_chunk
的办法来简化操作,感兴趣的读者能够看看代码。
拓展方向 5:上报日志和监控参数
在申请完结之后,咱们还能够通过 log
办法来做一些清场工作。这一类工作能够分成两类:
- 记录 metrics 指标,比方
prometheus
插件。 - 记录 access log,而后定期上报,比方
http-logger
插件。
如果你感兴趣的话,能够看看这两个插件的 log
办法是怎么实现的:
prometheus
插件文档:https://apisix.apache.org/zh/…http-logger
插件文档:https://apisix.apache.org/zh/…
对于 Apache APISIX
Apache APISIX 是一个动静、实时、高性能的开源 API 网关,提供负载平衡、动静上游、灰度公布、服务熔断、身份认证、可观测性等丰盛的流量治理性能。Apache APISIX 能够帮忙企业疾速、平安地解决 API 和微服务流量,包含网关、Kubernetes Ingress 和服务网格等。
Apache APISIX 落地用户(仅局部)
- Apache APISIX GitHub:https://github.com/apache/apisix
- Apache APISIX 官网:https://apisix.apache.org/
- Apache APISIX 文档:https://apisix.apache.org/zh/…