Kong Gateway 是一个轻量、快速、灵活的云原生API网关,其本质是一个运行在 Nginx中的Lua应用程序。
概述
Kong是Mashape开源的高性能高可用的API网关,可以水平扩展。它通过前置的负载均衡配置把请求分发到各个server,来应对大批量的网络请求。
由于Kong 的成功,Mashape 于 2017 年重组并更名为 Kong Inc.,将业务重点全面转移到 API 网关及相关技术上。如今,已成为 API 网关和服务网格的领先提供商。
主要功能
Kong提供了诸如HTTP基本认证、密钥认证、CORS、TCP、UDP、日志记录、请求限流、请求转发以及NGINX监控等基本功能。除此之外,它还可以通过插件(lua编写)来扩展已有的功能。其示意图如下:
Kong的核心功能如下:
API路由与反向代理
- Kong Gateway作为API请求的入口,将请求从客户端路由到相应的服务。
- 支持动态路由,可以根据URL、请求头、参数等条件来决定将请求转发到哪个服务端点。
认证与授权
- 提供多种认证方式,如OAuth 2.0、JWT、Basic Auth、Key Auth等,确保API只能被经过认证的客户端访问。
- 支持对用户进行授权控制,确保不同的用户只能访问他们被授权使用的API服务。
安全保护
- Kong Gateway可以保护API免受常见攻击,如DDos、SQL注入、跨站请求伪造等。
- 通过API限流,IP白名单/黑明单机制,防止恶意访问。
负载均衡
- Kong可以在多个服务之间分配流量,确保负载均衡分布,并具备健康检查功能,可以动态监测服务节点的健康状况。
- 支持协议的转换,比如从HTTP转换为HTTPS、从HTTP2转换为HTTP1.1。
限流与速度限制
- Kong提供了高级的速率限制功能,你可以为每个API路由配置速率限制策略,限制客户端在一定时间内发送的请求数量,防止资源滥用。
监控与日志记录
- Kong集成了多种监控工具,支持Prometheus、Datalog、Grafana等监控系统。
- 提供了详细的日志记录功能,记录API的访问日志,包括响应时间,请求来源等信息。
插件架构
- 支持通过插件来扩展功能,官方和社区提供了丰富的插件生态系统,覆盖认证、限流、缓存、日志记录等多方面。
- 开发者还可以自定义插件来满足特定的业务需求。
多协议支持
- 支持多种协议,如HTTP、HTTPS、gRPC、WebSocket等,允许在API层支持不同类型的服务通信。
服务网格集成
- Kong Gateway与Kong Mesh无缝集成,支持在更复杂的微服务架构中管理服务间的通信、提供服务发现、负载均衡以及自动化的安全策略。
Kong gateway是与OpenResty一起分发的,而OpenRestry是一个扩展于lua-nginx-module的模块。OpenRestry主要解决Nginx无法动态扩展功能的问题,它将Lua JIT内嵌到了 Nginx 的内部,以支持通过 lua 语言对 Nginx 的能力进行方便地扩展开发。
版本和模式
Kong Gateway的部署方式有两种:使用Kong Konnect进行托管和自主管理。
Konnect提供了实用Kong Gateway的最简单的方法。全局控制平面由Kong托管在云上,你只需要管理各个数据平面节点即可, 但Konnect是收费方案。另一种部署方式就是自管理。
自管理
Kong gateway有两个不同的版本,开源版和企业版。开源版包括基本的API网关功能和开源插件,企业版附加了一些额外功能,包括RBAC和企业插件。企业版是收费的。
开源版和企业版的功能比较:
功能描述 | 开源版 | 企业版 |
---|---|---|
API基础设施现代化 | ||
API网关 | ✅ | ✅ |
端对端自动化 | ✅ | ✅ |
Kong Ingress Controller | ✅ | ✅ |
网关Mocking | ❌ | ✅ |
admin界面管理器 | ✅ | ✅ |
流量管理和转换 | ||
基础流量管控插件 | ✅ | ✅ |
简单数据转换 | ✅ | ✅ |
gRPC转换 | ✅ | ✅ |
GraphQL | ❌ | ✅ |
请求验证 | ❌ | ✅ |
jq转换 | ❌ | ✅ |
高级缓存 | ❌ | ✅ |
高级qps限制 | ❌ | ✅ |
安全管控 | ||
认证和基本授权(Bot检测,CORS,ACLs) | ✅ | ✅ |
高级认证 | ❌ | ✅ |
基于角色的访问控制(RBAC) | ❌ | ✅ |
高级授权(OPA) | ❌ | ✅ |
证书管理 | ❌ | ✅ |
FIPS 140-2 支持 | ❌ | ✅ |
Signed Kong镜像 | ❌ | ✅ |
AI网关 | ||
多种大语言模型支持 | ✅ | ✅ |
AI流量控制 | ✅ | ✅ |
AI prompt安全 | ✅ | ✅ |
AI 可观测性 | ✅ | ✅ |
企业支持与服务 | ❌ | ✅ |
Kong Admin API 和 Kong Manager
Kong 网关的所有实体包括服务,路由,插件,消费者等等,都可以通过admin api来访问和配置。
Kong Manager是Kong 网关的图形化管理界面,实际上还是通过Admin API来调用接口进行管理。
Kubernetes
前文说过Kong Gateway是云原生网关,主要体现为它可以通过其ingress controller运行在k8s中。
工作原理
本节将从路由,负载均衡方面来介绍Kong Gateway的工作原理。
路由规则
Kong Gateway对外暴露多个接口,体现在如下的配置属性中:
proxy_listen
:这个属性定义了一系列地址/端口,通过这些地址和端口来接受公开的HTTP, gRPC, WebSocket等请求,随后将这些请求代理到服务(默认8000端口)。admin_listen
: 这个属性也定义了一系列地址和端口,但这个属性指定的是Admin API 的地址,提供给管理员来对Kong进行配置(默认8001端口)。stream_listen
: 这个属性与proxy_listen
作用类似,只不过它是适用于4层的代理(如TCP、TLS)。默认情况下是关闭的。
从顶层来看,Kong Gateway既可以监听7层的HTTP请求,也可以监听4层的请求(分别通过proxy_listen
和stream_listen
指定)。然后根据配置的路由,将匹配的请求代理到对应的服务。路由条件是根据子系统(HTTP/HTTPS, gRPC/gRPCS, TCP/TLS)来区分的,主要有下列条件:
- http: methods, hosts, headers, paths (对于https请求还有snis)
- tcp: sources, destinations(对于tls还有snis)
- grpc: hosts, headers, paths(对于grpcs还有snis)
假设现在我们创建了一个路由如下:
curl -i -X POST http://localhost:8001/routes/ \
-d 'hosts[]=test.com' \
-d 'hosts[]=foo-service.com' \
-d 'headers.region=north'
该路由规定了hosts和header参数,只有满足条件的请求才能被路由接受。
域名通配符
设置路由属性时既可以指定完整的域名,也可以使用域名通配符。
路径正则表达式
设置路由属性时既可以指定完整的路径,也可以使用正则表达式来表示路径。当路径是正则表达式时,需要用~
作为前缀,如
paths: ["~/foo/bar$"]
没有~
前缀会被当成纯文本,如
"paths": ["/users/\d+/profile", "/following"]
如果一个请求的路径同时满足两个路径条件,那应该如何路由呢?这时候就需要考虑路径优先级了。优先级是根据regex_priority
来判断的。该值越大,则优先级越高。如果没有设置regex_priority
, 则要根据其他规则来判断优先级:具体路径 > 前缀匹配 > 正则匹配。
捕获组
在Kong中,捕获组是正则表达式的一种功能,用于在路由中提取特定部分的请求路径。假设有一个正则表达式路由配置如下:
routes:
- name: user-route
paths:
- ~^/users/(\d+)/details$
service:
id: user-service
这里的(\d+)
就是一个捕获组。请求/users/123/details
可以匹配到该路径,其中的123
就是捕获组参数。后端服务可以通过动态路径来接受该参数,如Springboot中的@GetMapping("/users/{id}/details")
。
除此之外,还可以通过插件来对捕获组参数进行其他处理。例如request-transformer
插件可以将参数作为请求头放进去:
plugins:
- name: request-transformer
service: user-service
config:
add:
headers:
- "X-User-ID: $1"
preserve_host
Kong Gateway在代理请求时,会默认将Kong中配置的服务域名作为Host
发送给服务。如果将preserve_host
设为true,则会将原始客户端请求的host发送给服务。
举个例子,假设原始客户端请求的域名为service.com
, 该请求将被路由到域名为api.internal.service
的服务。如果不设置preserve_host
, 则服务收到的请求的Host
将是api.internal.service
。如果将preserve_host
设为true,则服务收到的请求的Host
为原始的service.com
。
负载均衡
Kong gateway提供了多种负载均衡方式,默认的是基于DNS的方式。
默认情况下,DNS负载均衡器是开启的,并且仅限于轮询的负载均衡的算法中。换句话说,默认使用DNS负载均衡器时,Kong不提供其他负载均衡算法,如最少连接,随机等。因此,尽管支持多种负载均衡策略,但是DNS负载均衡的上下文中,轮询是唯一可用的选择。
开启Kong Gateway之旅
安装方式有多种,既可以下载安装包,也可以通过docker来安装。本文以docker安装为例进行介绍。
安装kong gateway
首先创建一个docker网络:
docker network create kong-net
然后启动一个postgresql容器:
docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=kong" \
-e "POSTGRES_DB=kong" \
-e "POSTGRES_PASSWORD=kongpass" \
postgres:12
用户名和数据库名设置为kong是Kong gateway的默认值。密码则无要求。
接下来准备初始化数据
docker run --rm --network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kongpass" \
kong:3.8.0 kong miigrations bootstrap
如果docker的镜像仓库访问不了,可以用
docker pull public.ecr.aws/docker/library/kong:3.8.0
从aws的免费仓库拉取, 然后重新打标签:docker tag public.ecr.aws/docker/library/kong:3.8.0 kong:3.8.0
这里KONG_DATABASE
指定数据库类型,KONG_PG_HOST
就是上一步创建的postgres容器,密码也是上一步的密码。kong migrations bootstrap
是数据初始化命令。--rm
参数表示这是一个一次性任务,完成之后即删除容器。
启动容器:
docker run -d --name kong-gateway \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kongpass" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-e "KONG_ADMIN_GUI_URL=http://localhost:8002" \
-p 8000:8000 \
-p 8443:8443 \
-p 127.0.0.1:8001:8001 \
-p 127.0.0.1:8002:8002 \
-p 127.0.0.1:8444:8444 \
kong:3.8.0
KONG_ADMIN_LISTEN指定admin api监听的端口
验证是否安装成功:
curl -i -X GET --url http://localhost:8001/services
如果成功安装,你可以看到返回200
状态码。通过http://localhost:8002
查看管理界面。
Kong gateway还可以以无数据库模式启动,此时通过配置文件来加载配置。这种方式虽然更简单,但只适合独立的网关实例或小规模环境。这里就不介绍了。
服务与路由
服务和路由是Kong的流量管理策略中的两个重要组件。
服务管理
创建服务
先通过Admin API创建一个服务,该服务将映射到https://httpbin.konghq.com
。
curl -i -s -X POST http://localhost:8001/services \
--data name=example_service \
--data url='https://httpbin.konghq.com'
查看服务
查看服务配置:
curl -X GET http://localhost:8001/services/example_service
输出如下:
{
"host": "httpbin.konghq.com",
"name": "example_service",
"enabled": true,
...
}
更新服务
将retries参数从5改成6:
curl --request PATCH --url localhost:8001/services/example_service --data retries=6
列出服务
curl -X GET http://localhost:8001/services
路由管理
创建路由
创建关联服务的路由,请求该路由地址的流量将被转发到关联的服务:
curl -i -X POST http://localhost:8001/services/example_service/routes --data 'paths[]=/mock' --data name=example_route
查看路由配置
路由查看地址:
/services/{service name or id}/routes/{route name or id}
/routes/{route name or id}
以前者为例:
curl -X GET http://localhost:8001/services/example_service/routes/example_route
更新路由
curl --request PATCH --url localhost:8001/services/example_service/routes/example_route --data tags="tutorial"
列出路由
curl http://localhost:8001/routes
除了Admin API之外,也可以通过Manager查看和管理。
请求代理
根据上面创建的路由可知,当向/mock
发送请求时,将会被路由到example_service
。
发送请求如下:
curl -X GET http://localhost:8000/mock/anything
可以看到服务返回的响应结果。
请求速率限制
Rps限制可以针对全局,可以针对服务和路由,还可以针对消费者。速率限制插件是默认安装的。
全局限制
curl -i -X POST http://localhost:8001/plugins --data name=rate-limiting --data config.minute=5 --data config.policy=local
local表示当个kong节点计数,cluster表示多节点部署时,计数共享。
我们执行一个命令来发送6条请求,看看是否会触发rate limit:
for _ in {1..6}; do curl -s -i localhost:8000/mock/anything; echo; sleep 1; done
前面5次请求正常输出,最后一条请求输出:
{
"message":"API rate limit exceeded",
"request_id":"c9e6a941e6217c3011b8b86146081a72"
}
说明触发了速率限制。
服务限制
服务级别的rate limiting需要请求具体服务的plugins
curl -X POST http://localhost:8001/services/example_service/plugins --data "name=rate-limiting" --data config.minute=5 --data config.policy=local
路由限制
curl -X POST http://localhost:8001/routes/example_route/plugins --data "name=rate-limiting" --data config.minute=5 --data config.policy=local
消费者限制
所谓消费者即服务的调用方。
先创建一个消费者:
curl -X POST http://localhost:8001/consumers/ --data username=jsmith
再开启消费速率限制
curl -X POST http://localhost:8001/plugins --data "name=rate-limiting" --data "consumer.username=jsmith" --data "config.second=5"
在生产环境中,消费者通常是基于API密钥或者JWT等方式来识别的。
速率限制只是Kong Gateway的其中一个功能,其他的,如缓存、认证、负载均衡等,都是以类似的方式来进行配置的,并且这些基础功能,都可以通过Manger来操作。这里就不一一列举了,具体可以参见网上资料。
生产实践
部署方式
Kong Gateway一共有四种部署方式,分别是:
- Konnect(托管控制平面)
- Hybrid
- Traditional(database)
- DB-less and declarative
不通的模式都有各自的特点,需要在生产环境中需要根据具体情况来考虑。
Konnect
Konnect是一种混合模式的部署,它提供了使用Kong Gateway的最简单方法。全局控制平面由Kong托管在云上,而你可以在自己的网络环境中管理各个数据平面节点。
Hybrid
在混合模式中,Kong Gateway节点分成两种角色,一种是控制平面(CP),一种是数据平面(DP)。控制平面负责管理配置信息并提供Admin API接口。数据平面负责请求路由。控制平面访问数据库,数据平面连接到控制平面节点,获取最新的配置信息。如下图所示:
Traditional (database)
传统模式中,Kong Gateway在数据库中存储配置信息。所有的kong gateway节点均连接到数据库,每个节点自己管理配置信息。示意图如下:
与混合模式相比,传统模式没有控制平面。因此每个节点既有控制平面的作用,也有数据平面的作用。这意味着如果您的任何节点被泄露,则整个运行中的网关配置也会被泄露。相比之下,混合模式具有不同的CP和DP节点,减小了被攻击的暴露面。
DB-less and declarative mode
无数据库模式通过配置文件来读取配置信息,并将配置信息保存在节点的内存中。
此种模式下,Kong Gateway不能完全发挥所有功能。
Admin API 安全保护
通过Admin API 可以对Kong Gateway进行完全的控制,所以对Admin API 的访问控制就尤为显得重要。
网络层的访问限制
默认情况下,Kong只能通过本地接口访问,其admin_listen
的设置如下:
admin_listen = 127.0.0.1:8001
这个值是可以修改的,但不要扩大暴露面,比如0.0.0.0:8081
是不行的。
L3/4 的访问控制
如果必须将Admin API 的接口暴露给非本地接口,那也要从网络层面上做尽可能多的访问限制。假设现在有这样一个场景:Admin API 暴露在某个私有网络中,但只能给某个IP范围段的部分地址访问。在这种情况下,就必须配置基于主机的防火墙(例如 iptables)。
grep admin_listen /etc/kong/kong.conf
admin_listen 10.10.10.3:8001
# 显示设置只允许来自自身8001端口的TCP包
iptables -A INPUT -s 10.10.10.3 -m tcp -p tcp --dport 8001 -j ACCEPT
# 显示设置只允许来自以下地址的8001端口的TCP包
iptables -A INPUT -s 10.10.10.4 -m tcp -p tcp --dport 8001 -j ACCEPT
iptables -A INPUT -s 10.10.10.5 -m tcp -p tcp --dport 8001 -j ACCEPT
# 其他地址发送过来的包全部丢弃
iptables -A INPUT -m tcp -p tcp --dport 8001 -j DROP
灰度部署
蓝绿部署
"Blue" Environment
先创建upstream:
curl -X POST http://localhost:8001/upstreams --data "name=address.v1.service"
添加两个目标地址:
curl -X POST http://localhost:8001/upstreams/address.v1.service/targets --data "target=192.168.34.15:80" --data "weight=100"
curl -X POST http://localhost:8001/upstreams/address.v1.service/targets --data "target=192.168.34.16:80" --data "weight=50"
创建一个服务,将其关联到上面的upstream:
curl -X POST http://localhost:8001/services/ --data "name=address-service" --data "host=address.v1.service" --data "path=/address"
添加服务路由:
curl -X POST http://localhost:8001/services/address-service/routes/ --data "hosts[]=address.mydomain.com"
当请求的域名符合路由规则时,该请求将被转发到service对应的upstream上,并根据服务器实例的权重值进行负载均衡。三分之二的请求被转发到http://192.168.34.15:80/address
,三分之一的请求转发到另一台机器。
"Green" Environment
同样地再创建一个"Green"环境。
# 创建upstream
curl -X POST http://localhost:8001/upstreams --data "name=address.v2.service"
# 添加targets
curl -X POST http://localhost:8001/upstreams/address.v2.service/targets --data "target=192.168.34.17:80" --data "weight=100"
curl -X POST http://localhost:8001/upstreams/address.v2.service/targets --data "target=192.168.34.18:80" --data "weight=100"
服务和路由已经有了,接下来只需要修改一下服务的域名:
curl -X PATCH http://localhost:8001/services/address-service --data "host=address.v2.service"
此时再次发送请求curl http://address.mydomain.com/address
时,请求将被转发到服务对应的域名address.v2.service
关联的upstream上,并根据每台服务器的权重来做负载均衡。
通过Admin API 更改的Kong Gateway 配置实时生效,无需重载或者重启,当前正在处理中的请求也不会受到影响。
金丝雀部署
在上文的基础上,我们更新域名address.v2.service
关联的targets的权重值。假设192.168.34.17:80
为原机器,192.168.34.18:80
为新机器。
# 获取upstreams
curl http://localhost:8001/upstreams/
# 获取指定upstream关联的targets
curl http://localhost:8001/upstreams/{upstream_id}/targets
# 修改target(原机器ip)的权重
curl -X PATCH http://localhost:8001/upstreams/{upstream_id}/targets/{target_id} --data "weight=1000"
# 修改target(新机器ip)权重
curl -X PATCH http://localhost:8001/upstreams/{upstream_id}/targets/{target_id} --data "weight=0"
所有admin api的操作均可在manager console上操作
修改完权重后,因为新服务实例的权重为0,因此所有请求将被全部转发原服务实例上。接下来修改两个服务实例的权重值,其中一个降低为900,另一个增长为100。修改完成后10%的请求将被转发到新服务实例上。以此类推,直到所有请求全部被转发到新机器上。
区别
蓝绿部署和金丝雀部署都是用于发布和部署更新的策略,但两者的方式和目标有所不同。
蓝绿部署是一种发布策略,将应用的两个完整版本(蓝色和绿色)分别部署在独立的环境中。通常,"蓝色"表示旧版本,"绿色"表示新版本。其流程是:蓝色环境承载生产流量,新版本在绿色环境中完全准备好后,流量从蓝色环境切换到绿色环境,使所有用户瞬间访问到新版本,如果需要回滚,可以快速切换回蓝色环境。
金丝雀部署是一种渐进式发布策略,将新版本逐步推送给一小部分用户,进行验证后再逐步增加流量直到全部用户使用新版本。其流程是:新版本部署到少量节点,并分配一部分流量,根据监控的指标和反馈验证新版本的稳定性,逐渐增加流量。如果发生问题,可以快速将流量恢复到旧版本节点。
可以看到,蓝绿部署需要两套完整的生产环境,适用于发布较大更新的场景。而金丝雀部署需要复杂的流量控制和监控机制,适合频繁的小版本更新。
K8S中金丝雀部署
在k8s环境中,我们无法直接指定具体的服务实例,因此不能将流量控制到服务器级别,但可以控制到service级别。
假设现在有两个服务如下:
apiVersion: v1
kind: Service
metadata:
name: app-v1
spec:
selector:
app: your-app
version: v1
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: app-v2
spec:
selector:
app: your-app
version: v2
ports:
- port: 80
targetPort: 80
创建一个ingress用来进行流量路由:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
konghq.com/ingress.class: "kong"
konghq.com/plugins: "traffic-split"
spec:
rules:
- host: your-host
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-v1
port:
number: 80
再创建一个KongIngress来分配流量:
apiVersion: configuration.konghq.com/v1
kind: KongIngress
metadata:
name: app-ingress-config
namespace: your-namespace
proxy:
protocols:
- http
- https
route:
splits:
- weight: 90
service:
name: app-v1
port: 80
- weight: 10
service:
name: app-v2
port: 80
此时10%的流量将被转发到app-v2
服务,继续更新KongIngress
配置,逐步将流量导入到v2版本的服务,直至全部切到v2为止。这个过程即k8s下的canary部署。