Kong是Mashape开源的高性能高可用API网关和API服务管理层。自2015年在github开源后,广泛受到关注。它基于OpenResty,进行API管理,并提供了插件实现API的AOP。Kong在Mashape 管理了超过15,000 个API,为200,000开发者提供了每月数十亿的请求支持。
在微服务架构之下,服务被拆的非常零散,降低了耦合度的同时也给服务的统一管理增加了难度。如上图左所示,在旧的服务治理体系之下,鉴权,限流,日志,监控等通用功能需要在每个服务中单独实现,这使得系统维护者没有一个全局的视图来统一管理这些功能。API 网关致力于解决的问题便是为微服务纳管这些通用的功能,在此基础上提高系统的可扩展性。如右图所示,微服务搭配上 API 网关,可以使得服务本身更专注于自己的领域,很好地对服务调用者和服务提供者做了隔离。
Kong 的插件机制是其高可扩展性的根源,Kong 可以很方便地为路由和服务提供各种插件,网关所需要的基本特性,Kong 都如数支持:
云原生: 与平台无关,Kong可以从裸机运行到Kubernetes
动态路由: Kong 的背后是 OpenResty+Lua,所以从 OpenResty 继承了动态路由的特性
熔断
健康检查
日志: 可以记录通过 Kong 的 HTTP,TCP,UDP 请求和响应。
鉴权: 权限控制,IP 黑白名单,同样是 OpenResty 的特性
SSL: Setup a Specific SSL Certificate for an underlying service or API.
监控: Kong 提供了实时监控插件
认证: 如数支持 HMAC, JWT, Basic, OAuth2.0 等常用协议
限流
REST API: 通过 Rest API 进行配置管理,从繁琐的配置文件中解放
可用性: 天然支持分布式
高性能: 背靠非阻塞通信的 nginx,性能自不用说
插件机制: 提供众多开箱即用的插件,且有易于扩展的自定义插件接口,用户可以使用 Lua 自行开发插件
一、安装
Kong支持多种安装方式,这里使用Docker安装,比较简单。
1.1 安装Kong
使用数据库
1.创建一个Docker网络
首先,我们你需要创建一个自定义网络,这样的话多个容器之间能够相互发现和通讯,网络名称可以随便命名。
docker network create kong-net
2.启动数据库
你可以使用 PostgreSQL 或 Cassandra 作为你的存储。这里选择PostgreSQL作为Kong的数据库。
$ docker run -d --name kong-database \
--network=kong-net \
-p 5432:5432 \
-e "POSTGRES_USER=kong" \
-e "POSTGRES_DB=kong" \
postgres:9.6
如果你希望使用Cassandra。
$ docker run -d --name kong-database \
--network=kong-net \
-p 9042:9042 \
3.准备数据库
现在让我们通过启动一个短暂的Kong容器来准备我们的数据库,这个容器将运行适当的迁移并死掉!
$ docker run --rm \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
kong:latest kong migrations bootstrap
注意:上面的例子中,如果你使用的是 Cassandra,那么你应该更新 KONG_DATABASE 环境变量的值为 cassandra 。
4.启动Kong
当迁移已经运行并且数据库准备就绪后,启动一个Kong容器,它将连接到你的数据库容器。
$ docker run -d --name kong \
--network=kong-net \
-e "KONG_DATABASE=postgres" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
-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" \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
kong:latest
5.测试Kong!
Kong’s admin API is exposed on port 8001 and the gateway on port 8000
$ curl -i http://localhost:8001/
$ curl -i http://localhost:8000/
1.2 安装Konga
当前KONG的社区版是没有dashboard的,但是付费的企业版是有带的,并且还有一些企业版才能使用的插件以及升级后的企业版插件。所以对于使用社区版的用户而言,排除自己去撸一个dashboard的这种选择,第三方开源的dashboard无疑是首选。当前GitHub上还在更新维护的dashboard有三个,分别是kong-dashboard,kongdash 和 konga。
说道Kong的管理GUI,网上说的比较多的都是kong-dashboard,但目前最新版(v3.6.0)似乎并未支持最新版本的Kong。而目前在github能找到star比较多的就是konga了。konga不仅支持了Kong的最新版本(service和route的拆分新特性)同时支持管理员的权限控制和多个Kong连接池的管理。Konga由于自带了用户权限控制和Kong连接池管理,所以需要一些数据持久化处理。默认支持的数据库有mongodb、postgres、mysql。这里我们选择的是PostgresSQL,原因是KONG连接的数据库也是PGSQL,这样可以减少数据库的部署。
1.和以前一样,我们需要通过启动一个短暂的容器来准备Konga的数据库。
$ docker run --rm \
--network=kong-net \
pantsel/konga -c prepare -a postgres -u postgresql://kong@kong-database:5432/konga_db
当迁移运行了,我们可以启动 Konga
$ docker run -p 1337:1337 \
--network=kong-net \
-e "DB_ADAPTER=postgres" \
-e "DB_HOST=kong-database" \
-e "DB_USER=kong" \
-e "DB_DATABASE=konga_db" \
-e "KONGA_HOOK_TIMEOUT=120000" \
-e "NODE_ENV=production" \
--name konga \
pantsel/konga
After a while, Konga will be available at:
http://<your-servers-public-ip-or-host>:1337
二、Konga操作
2.1 设置连接
登录成功后,你将看到这个页面:
此时,您需要手动创建与Kong的管理API的连接。
如果您打开连接(connections)页面,您会注意到与先前创建的Kong实例的连接已经存在但尚未激活。
点击激活按钮。如果一切设置正确,Konga将连接到Kong,界面将充满各种酷感。
2.2 核心概念
简要介绍一些核心的概念
Dashboard
仪表板显示有关您当前连接的Kong实例,基础数据库和可用插件的基本信息。更多详细信息可在INFO页面中找到。
Snapshots
快照功能允许您轻松地跨节点备份,恢复和移动Kong配置。您还可以安排Kong实例的自动快照。
Settings
设置页面提供了一种配置Konga并为用户帐户设置基本ACL的简单方法。请记住,用户权限是全局设置的,并将用户帐户称为实体。尚不支持单个用户ACL。
三、Konga创建服务(Services)和路由(Routes)
我们将使用优秀的在线虚假API(由typicode提供的)进行测试和原型设计。
导航到服务页面并添加新服务。填写表格如下:
注意:url参数是一个简化参数,用于一次性添加protocol,host,port和path。另外不要把Services当作后端的具体API,要把它当作一个大的服务,该服务下面有多个API(endpoint or route)。所以创建服务的时候填上该服务的域名就行了。当然也可以是一个url(带Path的),这样每个API(route)会路由到该path上。
提交后,服务被创建,详情如下:
The jsonplaceholder API 提供以下资源(假设我们有这些API):
posts
comments
albums
photos
todos
users
我们需要为这些资源创建路由。单击json-placeholder服务,选择routes选项卡并添加新路由。
这里的 Path 就是具体业务API的路径(endpoint)。Hosts不设置会默认采用Services里的Host,但是一旦设置了,客户端请求该route的时候必须带上设置的host,且必须一致。、
如果Strip path设置为YES,这里的 Path 可以加一个前缀,如:/passport/users,但最终会映射到后端真实的API /users。Kong转发到后端服务的时候会把前缀/passport部分去掉。客户端调用API必须和Routes里的Path一致才行(/passport/users),否则会得到404,无法匹配。用户的请求是先匹配route,然后转发到service。
比如:
If your route has /my-service in the paths property, then
/my-service/foo will be routed upstream as /foo,
/my-service/bar will be routed upstream as /bar.
I’d recommend you to go through the definition of the Route Object in Kong to gain a better understanding.
试试看
$ curl -i http://localhost:8000/users
同样,创建响应POST,PUT,PATCH和DELETE请求的第二个路由。
继续创建一个新用户并测试它:
$ curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"JohnDoe","username":"jdoe"}' \
http://localhost:8000/users
到目前为止,我们成功部署了Kong&Konga并设法通过我们的网关访问jsonplaceholder API。
四、Kong的工作原理
4.1 kong默认开放的端口
接受客户端流量的端口,proxy部分
- :8000 http端口
- :8443 https端口
admin API 端口 admin部分
- :8001 http端口
-
4.2 Nginx配置 VS Kong配置
我们来看一个典型的Nginx的配置对应在Kong上是怎么样的,下面是一个典型的Nginx配置
upstream passportUpstream {
server localhost:8080 weight=100;
}
server {
listen 80;
location /hello {
proxy_pass http://passportUpstream;
}
}
下面我们来看看其对应Kong中的配置
# 配置 upstream
curl -X POST http://localhost:8001/upstreams
--data "name=passportUpstream"
# 配置 target
curl -X POST http://localhost:8001/upstreams/passport/targets
--data "target=localhost:8080" --data "weight=100"
# 配置 service
curl -X POST http://localhost:8001/services
--data "name=getUserInfo" --data "host=passportUpstream"
# 配置 route
curl -X POST http://localhost:8001/routes
--data "paths[]=/user"
--data "service.id=8695cc65-16c1-43b1-95a1-5d30d0a50409"
curl -X POST http://localhost:8001/routes
--data "hosts[]=*.example.com,test.com,*.abc.com"
--data "service.id=8695cc65-16c1-43b1-95a1-5d30d0a50409"
这一切配置都是通过其Http Restful API 来动态实现的,无需我们在手动的 reload Nginx.conf 。开发的同学看到这是不是感觉到很幸福了。
在上述的配置中涉及到了几个概念:upstrean、target、service、route等概念,它们是Kong的几个核心概念,也是我们在使用Kong Api 时经常打交道的,下面我们就其几个核心概念做一下简单的说明。4.3 Kong 关键术语/名词解析
Upstream
Upstream 对象表示虚拟主机名,可用于通过多个服务(目标)对传入请求进行负载均衡。例如:service.v1.xyz 为Service对象命名的上游host是service.v1.xyz对此服务的请求将代理到上游定义的目标。
Target
目标IP地址/主机名,其端口表示后端服务的实例。每个上游都可以有多个target,并且可以动态添加Target。
由于上游维护Target的更改历史记录,因此无法删除或者修改Target。要禁用目标,请发布一个新的Targer weight=0,或者使用DELETE来完成相同的操作。
Service
顾名思义,服务实体是每个上游服务的抽象。服务的示例是数据转换微服务,计费API等。
服务的主要属性是它的URL(其中,Kong应该代理流量),其可以被设置为单个串或通过指定其protocol, host,port和path。
服务与路由相关联(服务可以有许多与之关联的路由)。路由是Kong的入口点,并定义匹配客户端请求的规则。一旦匹配路由,Kong就会将请求代理到其关联的服务。
Route
路由实体定义规则以匹配客户端的请求。每个Route与一个Service相关联,一个服务可能有多个与之关联的路由。与给定路由匹配的每个请求都将代理到其关联的Service上。可以配置的字段有 hosts
- paths
- methods
Service 和 Route 的组合(以及它们之间的关注点分离)提供了一种强大的路由机制,通过它可以在Kong中定义细粒度的入口点,从而使基础架构路由到不同上游服务。
Consumer
Consumer 对象表示服务的使用者或者用户。您可以依靠Kong作为主数据库存储,也可以将使用者列表与数据库映射,以保持Kong与现有的主数据存储之间的一致性。
Plugin
插件实体表示将在HTTP请求/响应生命周期期间执行的插件配置。它是如何为在Kong后面运行的服务添加功能的,例如身份验证或速率限制。
将插件配置添加到服务时,客户端向该服务发出的每个请求都将运行所述插件。如果某个特定消费者需要将插件调整为不同的值,您可以通过创建一个单独的插件实例,通过service和consumer字段指定服务和消费者 。
对应关系
Upstream : target -> 1:n
Service:Upstream -> 1:1 or 1:0 (Service 可以直接指向具体的Target,相当于不做负载均衡)
Service : Route -> 1:n
Note: Client请求的流量通过Route指向与之相关的Service,如果配置插件的话就会作用插件,Service接到流量后给相应的Upstream的服务上面。
五、Kong API操作
5.1 配置服务
通过向Admin API发送HTTP请求来向Kong添加服务:
curl -i -X POST http://localhost:8001/services/ \
-d 'name=foo-service' \
-d 'url=http://foo-service.com'
HTTP/1.1 201 Created
...
{
"connect_timeout": 60000,
"created_at": 1515537771,
"host": "foo-service.com",
"id": "d54da06c-d69f-4910-8896-915c63c270cd",
"name": "foo-service",
"path": "/",
"port": 80,
"protocol": "http",
"read_timeout": 60000,
"retries": 5,
"updated_at": 1515537771,
"write_timeout": 60000
}
这里注册一个名为“foo-service”的服务,该服务指向http://foo-service.com(上游)。
注意:url参数是一个简化参数,用于一次性添加protocol,host,port和path。
更多见官方指南
5.2 路由匹配规则
现在让我们讨论Kong如何匹配针对路由的已配置host,path和methods属性(或字段)的请求。请注意,所有这三个字段都是可选的,但必须至少指定其中一个。
对于匹配路线的请求:
- 请求必须包含所有已配置的字段
- 请求中的字段值必须至少与其中一个配置值匹配(当字段配置接受一个或多个值时,请求只需要其中一个值被视为匹配)
这里思考一个问题,Kong route 中的host的作用是什么?有什么意义?哪些场景会用到设置多个host呢?
这是官方的解释:
Routing a request based on its Host header is the most straightforward way to proxy traffic through Kong, as this is the intended usage of the HTTP Host header. Kong makes it easy to do so via the hosts field of the API entity.
显然官方对host的说明,没有回答上面的问题,下面看下网络上对host的解释:
我们知道Http请求头信息里面会带有一个Host字段,很多人不是很清楚这个字段具体的作用或者用法,包括我被很多人问过也曾经有些迷茫,这里具体扫盲下。
Host 是HTTP 1.1 协议中新增的一个请求头,主要用来实现虚拟主机技术。我们知道一个IP地址可以对应多个域名,比如假设我有这么几个域名 www.qiniu.com,www.taobao.com和www.jd.com 然后在域名提供商那通过A记录或者CNAME记录的方式最终都和我的虚拟机服务器IP 111.111.111.111关联起来,那么我通过任何一个域名去访问最终解析到的都是IP 111.111.111.111。
但是还是没有提到Host的概念,其实可以这样看,我们的那台虚拟机111.111.111.111上面其实是可以放很很多网站的(不然如果只能放一个网站的话就太不合理了,虚拟机那么多资源都浪费了),我们可以把www.qiniu.com,www.taobao.com 和 www.jd.com 这些网站都假设那台虚拟机上面,但是这样会有一个问题,我们每次访问这些域名其实都是解析到服务器IP 111.111.111.111,我怎么来区分每次根据域名显示出不同的网站的内容呢,其实这就要用到请求头中Host的概念了,每个Host可以看做是我在服务器111.111.111.111上面的一个站点,每次我用那些域名访问的时候都是会解析同一个虚拟机没错,但是我通过不同的Host可以区分出我是访问这个虚拟机上的哪个站点。
我们再来看几个例子就彻底明白了。考虑如下配置的路由:
{
"hosts": ["example.com", "foo-service.com"],
"paths": ["/foo", "/bar"],
"methods": ["GET"]
}
下面我们假设请求时不带Host
$ curl -v -H "Host: ''" -i http://localhost:8000/users/1
注意观察请求头,* Connected to localhost (127.0.0.1) port 8000 (#0);客户端和kong建立了tcp连接,但是host是空的,所以Kong匹配不到上面的route。
$ curl -v -H "Host: ''" -i http://localhost:8000/users/1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /users/1 HTTP/1.1
> Host: ''
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
HTTP/1.1 404 Not Found
< Date: Thu, 19 Sep 2019 09:49:49 GMT
Date: Thu, 19 Sep 2019 09:49:49 GMT
< Content-Type: application/json; charset=utf-8
Content-Type: application/json; charset=utf-8
< Connection: keep-alive
Connection: keep-alive
< Content-Length: 48
Content-Length: 48
< Server: kong/1.3.0
Server: kong/1.3.0
<
* Connection #0 to host localhost left intact
{"message":"no Route matched with those values"}
让我们看下设置了正确Host的请求:
curl -v -H "Host: example.com" -i http://localhost:8000/users/1
这次kong匹配到了route,所以拿到了数据。
$ curl -v -H "Host: example.com" -i http://localhost:8000/users/1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /users/1 HTTP/1.1
> Host: example.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
Content-Type: application/json; charset=utf-8
< Content-Length: 509
Content-Length: 509
< Connection: keep-alive
Connection: keep-alive
< Date: Thu, 19 Sep 2019 10:13:05 GMT
Date: Thu, 19 Sep 2019 10:13:05 GMT
< Set-Cookie: __cfduid=d20158b06862bc2fa86521fef7e966e771568887985; expires=Fri, 18-Sep-20 10:13:05 GMT; path=/; domain=.typicode.com; HttpOnly
Set-Cookie: __cfduid=d20158b06862bc2fa86521fef7e966e771568887985; expires=Fri, 18-Sep-20 10:13:05 GMT; path=/; domain=.typicode.com; HttpOnly
< X-Powered-By: Express
X-Powered-By: Express
< Vary: Origin, Accept-Encoding
Vary: Origin, Accept-Encoding
< Access-Control-Allow-Credentials: true
Access-Control-Allow-Credentials: true
< Cache-Control: public, max-age=14400
Cache-Control: public, max-age=14400
< Pragma: no-cache
Pragma: no-cache
< Expires: Thu, 19 Sep 2019 14:13:05 GMT
Expires: Thu, 19 Sep 2019 14:13:05 GMT
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< Etag: W/"1fd-+2Y3G3w049iSZtw5t1mzSnunngE"
Etag: W/"1fd-+2Y3G3w049iSZtw5t1mzSnunngE"
< Via: kong/1.3.0
Via: kong/1.3.0
< CF-Cache-Status: MISS
CF-Cache-Status: MISS
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
Server: cloudflare
< CF-RAY: 518ac8f28e0bd362-LAX
CF-RAY: 518ac8f28e0bd362-LAX
< X-Kong-Upstream-Latency: 981
X-Kong-Upstream-Latency: 981
< X-Kong-Proxy-Latency: 0
X-Kong-Proxy-Latency: 0
<
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
* Connection #0 to host localhost left intact
}
参考
https://docs.konghq.com/insta…
https://docs.konghq.com/0.14….
https://pantsel.github.io/konga/
https://ajaysreedhar.github.i…
https://discuss.konghq.com/t/…
https://zhuanlan.zhihu.com/p/…
https://www.lijiaocn.com/%E9%…
https://keyla.vip/kong/route/
https://www.li-rui.top/2019/0…
https://www.linzepeng.com/201…
https://medium.com/@tselentis…
https://www.cnkirito.moe/kong…
https://www.li-rui.top/2019/0…
https://www.qingtingip.com/h_…
http://www.102no.com/archives…