设计一个直观的、用户友好的RESTful API是一项艰难的工作。如果这是你的第一次尝试,它可能已经是一个巨大的任务。规划你的API的生命周期管理可能是一个事后的想法。但无论如何,这是有可能的:在这篇文章中,我想提出一个不折不扣的方法来进化你的API,即使它没有被计划。
最初的情况
让我们考虑一个在使用时说 "你好 "的示例应用程序:
> curl http://org.apisix/hello
Hello world
> curl http://org.apisix/hello/Joe
Hello Joe
底层技术并不重要,我们将专注于API部分:
使用一个API网关
第一步也是最关键的一步是停止将应用程序直接暴露在互联网上,在它们之间建立一个API网关。如果你不熟悉API网关的概念,你可以把它看成是一个强化了的反向代理。维基百科提供了以下定义。
网关:作为API前端的服务器,接收API请求,执行节流和安全策略,将请求传递给后端服务,然后将响应传回给请求者。网关通常包括一个转换引擎,以协调和修改飞行中的请求和响应。网关还可以提供一些功能,如收集分析数据和提供缓存。网关可以提供支持认证、授权、安全、审计和监管的功能。
在这篇文章中,我将使用Apache APISIX,但请随意使用你最熟悉的那个。
暴露网关而不是应用程序需要你更新你的DNS记录,以指向网关而不是应用程序,并等待它被传播到世界各地。这可能需要一些时间。为了跟踪传播情况,你可以使用像dnschecker这样的网站。
然而,你首先需要将你的HTTP请求从网关路由到你的应用程序。使用APISIX,你可以通过向网关发送HTTP请求来创建一个路由:
curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PUT -d ' (1) (2)
{
"name": "Direct Route to Old API", (3)
"methods": ["GET"], (4)
"uris": ["/hello", "/hello/", "/hello/*"], (5)
"upstream": { (6)
"type": "roundrobin", (8)
"nodes": {
"oldapi:8081": 1 (7)
}
}
}'
| 1 | APISIX可以分配一个自动生成的ID,或者使用提供的ID。在这种情况下,我们选择后者,在URL中传递它 -1 ,并使用PUT 动词 |
| 2 | 要更新路由,我们需要传递API密钥 |
| 3 | 命名路由并不是必须的,但它可以让我们更好地理解它的作用 |
| 4 | 路由的HTTP方法数组 |
| 5 | 要路由的URL数组 |
| 6 | 上游是一个后端应用程序。在我们的例子中,它是Hello World API。 |
| 7 | 具有各自权重的节点的哈希图。权重只有在有多个节点的情况下才有意义,而在这个简单的场景中并不是这样的。 |
| 8 | 当你配置多个节点时,要使用的平衡算法 |
在这个阶段,你可以查询网关并得到与之前相同的结果:
> curl http://org.apisix/hello
Hello world
> curl http://org.apisix/hello/Joe
Hello Joe
对API进行版本升级
API的发展意味着API的多个版本需要在某些时候并存。有三个选项可以使一个人的API版本化:
| 类型 | 例子 |
|---|---|
| 查询参数 |
curl http://org.apisix/hello?version=1
curl http://org.apisix/hello?version=2
标题
curl -H 'Version: 1' http://org.apisix/hello
curl -H 'Version: 2' http://org.apisix/hello
路径
curl http://org.apisix/v1/hello
curl http://org.apisix/v2/hello
关于什么是最好的选择,已经写了很多文章。在这篇文章的范围内,我们将使用基于路径的版本管理,因为它是最广泛的。如果你想使用其他选项,APISIX也支持这些选项。
在上一节中,我们创建了一个包裹上游的路由。APISIX允许我们创建一个有专用ID的上游,以便在多个路由中重复使用。
curl http://apisix:9080/apisix/admin/upstreams/1 -H 'X-API-KEY: xyz' -X PUT -d ' (1)
{
"name": "Old API", (2)
"type": "roundrobin",
"nodes": {
"oldapi:8081": 1
}
}'
| 1 | 使用upstreams 路由 |
| 2 | 新上游的有效载荷 |
我们还需要重写进入网关的查询,然后再将其转发到上游。后者知道/hello ,而不是/v1/hello 。APISIX允许通过插件进行这种转换、过滤等。让我们创建一个插件配置来重写路径。
curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PUT -d ' (1)
{
"plugins": {
"proxy-rewrite": { (2)
"regex_uri": ["/v1/(.*)", "/$1"] (3)
}
}
}'
| 1 | 使用plugin-configs 路径 |
| 2 | 使用proxy-rewrite插件 |
| 3 | 移除版本前缀 |
我们现在可以创建版本路由,引用新创建的上游和插件配置:
curl http://apisix:9080/apisix/admin/routes/2 -H 'X-API-KEY: xyz' -X PUT -d ' (1)
{
"name": "Versioned Route to Old API",
"methods": ["GET"],
"uris": ["/v1/hello", "/v1/hello/", "/v1/hello/*"],
"upstream_id": 1,
"plugin_config_id": 1
}'
| 1 | 看,妈,一个新的路由! |
在这个阶段,我们已经配置了两条路由,一条是有版本的,另一条是无版本的:
> curl http://org.apisix/hello
Hello world
> curl http://org.apisix/v1/hello
Hello world
将用户从非版本的路径迁移到版本的路径上
我们已经对我们的API进行了版本升级,但是我们的用户可能仍然使用传统的非版本升级的API。我们想让他们迁移,但我们不能直接删除遗留路径,因为我们的用户并不知道。幸运的是,301 HTTP状态代码是我们的朋友:我们可以让用户知道,资源已经从org.apisix/hello 转到org.apisix/v1/hello。这需…](apisix.apache.org/docs/apisix…
curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
"plugins": {
"redirect": {
"uri": "/v1$uri",
"ret_code": 301
}
}
}'
结果是有趣的:
>curl http://apisix.org/hello
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty</center>
</body>
</html>
>curl -L apisix:9080/hello (1)
Hello world
| 1 | -L 选项跟随重定向 |
要么用户会透明地使用新的端点,因为他们会跟随,要么他们的整合中断,他们会注意到301状态和新的API位置的使用。
了解你的用户
你可能已经注意到,到目前为止,我们还不知道谁在使用我们的API。当我们不得不引入一个变化时,我们必须要有创造性,不要破坏用户的使用。其他的变化可能就不那么容易应付了。因此,我们应该努力了解我们的用户,以便在必要时联系他们。
让我们坦率地说;大多数开发者,包括我自己,如果可以避免的话,都不喜欢注册和提供联系信息。我想这是营销团队的错,他们不理解我们的心态--不给我打电话,我就给你打电话。然而,在这种特殊情况下,这将是有益的。
核 "选项完全不允许用户在系统中注册前调用我们的API。我更喜欢另一种选择:限制未注册用户在一个时期内的呼叫次数。如果他们达到了限制,我们将返回(不)著名的429 HTTP状态和邀请他们注册的信息。
在写这篇文章的时候,没有任何开箱即用的插件可以实现这个目标。但我们可以自己编写。APISIX位于一个Lua引擎之上,所有提供的插件都是用Lua编写的。另外,你也可以用Go、Python、WebAssembly或任何基于JVM的语言编写你的插件。
为了使事情简单,我写了一个Lua插件。由于这篇文章的目的不是为了理解Lua,所以我不会进一步深入研究。如果你对代码感兴趣,它可以在GitHub上找到。当公众准备好后,我们还有几个步骤要完成。
-
配置APISIX以使用该目录:
config.yaml
apisix: extra_lua_path: "/opt/apisix/?.lua" (1)1 APISIX可以使用位于 /opt/apisix/文件夹中的任何Lua脚本 -
加载该插件:
2.APISIX可以自己热重载。我们不需要重新启动它--和承受停机时间--来添加额外的插件! 2.
curl http://apisix:9080/apisix/admin/plugins/reload -H 'X-API-KEY: xyz' -X PUT -
对现有的插件配置进行修补:
最后,我们需要对插件本身进行配置。由于我们创建了一个专门的插件配置,我们只需要用新的配置来更新它。
curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d ' { "plugins": { "proxy-rewrite": { (1) "regex_uri": ["/v1/(.*)", "/$1"] }, "unauth-limit": { (2) "count": 1, (3) "time_window": 60, (3) "key_type": "var", (4) "key": "consumer_name", (4) "rejected_code": 429, "rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls" } } }'
| 1 | 不幸的是,我们需要重复现有的插件配置。APISIX团队正在进行修复,所以你可以在不知道现有配置的情况下添加一个插件。 |
| 2 | 我们的插件! |
| 3 | 如果用户是未认证的,该插件限制每60秒超过一次呼叫。否则,它不会限制任何东西。 |
| 4 | 在下一节中解释 |
我们现在可以检查它的行为是否符合预期:
>curl apisix:9080/v1/hello
Hello world
>curl apisix:9080/v1/hello
{"error_msg":"Please register at https:\/\/apisix.org\/register to get your API token and enjoy unlimited calls"}
事实上,它确实如此。
创建用户
你可能应该开始看到你的用户访问注册页面,这取决于你对未认证使用的限制程度。注册有很多方面它可以是:
- 自动或需要你所希望的许多手动验证步骤
- 免费或付费
- 简单到不需要进一步验证的电子邮件,或者复杂到需要更多的数据
- 等等。
这取决于你的具体环境。
关于APISIX,归根结底,它转化为一个新的消费者。为了创建这样一个消费者,我们需要配置一个指定认证的插件。有几个认证插件是开箱即用的:basic、API key、JWT、OpenId、LDAP、Keycloak,等等。
在这篇文章的范围内,密钥认证插件已经足够了。让我们来配置一个由API密钥认证的消费者对象。
curl http://apisix:9080/apisix/admin/consumers -H 'X-API-KEY: xyz' -X PUT -d '
{
"username": "johndoe", (1)
"plugins": {
"key-auth": { (2)
"key": "mykey" (3)
}
}
}'
| 1 | 消费者的ID |
| 2 | 要使用的插件 |
| 3 | 有效的令牌是mykey |
请注意,默认的标头是apikey 。可以配置另一个标头:请查看key-auth插件文档。
我们现在可以测试我们的设置,并验证它是否按照我们的要求工作:
>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world
>curl -H 'apikey: mykey' apisix:9080/v1/hello
Hello world
在生产中测试
在这个阶段,我们现在已经准备好让用户了解我们的Hello world API的改进版本。我想我们的团队对它进行了彻底的测试,但新的代码总是有风险的。部署一个现有应用程序的有错误的新版本会对API供应商的形象(和收入!)产生负面影响。
为了尽量减少风险,一个公认的策略是进行金丝雀发布。
金丝雀发布是一种在生产中引入新的软件版本的技术,在将其推广到整个基础设施并提供给所有人之前,先在一小部分用户中慢慢推出该变化,以减少风险。
如果有什么东西失败了,它将只影响到用户群的一小部分,我们将能够在没有太大影响的情况下恢复这个变化。然而,通过API网关,我们可以在金丝雀发布之前引入一个步骤:我们将复制生产流量到新的API端点。虽然网关会丢弃响应,但我们可以在对用户没有影响的情况下发现额外的bug。
APISIX提供了代理镜像插件,将生产流量复制到其他节点。让我们来更新我们的插件配置。
curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
"plugins": {
"proxy-rewrite": {
"regex_uri": ["/v1/(.*)", "/$1"]
},
"unauth-limit": {
"count": 1,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
},
"proxy-mirror": {
"host": "http://new.api:8082" (1)
}
}
}'
| 1 | APISIX也将向这个主机发送流量 |
我们可以同时监控新的和旧的端点,以确保前者没有比后者更多的错误发生。如果没有,我们可以修复错误并再次重新部署,直到出现这种情况。现在我们准备进行金丝雀发布。
首先,我们创建一个指向新API的上游:
curl http://apisix:9080/apisix/admin/upstreams/2 -H 'X-API-KEY: xyz' -X PUT -d '
{
"name": "New API",
"type": "roundrobin",
"nodes": {
"newapi:8082": 1
}
}'
然后,我们可以用traffic-split替换proxy-mirror 插件:
curl http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
"plugins": {
"proxy-rewrite": {
"regex_uri": ["/v1/(.*)", "/$1"]
},
"unauth-limit": {
"count": 1,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
},
"traffic-split": {
"rules": [
{
"weighted_upstreams": [ (1)
{
"upstream_id": 2,
"weight": 1
},
{
"weight": 1
}
]
}
]
}
}
}'
| 1 | 为演示目的,将50%的流量发送到新的API。在现实生活中,你可能会把起点降低很多,甚至只配置内部用户到新的端点。 |
curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world
curl -L -H 'apikey: mykey' apisix:9080/hello
Hello world (souped-up version!)
如果一切正常,我们可以逐渐增加发送到新API的流量百分比,直到达到100%。现在我们可以取消流量分割,从默认端点重定向到v2而不是v1。
废弃遗留的版本
大多数用户可能会迁移到新版本以从中受益,但也有一部分用户会留在v1版本上。这可能有多种原因:没有合适的时间(提示:永远不会),太贵了,没有足够的动力迁移,你说是吧。但作为一个API供应商,每一个部署的版本都有明确的成本。你可能需要在某个时间点淘汰V1版本。
REST不是一个标准,但IETF有一个关于它的规范草案。更多细节,请阅读The Deprecation HTTP Header Field。顾名思义,它是基于一个特定的HTTP响应头。
在API网关的帮助下,我们可以配置路由以沟通其未来的废弃和替换。为此,APISIX提供了响应重写的功能。虽然它可以重写响应的任何部分,但我们将使用它来添加额外的弃用头信息。
curl -v http://apisix:9080/apisix/admin/plugin_configs/1 -H 'X-API-KEY: xyz' -X PATCH -d '
{
"plugins": {
"proxy-rewrite": {
"regex_uri": ["/v1/(.*)", "/$1"]
},
"unauth-limit": {
"count": 1,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Please register at https://apisix.org/register to get your API token and enjoy unlimited calls"
},
"response-rewrite": {
"headers": {
"Deprecation": "true",
"Link": "<$scheme://apisix:$server_port/v2/hello>; rel=\"successor-version\""
}
}
}
}'
curl -v -H 'apikey: mykey' apisix:9080/v1/hello
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Connection: keep-alive
< Date: Fri, 18 Feb 2022 16:33:30 GMT
< Server: APISIX/2.12.0
< Link: <http://apisix:9080/v2/hello>; rel="successor-version"
< Deprecation: true
<
Hello world
结论
在这篇文章中,我们描述了一个简单的一步一步的过程来管理你的API的生命周期:
- 不要直接暴露你的API;在前面设置一个API网关。
- 使用路径、查询参数或请求头对现有的API进行升级
- 用301状态代码将用户从未版本化的端点迁移到版本化的端点上
- 轻轻地推动你的用户去注册
- 在生产中进行测试,首先复制流量,然后将一小部分用户转移到新的版本上。
- 正式发布新的版本
- 通过标准的响应头来传达旧版本的淘汰。
这篇文章的完整源代码可以在Github上找到,格式为maven。