kong网关consumer service route plugin 理解与实践

近几天在学习api网关kong,发现中文网基本没人发表关于kong的consumer、plugin、route、service的进一步探索和使用相关文章,只是分开描述了单独的概念和做简单配置,因此为了补全这个缺失,我尽量详细记录这几天的摸索历程,以及对这些概念的理解和实践。

传送门:

kong网关中文文档

kong官网

kong官方文档

前言

近几天在学习api网关kong,发现中文网基本没人发表关于kong的consumer、plugin、route、service的进一步探索和使用相关文章,只是分开描述了单独的概念和做简单配置,因此为了补全这个缺失,我尽量详细记录这几天的摸索历程,以及对这些概念的理解和实践。

理论

Consumer

The Consumer object represents a consumer - or a user - of an API. You can either rely on Kong as the primary datastore, or you can map the consumer list with your database to keep consistency between Kong and your existing primary datastore.

(官方原文解释中的消费者和数据库的映射这里暂时不涉及)

consumer直译消费者。它是个抽象概念,代表一类事物。例如你可以创建一组consumer代表api版本号v1、v2、v3, 也可以代表请求来源类型,来自客户端请求可标记为android/iOS, web前端请求标记为web frontend,IoT设备请求标记为IoT device。

Kong所在的架构好比一家公司,kong consumer 就代表公司员工种类,普通员工、主管、经理、老板等。当然,标记这些人员需要工牌,工牌上具有员工认证信息,无工牌社会人员无法进入。

请求未认证情况下不为consumer, 只会根据请求的customIP标记,因此请求被kong标记为consumer前需要进行认证。

kong提供了多种认证方式:BASIC AUTH、API KEYS、HMAC、OAUTH2、JWT

他们适合的使用场景都不同,按需设置,我这里需要用kong搭建分布式场景应用,因此选择JWT

(这里使用kong图形界面KONGA实践)

image
image

JWT验证需要key识别用户,secret为kong保存的私钥,algorithm为最终签名算法。

JWT最终的格式简化为三段式: base64「algorithm」. base64「payload」. base64「最终签名」 。

payload中存放key以及一些不敏感信息 username, expire time等,最终签名是使用第一段的hash算法对第二段的payload哈希后的base64值。

JWT token 可以在JWT 官网去生成,当然最好是使用JWT库封装接口传参生成JWT token。

请求时,我们使用生成的JWT token加入请求头Authorization中, 这边提一句,请求kong的路由需要先用JWT插件开启JWT验证,这样kong才能主动检查JWT token,JWT验证的粒度可以为全局,可以负载在route上,也可以在service上(对route service不清晰的同学可以看关于他们的解释),这里我们在请求的route ping上开启JWT验证:

image
image

这里保持所有为默认设置就可以了。其中迷惑性较大的是consumer 参数,参数下面的解释意思是所有匿名请求来源都会被标记为user, 该参数留白的话会将所有user标记为consumer, 也就是说请求头必须带上JWT token 否则不能被识别为consumer,请求会被kong拦截,如果填上了consumer_id,并且身份验证开启了才会奏效,说的人很懵,经过一番测试下来,发现填和不填,效果是一样的

上面的consumer参数我设置成留白了,看看会发生什么:

image
image

未带上JWT token的请求被拦截,返回401鉴权失败,带上JWT token的请求被放行,请求到了网页

我们把test route上的JWT插件consumer填上一个consumer_id, 看看会发生什么:

image
image

第一个token是被放行的consumer的JWT token

第二个token是其他consumer的JWT token

不传token:

image

传第一个token:

image

传第二个token:

image

可见,即使填入了consumer_id, 其他consumer照样会被放行,和留白是一样的效果,唯一区别是带上其他consumer的token的user是否会被标记为其他consumer?

下面继续实践:

开启Acl,把被放行的consumer所在组(admin)标记为allow, 其他consumer的所在组(user)标记为deny

image
image

这时候用第二个token继续请求,

image

发现其他consumer被拦截了,也就是说其他consumer的身份被识别出来了。这就证明即使JWT插件填上了consumer_id,该插件并不会针对这个consumer进行身份识别,其他consumer不进行身份识别,而是和留白情况下的逻辑是一样的,所以这个参数是多余的,建议KONGA删除。因为consumer_id只会在「在consumer下创建JWT验证插件时生成,用于consumer的身份验证」,在一个route的JWT插件中设置consumer_id不符合逻辑。

Service

Service entities, as the name implies, are abstractions of each of your own upstream services. Examples of Services would be a data transformation microservice, a billing API, etc.

Service译作服务,不同于kubernetes的service, k8s的service是一个实体服务,里面包括实体pod进行逻辑处理,而kong的服务属于逻辑上的概念,要知道,kong是个api网关,大多数情况下在进行流量转发,因此kong的service 引用的是其他实体服务访问url,这里创建一个testApi service:

image

这个service配置了访问链接的host, protocol, port, path等资源定位参数,做了个套娃,形成了自己的东西,美其名曰流量转发。当然这个是有好处的哈,不是每个实体服务都在公网上。现有一种情况:kong 和 其他实体服务位于同一内网内,路由器网关只允许公网请求访问kong, 其他实体服务访问不到,这样一来就只暴露了网关kong,保护了内部服务。针对多个同名服务,kong还能进行负载均衡,kong这个功能特别强大,之前我们说到consumer能用来对请求分门别类,kong同样可以根据consumer做负载均衡限制,例如第一版api请求标记为consumer v1, 经过route负载均衡后会访问v1 api的service, consumer v2 访问同样route会打到提供v2api服务的service,各种功能结合起来非常有意思。

Route

The Route entities defines rules to match client requests. Each Route is associated with a Service, and a Service may have multiple Routes associated to it. Every request matching a given Route will be proxied to its associated Service.

route顾名思义,路由,提供流量入口。kong的路由形式挺多的,有host path method可供设置:

image

他们是逻辑and的关系,只有全满足时才会访问到route,如果一个请求有多个route可以满足,kong会计算路由的优先级,优先级越高就访问哪个route,优先级的计算也比较简单,就是看请求命中route的配置的个数,个数越多,优先级越高。例如请求命中host和path的路由的优先级比只命中path的优先级高。

route可以提供负载均衡,就是说一个route可以对应多个service。

当然route的配置还有很多,供大家一一挖掘,这里不展开了。

Plugin

A Plugin entity represents a plugin configuration that will be executed during the HTTP request/response workflow, and it’s how you can add functionalities to APIs that run behind Kong, like Authentication or Rate Limiting for example.

plugin即插件,主流使用lua开发(kong是openResty项目 nginx+lua),插件开发非常灵活,首先因为openresty提供了非常多的第三方库供使用,并且可以触及kong接收到的请求的生命周期,基于生命周期开发,可细粒度控制各个环节,几乎任何功能都能做到。

具体可以查看中文文档中的kong开发套件。

Upstream

The upstream object represents a virtual hostname and can be used to loadbalance incoming requests over multiple services (targets). So for example an upstream named service.v1.xyz with an API object created with an upstream_url=https://service.v1.xyz/some/path. Requests for this API would be proxied to the targets defined within the upstream.

很抽象的概念,其实也很好理解,类比http中的Host请求头(和route中的host一样),这个host可以理解为一个虚拟主机,Upstream中的实体和host值同名,创建Upstream,是为了控制向同名host发起的请求,或者对同名host的route做一些配置,健康检查之类的。