背景介绍
许多 Apache APISIX 的用户提到,尽管该网关内置了许多强大的插件功能,但在企业环境中仍然会遇到一些需要定制开发的需求,以满足特定场景下的个性化要求。
通常情况下,用户会选择使用 Lua 编写插件,并将其挂载到 APISIX 实例上以供使用。然而,Lua 的受众面相对较窄,尽管易于上手,但要精通却并非易事,并且在 Lua 中实现一些复杂的数据转换逻辑可能会变得非常复杂。此外,Java Plugin Runner 目前只暴露了部分钩子供开发者调用,对于不直接支持的功能,需要修改 Java Plugin Runner 的源码。
下图展示了三种常见的插件使用方式:Lua 插件直接内置在 APISIX 核心中运行,Plugin Runner 通过 RPC 方式与 Java、Golang 等语言的 Plugin Runner 通信,而 WASM 插件则被转换为字节码在 APISIX 核心内部运行。
根据用户反馈,通常用户自定义插件的需求主要集中在数据转换(例如 HTTP 请求参数)和调用外部 API 进行数据处理等方面。鉴于此,本文提出了一种全新的扩展 Apache APISIX 能力的思路:仅使用 Apache APISIX 中内置插件来实现通用的能力配置(例如身份认证、限流限速等),而将需要自定义开发的新的逻辑放在外部服务中。 这个外部服务可以是一个二进制程序,也可以是其它 API 服务,并将其作为 APISIX 的上游,类似于一个中间件一样处理请求与响应,这个思路同样适用于其它 API 网关或代理服务。
场景描述
我们拥有一组上游服务用于提供数据查询服务(本文使用 https://api-ninjas.com/api 作为上游服务)。例如,根据城市名称即可获取最新的天气信息、所在的国家信息(国家 GDP 总值、首都名称、货币单位)。
我们的目标是仅向开发者公开一个通用的请求接口,但能根据参数中的城市名和数据范围(scope)确定开发者想要获取的数据内容。此外,为了保护上游服务不被滥用,我们需要为暴露给开发者的接口添加身份验证服务,只有携带正确 API Key 的请求才会被转发至上游服务。
问题分析
在引入 Node-Red 之前,为了实现上述需求,APISIX 开发者自然而然地会考虑使用 Lua 插件。尽管 Apache APISIX 提供了详细的插件开发文档,但对于业务开发者来说,他们需要学习 Lua 语法和调优技巧,了解 APISIX 对外暴露的不同请求阶段钩子,并在编写参数提取和验证逻辑的同时不断重新加载插件以进行验证。完成测试后,他们还需要将 Lua 插件打包到 APISIX 程序中或分发给所有 APISIX 实例进行挂载。
显然,本文所给出的示例需求仅仅是从客户端请求中解析出特定参数,然后构造请求从不同的上游服务中获取数据,但我们花费了大量时间处理业务编写之外的事务。因此,对于这种参数转换、格式转换或外部调用的逻辑,我们可以采用更轻量、直观的方式来实现,这正是下文中将介绍的 Node-Red 所能解决的问题。
解决方案
Node-Red 是一个功能强大且易于使用的流程编程工具,适用于各种领域的自动化和数据流处理任务。其可视化编程界面、丰富的节点库和灵活的扩展性使得我们可以快速构建出复杂的流程,并实现各种各样的应用场景。以下是部分 Node-Red 提供的节点:
HTTP_IN 节点:可对外暴露一个端点供外部服务调用,我们将此端点作为 APISIX 的上游服务;
Function 节点:允许开发者通过 JavaScript 编写代码函数,用于对输入/输出进行修改、删除等操作;
Switch 节点:允许开发者设定一组条件,当符合某个条件时进入下一个指定节点;
HTTP_Request 节点:可设置 URL 等,在执行整个工作流时通过 Node-Red 向该端点发送数据;
Change 节点:可增加、修改、删除指定对象的指定值;
HTTP_Response 节点:用于返回响应给客户端。
除了以上列出的节点外,Node-Red 还提供了许多其它内置的节点,接下来将为读者展示如何通过 Node-Red 实现上述需求。
示例演示
环境准备
我们将通过容器化的方式部署所需的组件,此示例中使用了 DigitalOcean Droplet 作为服务器资源。
1$ doctl compute ssh-key list
2
3ID Name FingerPrint
425621060 Zhiyuan Ju 2c:84:b7:d8:14:0a:a0:0f:ca:fe:ca:24:06:a4:fe:39
5
6$ doctl compute droplet create \
7 --image docker-20-04 \
8 --size s-2vcpu-4gb-amd \
9 --region sgp1 \
10 --vpc-uuid 646cf2b8-03d8-4f48-b7c8-57cdee60ad27 \
11 --ssh-keys 25621060 \
12 apisix-nodered-docker-ubuntu-s-2vcpu-4gb-amd-sgp1-01
13
14$ doctl compute droplet list
15
16ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
17404094941 apisix-nodered-docker-ubuntu-s-2vcpu-4gb-amd-sgp1-01 143.198.192.64 10.104.0.3 4096 2 80 sgp1 Ubuntu Docker 25.0.3 on Ubuntu 22.04 646cf2b8-03d8-4f48-b7c8-57cdee60ad27 active droplet_agent,private_networking
部署 Apache APISIX
使用 APISIX Quickstart 启动新的 APISIX 实例,具体文档请查阅 https://docs.api7.ai/apisix/getting-started/。
1$ curl -sL https://run.api7.ai/apisix/quickstart | sh
部署 Node-Red
Node-Red 提供了多种部署方式,我们将通过 Docker 快速部署并与现有的环境集成。有关更多部署细节,请参阅官方文档:https://nodered.org/docs/getting-started/。
在部署 Node-Red 时,需要确保将该容器添加到 APISIX 网络中,以确保它可以与 APISIX 进行通信并处理请求。
1$ docker run -d -it -p 1880:1880 -v $PWD/configs/nodered/data:/data --network=apisix-quickstart-net --name mynodered -u Node-Red:dialout nodered/Node-Red
配置 Node-Red
首先,我们需要梳理清楚整个逻辑:
- 当请求从 APISIX 进入 Node-Red 后,Node-Red 需要检查请求中的参数是否存在且合法。如果参数缺失或不合法,则返回相应的错误信息;如果参数合法,则继续到下一个节点。在本示例中,我们约定仅允许查询斯德哥尔摩(
city=stockholm
)和柏林(city=berlin
)两个城市的数据。
- 进入下一个节点后,Node-Red 需要判断请求的目标数据类型。本示例预设了 3 种情况:天气信息(
scope=weather
)、城市所在国家的信息(scope=country
)、城市所在国家的 GDP 总值(scope=gdp
)。
- 当
city
和scope
两个参数都合法后,Node-Red 将根据 scope 的值来确定下一个节点从哪个 API 获取数据。通过设置 URL、Method、Payload、X-API-Key 等信息后,Node-Red 的该节点被触发时将访问相应的端点获取数据。
- 当获取
scope=gdp
的数据时,Node-Red 需要从外部 API 的响应体中解析GDP
的值。这一步可以使用 Change 节点进行提取,也可以通过 Function 节点来实现。
- 流程完成后,Node-Red 将返回处理好的数据给 APISIX,由 APISIX 将响应传递给客户端。下面是最终的 Node-Red 示意图。
创建 APISIX 路由
为了将 Node-Red 服务暴露给客户端,我们需要通过 APISIX 反向代理 Node-Red 所暴露的端点。下面是具体的步骤:
创建一条 APISIX Route,将
mynodered:1880
设置为该 Route 的上游。这样,所有发送到该端点的请求都会被转发至 Node-Red 服务中。启用 Key Authentication 认证方式。这意味着只有携带有效的 API Key 的请求才能通过验证,从而访问 Node-Red 服务。
通过以上步骤,我们可以确保 Node-Red 服务安全地暴露给客户端,并且只有经过授权的用户才能访问。
1$ curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
2{
3 "id": "proxy-global-data-endpoint",
4 "uri": "/global-data",
5 "upstream": {
6 "type": "roundrobin",
7 "nodes": {
8 "mynodered:1880": 1
9 }
10 },
11 "plugins": {
12 "key-auth": {}
13 }
14}'
15
16$ curl -i "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT -d '
17{
18 "username": "tom",
19 "plugins": {
20 "key-auth": {
21 "key": "secret-key"
22 }
23 }
24}'
验证请求
我们将分别尝试几种情况,以验证 APISIX、Node-Red 是否符合预期:
情况一
情况描述:携带错误的 Key 访问 API。
预期结果:由于携带的 API Key 不正确,请求应该被拒绝,并返回相应的错误信息。
1$ curl http://143.198.192.64:9080/global-data -H "apikey: invalid-key" -i
2
3HTTP/1.1 401 Unauthorized
4Date: Mon, 04 Mar 2024 07:47:24 GMT
5Content-Type: text/plain; charset=utf-8
6Transfer-Encoding: chunked
7Connection: keep-alive
8Server: APISIX/3.8.0
9
10{"message":"Invalid API key in request"}
情况二
情况描述:携带正确的 Key 访问 API,但携带不合法的
City
字段。预期结果:由于请求参数不符合要求,应该返回相应的错误信息,指示
City
字段不合法。
1$ curl "http://143.198.192.64:9080/global-data?city=singapore&scope=country" -H "apikey: secret-key" -i
2
3HTTP/1.1 400 Bad Request
4Content-Type: application/json; charset=utf-8
5Content-Length: 69
6Connection: keep-alive
7Access-Control-Allow-Origin: *
8X-Content-Type-Options: nosniff
9ETag: W/"45-IOhgB2XkDHi2Kt4PP42n1xa8Gys"
10Date: Mon, 04 Mar 2024 07:48:02 GMT
11Server: APISIX/3.8.0
12
13{"errorCode":400,"message":"Allowed city Options: Stockholm, Berlin"}
情况三
情况描述:携带正确的 Key 访问 API,携带合法的
City
和Scope
字段,以获取国家数据。预期结果:请求应当成功,并返回该城市所在国家的相关信息。
1$ curl "http://143.198.192.64:9080/global-data?city=stockholm&scope=country" -H "apikey: secret-key" -i
2
3HTTP/1.1 200 OK
4Content-Type: application/json; charset=utf-8
5Content-Length: 947
6Connection: keep-alive
7Access-Control-Allow-Origin: *
8X-Content-Type-Options: nosniff
9ETag: W/"3b3-XDlm9OHfuUrWH+g42q8L1F2uu/o"
10Date: Mon, 04 Mar 2024 07:48:26 GMT
11Server: APISIX/3.8.0
12
13[{"gdp":556086,"sex_ratio":100.4,"surface_area":438574,"life_expectancy_male":80.8,"unemployment":6.7,"imports":158710,"homicide_rate":1.1,"currency":{"code":"SEK","name":"Swedish Krona"},"iso2":"SE","employment_services":80.7,"employment_industry":17.7,"urban_population_growth":1.1,"secondary_school_enrollment_female":157.9,"employment_agriculture":1.6,"capital":"Stockholm","co2_emissions":37.6,"forested_area":68.9,"tourists":7440,"exports":160538,"life_expectancy_female":84.4,"post_secondary_enrollment_female":82.1,"post_secondary_enrollment_male":52.7,"primary_school_enrollment_female":127.4,"infant_mortality":2,"gdp_growth":2.2,"threatened_species":98,"population":10099,"urban_population":87.7,"secondary_school_enrollment_male":148.1,"name":"Sweden","pop_growth":0.7,"region":"Northern Europe","pop_density":24.6,"internet_users":92.1,"gdp_per_capita":55766.8,"fertility":1.8,"refugees":310.4,"primary_school_enrollment_male":125.8}]
情况四
情况描述:携带正确的 Key 访问 API,携带合法的
City
和Scope
字段,以获取GDP
数据。预期结果:请求应该成功,并返回该城市所在国家的
GDP
数据。
1$ curl "http://143.198.192.64:9080/global-data?city=stockholm&scope=gdp" -H "apikey: secret-key" -i
2
3HTTP/1.1 200 OK
4Content-Type: text/html; charset=utf-8
5Content-Length: 6
6Connection: keep-alive
7Access-Control-Allow-Origin: *
8ETag: W/"6-j8I5kokycgWjCeKC1c2UfJW7AQY"
9Date: Mon, 04 Mar 2024 07:48:48 GMT
10Server: APISIX/3.8.0
11
12556086
通过以上四种情况的验证,我们可以确认 APISIX 和 Node-Red 是按照预期工作的,并且能够正确地处理各种不同的请求情况。
总结
通过一个场景示例,我们引出了需要自定义开发新插件的需求,并在讨论当前主流的方案后,提供了一种新的思路来更巧妙地解决自定义能力开发的问题。
API 请求路由与身份校验:首先,利用 Apache APISIX 的路由功能和身份验证插件,当请求携带的凭证合法时,APISIX 将客户端请求转发至 Node-Red 服务中。
请求处理与转换:在 Node-Red 中,我们创建了一个流程用于处理接收到的 API 请求。通过 HTTP 输入节点接收来自 APISIX 的请求,并对请求参数进行解析和验证,确保参数符合业务需求。
业务逻辑处理:一旦收到有效的请求,我们可以在 Node-Red 中执行业务逻辑。例如,根据参数将请求发送至不同的业务 API 获取数据,并从响应结果中提取出需要的字段。完成这些操作后,再将最终的数据返回给 APISIX。
错误处理与日志记录:在处理过程中,如果出现任何错误或异常情况,我们可以在 Node-Red 中添加错误处理节点,对异常情况进行捕获和处理。同时,我们还可以通过日志记录节点记录处理过程中的关键信息,以便后续排查和分析,这一点在本示例中未展示。
通过将 APISIX 和 Node-Red 结合使用,我们可以以可视化的方式实现一个完整的请求处理流程,包括请求路由、数据处理、业务逻辑等功能,而无需编写复杂的代码或插件。这种灵活、可定制的解决方案可以帮助我们更快速地构建和调整系统功能,提高开发效率,降低开发成本,同时保证系统的稳定性和可扩展性。