使用Kong和Konga管理微服務和API

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 設置連接

登錄成功後,你將看到這個頁面:

clipboard.png

此時,您需要手動創建與Kong的管理API的連接。

如果您打開連接(connections)頁面,您會注意到與先前創建的Kong實例的連接已經存在但尚未激活。

clipboard.png

點擊激活按鈕。如果一切設置正確,Konga將連接到Kong,界面將充滿各種酷感。

clipboard.png

2.2 核心概念

簡要介紹一些核心的概念

Dashboard

bVbxEz0?w=2876&h=1472uploading.4e448015.gif轉存失敗重新上傳取消clipboard.png

儀表板顯示有關您當前連接的Kong實例,基礎數據庫和可用插件的基本信息。更多詳細信息可在INFO頁面中找到。

clipboard.png

Snapshots

快照功能允許您輕鬆地跨節點備份,恢復和移動Kong配置。您還可以安排Kong實例的自動快照。

clipboard.png

Settings

clipboard.png

設置頁面提供了一種配置Konga併爲用戶帳戶設置基本ACL的簡單方法。請記住,用戶權限是全局設置的,並將用戶帳戶稱爲實體。尚不支持單個用戶ACL。

三、Konga創建服務(Services)和路由(Routes)

我們將使用優秀的在線虛假API(由typicode提供的)進行測試和原型設計。

導航到服務頁面並添加新服務。填寫表格如下:

注意:url參數是一個簡化參數,用於一次性添加protocol,host,port和path。另外不要把Services當作後端的具體API,要把它當作一個大的服務,該服務下面有多個API(endpoint or route)。所以創建服務的時候填上該服務的域名就行了。當然也可以是一個url(帶Path的),這樣每個API(route)會路由到該path上。

bVbxEDh?w=1842&h=1334uploading.4e448015.gif轉存失敗重新上傳取消clipboard.png

提交後,服務被創建,詳情如下:

bVbxUKd?w=2018&h=1230uploading.4e448015.gif轉存失敗重新上傳取消clipboard.png

The jsonplaceholder API 提供以下資源(假設我們有這些API):
posts
comments
albums
photos
todos
users

我們需要爲這些資源創建路由。單擊json-placeholder服務,選擇routes選項卡並添加新路由。

clipboard.png

這裏的 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的工作原理

clipboard.png

clipboard.png

 

 

4.1 kong默認開放的端口

接受客戶端流量的端口,proxy部分

  • :8000 http端口
  • :8443 https端口

admin API 端口 admin部分

  • :8001 http端口
  • :8444 https端口

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": "[email protected]",
  "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
}

所以Host的作用是上面,現在完全搞清楚了吧。

參考

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...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章