轻松实现定制化需求:Apache APISIX 和 Node-Red 的联合利用

更新时间 3/6/2024

背景介绍

许多 Apache APISIX 的用户提到,尽管该网关内置了许多强大的插件功能,但在企业环境中仍然会遇到一些需要定制开发的需求,以满足特定场景下的个性化要求。

通常情况下,用户会选择使用 Lua 编写插件,并将其挂载到 APISIX 实例上以供使用。然而,Lua 的受众面相对较窄,尽管易于上手,但要精通却并非易事,并且在 Lua 中实现一些复杂的数据转换逻辑可能会变得非常复杂。此外,Java Plugin Runner 目前只暴露了部分钩子供开发者调用,对于不直接支持的功能,需要修改 Java Plugin Runner 的源码。

下图展示了三种常见的插件使用方式:Lua 插件直接内置在 APISIX 核心中运行,Plugin Runner 通过 RPC 方式与 Java、Golang 等语言的 Plugin Runner 通信,而 WASM 插件则被转换为字节码在 APISIX 核心内部运行。

plugin and plugin runners of 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

首先,我们需要梳理清楚整个逻辑:

  1. 当请求从 APISIX 进入 Node-Red 后,Node-Red 需要检查请求中的参数是否存在且合法。如果参数缺失或不合法,则返回相应的错误信息;如果参数合法,则继续到下一个节点。在本示例中,我们约定仅允许查询斯德哥尔摩(city=stockholm)和柏林(city=berlin)两个城市的数据。

Use Node-Red to check parameters

  1. 进入下一个节点后,Node-Red 需要判断请求的目标数据类型。本示例预设了 3 种情况:天气信息(scope=weather)、城市所在国家的信息(scope=country)、城市所在国家的 GDP 总值(scope=gdp)。

Use Node-Red to target data type

  1. cityscope 两个参数都合法后,Node-Red 将根据 scope 的值来确定下一个节点从哪个 API 获取数据。通过设置 URL、Method、Payload、X-API-Key 等信息后,Node-Red 的该节点被触发时将访问相应的端点获取数据。

Choose API for obtaining data according to scope

  1. 当获取 scope=gdp 的数据时,Node-Red 需要从外部 API 的响应体中解析 GDP 的值。这一步可以使用 Change 节点进行提取,也可以通过 Function 节点来实现。

Parse value of GDP from external API's response body

  1. 流程完成后,Node-Red 将返回处理好的数据给 APISIX,由 APISIX 将响应传递给客户端。下面是最终的 Node-Red 示意图。

Flow chart of combining APISIX and Node-Red

创建 APISIX 路由

为了将 Node-Red 服务暴露给客户端,我们需要通过 APISIX 反向代理 Node-Red 所暴露的端点。下面是具体的步骤:

  1. 创建一条 APISIX Route,将 mynodered:1880 设置为该 Route 的上游。这样,所有发送到该端点的请求都会被转发至 Node-Red 服务中。

  2. 启用 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,携带合法的 CityScope 字段,以获取国家数据。

  • 预期结果:请求应当成功,并返回该城市所在国家的相关信息。

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,携带合法的 CityScope 字段,以获取 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 是按照预期工作的,并且能够正确地处理各种不同的请求情况。

总结

通过一个场景示例,我们引出了需要自定义开发新插件的需求,并在讨论当前主流的方案后,提供了一种新的思路来更巧妙地解决自定义能力开发的问题。

  1. API 请求路由与身份校验:首先,利用 Apache APISIX 的路由功能和身份验证插件,当请求携带的凭证合法时,APISIX 将客户端请求转发至 Node-Red 服务中。

  2. 请求处理与转换:在 Node-Red 中,我们创建了一个流程用于处理接收到的 API 请求。通过 HTTP 输入节点接收来自 APISIX 的请求,并对请求参数进行解析和验证,确保参数符合业务需求。

  3. 业务逻辑处理:一旦收到有效的请求,我们可以在 Node-Red 中执行业务逻辑。例如,根据参数将请求发送至不同的业务 API 获取数据,并从响应结果中提取出需要的字段。完成这些操作后,再将最终的数据返回给 APISIX。

  4. 错误处理与日志记录:在处理过程中,如果出现任何错误或异常情况,我们可以在 Node-Red 中添加错误处理节点,对异常情况进行捕获和处理。同时,我们还可以通过日志记录节点记录处理过程中的关键信息,以便后续排查和分析,这一点在本示例中未展示。

通过将 APISIX 和 Node-Red 结合使用,我们可以以可视化的方式实现一个完整的请求处理流程,包括请求路由、数据处理、业务逻辑等功能,而无需编写复杂的代码或插件。这种灵活、可定制的解决方案可以帮助我们更快速地构建和调整系统功能,提高开发效率,降低开发成本,同时保证系统的稳定性和可扩展性。

微信咨询

获取方案