聊一聊微服務網關 Kong
Kong 是由 Mashape 開發的并于2015年開源的一款API 網關,它是基于OpenResty(Nginx + Lua模塊)和 Apache Cassandra/PostgreSQL 構建的,能提供易于使用的RESTful API來操作和配置API管理系統。Kong 可以水平擴展多個 Kong Server,通過前置的負載均衡配置把請求均勻地分發到各個Server,來應對大批量的網絡請求。
Kong 的擴展是通過插件機制進行的,并且也提供了插件的定制示例方法。插件定義了一個請求從進入到最后反饋到客戶端的整個生命周期,所以可以滿足大部分的定制需求,本身 Kong 也已經集成了相當多的插件,包括密鑰認證、CORS、文件日志、API 請求限流、請求轉發、健康檢查、熔斷等。官網地址:https://konghq.com/,代碼托管地址:https://github.com/Kong/kong。
Nginx、Openresty和Kong三者緊密相連:
- Nginx = Http Server + Reversed Proxy + Load Balancer
- Openresty = Nginx + Lua-nginx-module,Openresty是寄生在 Nginx 上,暴露 Nginx 處理的各個階段的鉤子, 使用 Lua 擴展 Nginx
- Kong = Openresty + Customized Framework,Kong作為 OpenResty 的一個應用程序
在使用Kong之前,最好新了解一下 OpenResty和Nginx
Kong 網關具有以下的特性:
- 可擴展性: 通過簡單地添加更多的服務器,可以輕松地進行橫向擴展,這意味著您的平臺可以在一個較低負載的情況下處理任何請求。
- 模塊化: 可以通過添加新的插件進行擴展,這些插件可以通過RESTful Admin API輕松配置。
- 在任何基礎架構上運行: Kong 網關可以在任何地方都能運行。可以在云或內部網絡環境中部署 Kong,包括單個或多個數據中心設置,以及 public,private 或 invite-only APIs。
Kong的整體架構如下所示:
- Kong Restful 管理API提供了API、API消費者、插件、upstreams、證書等管理。
- Kong 插件攔截請求/響應,相當于 Servlet中的攔截器,實現請求的AOP處理。
- 數據中心用于存儲 Kong 集群節點信息、API、消費者、插件等信息,目前提供了PostgreSQL和Cassandra支持,如果需要高可用建議使用Cassandra。
- Kong 集群中的節點通過 Gossip 協議自動發現其他節點,當通過一個 Kong 節點的管理 API 進行一些變更時也會通知其他節點。每個 Kong 節點的配置信息是會緩存的,如插件,那么當在某一個 Kong 節點修改了插件配置時,需要通知其他節點配置的變更。
- Kong 核心基于 OpenResty,實現了請求/響應的 Lua 處理化。
Kong 網關的API接口的典型請求工作流程如下圖所示:
當 Kong 運行時,每個對 API 的請求將先被 Kong 命中,然后這個請求將會被代理轉發到最終的 API 接口。在請求(Requests)和響應(Responses)之間,Kong 將會執行已經事先安裝和配置好的任何插件,授權 API 訪問操作。Kong 是每個API請求的入口點(Endpoint)。
InstallKong
可運行在某些 Linux 發行版、Mac OS X 和 Docker 中,無論是本地機還是云端服務器皆可運行。除了免費的開源版本,Mashape 還提供了付費的企業版[1],其中包括技術支持、使用培訓服務以及 API 分析插件。
為了演示方便,下面就以Docker環境中部署Kong為例來做相關講解,內容參考官網:https://docs.konghq.com/install/docker/。Kong 安裝有兩種方式,一種是沒有數據庫依賴的DB-less 模式,另一種是with a Database 模式。我們這里使用第二種帶Database的模式,因為這種模式功能更全。
1. 構建 Kong 的容器網絡
首先我們創建一個 docker 自定義網絡,以允許容器相互發現和通信。在下面的創建命令中 kong-net 是我們創建的Docker網絡名稱。
- $ docker network create kong-net
2. 搭建數據庫環境
Kong 目前使用 Cassandra 或者PostgreSQL,你可以執行以下命令中的一個來選擇你的Database。請注意定義網絡 --network=kong-net 。
使用Cassandra:
- docker run -d --name kong-database \
- --network=kong-net \
- -p 9042:9042 \
- cassandra:3
使用 PostgreSQL:
- $ docker run -d --name kong-database \
- --network=kong-net \
- -p 5432:5432 \
- -e "POSTGRES_USER=kong" \
- -e "POSTGRES_DB=kong" \
- -e "POSTGRES_PASSWORD=kong" \
- postgres:9.6
3. 初始化或者遷移數據庫
我們使用docker run --rm來初始化數據庫,該命令執行后會退出容器而保留內部的數據卷(volume)。這個命令我們還是要注意的,一定要跟你聲明的網絡,數據庫類型、host名稱一致。同時注意Kong的版本號,注:當前 Kong 最新版本為 2.x,不過目前的kong-dashboard (Kong Admin UI) 尚未支持 2.x 版的Kong,為了方便后面的演示,這里以最新的 1.x 版的Kong作為演示。(截止2020-04-24時,Kong 最新版為1.5.1)
下面指定的數據庫是 PostgreSQL,如果連接的是 Cassandra,可以將下面的 KONG_DATABASE 配置為 cassandra。
- $ docker run --rm \
- --network=kong-net \
- -e "KONG_DATABASE=postgres" \
- -e "KONG_PG_HOST=kong-database" \
- -e "KONG_PG_PASSWORD=kong" \
- -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
- kong:1.5.1 kong migrations bootstrap
4. 啟動 Kong 容器
完成初始化或者遷移數據庫后,我們就可以啟動一個連接到數據庫容器的 Kong 容器,請務必保證你的數據庫容器啟動狀態,同時檢查所有的環境參數 -e 是否是你定義的環境。
- $ docker run -d --name kong \
- --network=kong-net \
- -e "KONG_DATABASE=postgres" \
- -e "KONG_PG_HOST=kong-database" \
- -e "KONG_PG_PASSWORD=kong" \
- -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:1.5.1
Kong 默認綁定4個端口:
- 8000:用來接收客戶端的 HTTP 請求,并轉發到 upstream。
- 8443:用來接收客戶端的 HTTPS 請求,并轉發到 upstream。
- 8001:HTTP 監聽的 API 管理接口。
- 8444:HTTPS 監聽的 API 管理接口。
到這里,Kong 已經安裝完畢,我們可以使用 docker ps命令查看當前運行容器,正常情況下可以看到 Kong 和 PostgreSQL 的兩個容器:
- $ docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- a28160da4a9d kong:latest "/docker-entrypoint.…" 10 seconds ago Up 9 seconds 0.0.0.0:8000-8001->8000-8001/tcp, 0.0.0.0:8443-8444->8443-8444/tcp kong
- 6c85a2e5491f postgres:9.6 "docker-entrypoint.s…" 31 minutes ago Up 31 minutes 0.0.0.0:5432->5432/tcp kong-database
我們可以通過 curl -i http://localhost:8001/ 來查看 Kong 是否運行完好。
Kong UI
Kong 企業版提供了管理UI,開源版本是沒有的。但是有很多的開源的管理 UI ,其中比較 Fashion的有Kong Dashboard和 Konga。Kong Dashboard 當前最新版本(3.6.x)并不支持最新版本的 Kong,最后一次更新也要追溯到1年多以前了,選擇 Konga 會更好一點。這里簡單介紹一下Kong Dashboard和 Konga。
Kong Dashboard
Kong Dashboard的Github地址為:https://github.com/PGBI/kong-dashboard。docker 環境中安裝運行如下:
- $ docker run --rm \
- --network=kong-net \
- -p 8080:8080 \
- pgbi/kong-dashboard start \
- --kong-url http://kong:8001
啟動之后,可以在瀏覽器中輸入 http://localhost:8080來訪問 Kong Dashboard 管理界面。
Konga
Konga (官網地址:https://pantsel.github.io/konga/,Github地址:https://github.com/pantsel/konga)可以很好地通過UI觀察到現在 Kong 的所有的配置,并且可以對于管理 Kong 節點情況進行查看、監控和預警。Konga 主要是用 AngularJS 寫的,運行于nodejs服務端。具有以下特性:
- 管理所有Kong Admin API對象。
- 支持從遠程源(數據庫,文件,API等)導入使用者。
- 管理多個Kong節點。使用快照備份,還原和遷移Kong節點。
- 使用運行狀況檢查監視節點和API狀態。
- 支持電子郵件和閑置通知。
- 支持多用戶。
- 易于數據庫集成(MySQL,PostgresSQL,MongoDB,SQL Server)。
下面使用的 PostgresSQL 是和上面在docker環境中安裝 Kong時的是一致的,注意用戶名、密碼、數據庫名稱等配置,docker環境安裝啟動 Konga:
- $ docker run -d -p 1337:1337 \
- --network kong-net \
- --name konga \
- -e "DB_ADAPTER=postgres" \
- -e "DB_URI=postgresql://kong:kong@kong-database/kong" \
- pantsel/konga
如果Konga容器啟動成功,可以通過 http://localhost:1337/訪問管理界面。通過注冊后進入,然后在 CONNECTIONS 中添加 Kong 服務的管理路徑http://xxx.xxx.xxx.xxx:8001。Konga管理界面示例如下:
Kong Admin API
部署好 Kong 之后,則需要將我們自己的接口加入到 Kong 的中管理,Kong 提供了比較全面的RESTful API,每個版本會有所不同,詳細可以參考官網:https://docs.konghq.com/2.0.x/admin-api/。Kong 管理API的端口是8001(8044),服務、路由、配置都是通過這個端口進行管理,所以部署好之后頁面可以直接訪問 http://localhost:8001。
這里我們先來了解一下如何使用 RESTful 管理接口來管理 Service (服務)、Route(路由)。
1. 添加一個Service
- $ curl -i -X POST http://localhost:8001/services \
- --data name=hello-service \
- --data url='http://xxx.xxx.xxx.xxx:8081/hello'
這里的 'http://xxx.xxx.xxx.xxx:8081/hello' 是在《網關 Zuul 科普》中提及的一個簡單的基礎服務接口,調用這個接口會返回 Hello!。
客戶端調用 Service 名稱 hello-service 訪問 'http://xxx.xxx.xxx.xxx:8081/hello'。添加成功后,系統將返回:
- {
- "host": "xxx.xxx.xxx.xxx",
- "created_at": 1587959433,
- "connect_timeout": 60000,
- "id": "d96f418a-8158-4b1d-844d-ed994fdbcc2c",
- "protocol": "http",
- "name": "hello-service",
- "read_timeout": 60000,
- "port": 8081,
- "path": "\/hello",
- "updated_at": 1587959433,
- "retries": 5,
- "write_timeout": 60000,
- "tags": null,
- "client_certificate": null
- }
2. 為 Service 添加一個 Route
- $ curl -i -X POST \
- --url http://localhost:8001/services/hello-service/routes \
- --data 'paths[]=/hello' \
- --data name=hello-route
添加成功后,系統將返回:
- {
- "id": "667bafde-7ca4-4fc4-b4f1-15c3cbec0b09",
- "path_handling": "v1",
- "paths": [
- "\/hello"
- ],
- "destinations": null,
- "headers": null,
- "protocols": [
- "http",
- "https"
- ],
- "methods": null,
- "snis": null,
- "service": {
- "id": "d96f418a-8158-4b1d-844d-ed994fdbcc2c"
- },
- "name": hello-route,
- "strip_path": true,
- "preserve_host": false,
- "regex_priority": 0,
- "updated_at": 1587959468,
- "sources": null,
- "hosts": null,
- "https_redirect_status_code": 426,
- "tags": null,
- "created_at": 1587959468
- }
3. 驗證
我們可以通過訪問 http://localhost:8000/hello 來驗證一下配置是否正確。
前面的操作就等效于配置 nginx.conf:
- server {
- listen 8000;
- location /hello {
- proxy_pass http://xxx.xxx.xxx.xxx8081/hello;
- }
- }
不過,前面的配置操作都是動態的,無需像 Nginx一樣需要重啟。
Service是抽象層面的服務,它可以直接映射到一個物理服務,也可以指向一個Upstream(同Nginx中的Upstream,是對上游服務器的抽象)。Route是路由的抽象,它負責將實際的請求映射到 Service。除了Serivce、Route之外,還有 Tag、Consumer、Plugin、Certificate、SNI、Upstream、Target等,讀者可以從官網的介紹文檔[2]中了解全貌。
下面在演示一個例子,修改 Service,將其映射到一個 Upstream:
- # 添加 name為 hello-upstream 的 Upstream
- $ curl -i -X POST http://localhost:8001/upstreams \
- --data name=hello-upstream
- # 為 mock-upstream 添加 Target,Target 代表了一個物理服務(IP地址/hostname + port的抽象),一個Upstream可以包含多個Targets
- $ curl -i -X POST http://localhost:8001/upstreams/hello-upstream/targets \
- --data target="xxx.xxx.xxx.xxx:8081"
- # 修改 hello-service,為其配置
- $ curl -i -X PATCH http://localhost:8001/services/hello-service \
- --data url='http://hello-upstream/hello'
上面的配置等同于 Nginx 中的nginx.conf配置 :
- upstream hello-upstream{
- server xxx.xxx.xxx.xxx:8081;
- }
- server {
- listen 8000;
- location /hello {
- proxy_pass http://hello-upstream/hello;
- }
- }
當然,這里的配置我們也可以通過管理界面來操作。上面操作完之后,在Konga中也有相關信息展示出來:
Kong Plugins
Kong通過插件Plugins實現日志記錄、安全檢測、性能監控和負載均衡等功能。下面我將演示一個例子,通過啟動 apikey 實現簡單網關安全檢驗。
1. 配置 key-auth 插件
- $ curl -i -X POST http://localhost:8001/routes/hello-route/plugins \
- --data name=key-auth
這個插件接收config.key_names定義參數,默認參數名稱 ['apikey']。在HTTP請求中 header和params參數中包含apikey參數,參數值必須apikey密鑰,Kong網關將堅持密鑰,驗證通過才可以訪問后續服務。
此時我們使用 curl -i http://localhost:8000/hello 來驗證一下是否生效,如果如下所示,訪問失敗(HTTP/1.1 401 Unauthorized,"No API key found in request" ),說明 Kong 安全機制生效了。
- HTTP/1.1 401 Unauthorized
- Date: Mon, 27 Apr 2020 06:44:58 GMT
- Content-Type: application/json; charset=utf-8
- Connection: keep-alive
- WWW-Authenticate: Key realm="kong"
- Content-Length: 41
- X-Kong-Response-Latency: 2
- Server: kong/1.5.1
- {"message":"No API key found in request"}
在Konga中我們也可以看到相關記錄:
2. 為Service添加服務消費者(Consumer),定義消費者訪問 API Key, 讓他擁有訪問hello-service的權限。
創建消費者 Hidden:
- $ curl -i -X POST http://localhost:8001/consumers/ \
- --data username=Hidden
創建成功之后,返回:
- {
- "custom_id": null,
- "created_at": 1587970751,
- "id": "95546c8f-248c-45c7-bce5-d972d3d9291a",
- "tags": null,
- "username": "Hidden"
- }
- 之后為消
之后為消費者 Hidden 創建一個 api key,輸入如下命令:
- $ curl -i -X POST http://localhost:8001/consumers/Hidden/key-auth/ \
- --data key=ENTER_KEY_HERE
現在我們再來驗證一下http://localhost:8000/hello:
- $ curl -i -X GET http://localhost:8000/hello \
- --header "apikey:ENTER_KEY_HERE"
返回:
- HTTP/1.1 200
- Content-Type: text/plain;charset=UTF-8
- Content-Length: 7
- Connection: keep-alive
- Date: Mon, 27 Apr 2020 07:08:38 GMT
- X-Kong-Upstream-Latency: 116
- X-Kong-Proxy-Latency: 71
- Via: kong/1.5.1
- Hello!
Well done.
Kong 官網(https://docs.konghq.com/hub/)列出了已有的所有插件,如下圖所示:
Kong 網關插件概括為如下:
- 身份認證插件:Kong提供了Basic Authentication、Key authentication、OAuth2.0 authentication、HMAC authentication、JWT、LDAP authentication認證實現。
- 安全控制插件:ACL(訪問控制)、CORS(跨域資源共享)、動態SSL、IP限制、爬蟲檢測實現。
- 流量控制插件:請求限流(基于請求計數限流)、上游響應限流(根據upstream響應計數限流)、請求大小限制。限流支持本地、Redis和集群限流模式。
- 分析監控插件:Galileo(記錄請求和響應數據,實現API分析)、Datadog(記錄API Metric如請求次數、請求大小、響應狀態和延遲,可視化API Metric)、Runscope(記錄請求和響應數據,實現API性能測試和監控)。
- 協議轉換插件:請求轉換(在轉發到upstream之前修改請求)、響應轉換(在upstream響應返回給客戶端之前修改響應)。
- 日志應用插件:TCP、UDP、HTTP、File、Syslog、StatsD、Loggly等。
總結
Kong 作為API網關提供了API管理功能及圍繞API管理實現了一些默認的插件,另外還具備集群水平擴展能力,從而提升整體吞吐量。Kong 本身是基于 OpenResty,可以在現有 Kong 的基礎上進行一些擴展,從而實現更復雜的特性。雖然有一些特性 Kong 默認是缺失的,如API級別的超時、重試、fallback策略、緩存、API聚合、AB測試等,這些功能插件需要企業開發人員通過 Lua 語言進行定制和擴展。綜上所述,Kong API 網關默認提供的插件比較豐富, 適應針對企業級的API網關定位。
References
- https://github.com/Kong/kong
- https://www.jianshu.com/p/a2e0bc8f4bfb
- https://docs.konghq.com/install/docker
- https://www.cnblogs.com/duanxz/p/9770645.html
- https://docs.konghq.com/2.0.x/admin-api/
- https://docs.konghq.com/hub/
參考資料
[1]企業版: http://getkong.org/enterprise/
[2]官網的介紹文檔: https://docs.konghq.com/2.0.x/admin-api/