Kong(https://github.com/Kong/kong)是一個雲原生,高效,可擴展的分布式 API 網關。 自 2015 年在 github 開源后,廣泛受到關注,目前已收獲 1.68w+ 的 star,其核心價值在於高性能和可擴...
為什么需要 API 網關
在微服務架構之下,服務被拆的非常零散,降低了耦合度的同時也給服務的統一管理增加了難度。如上圖左所示,在舊的服務治理體系之下,鑒權,限流,日志,監控等通用功能需要在每個服務中單獨實現,這使得系統維護者沒有一個全局的視圖來統一管理這些功能。API 網關致力於解決的問題便是為微服務納管這些通用的功能,在此基礎上提高系統的可擴展性。如右圖所示,微服務搭配上 API 網關,可以使得服務本身更專注於自己的領域,很好地對服務調用者和服務提供者做了隔離。
為什么是 Kong
SpringCloud 玩家肯定都聽說過 Zuul 這個路由組件,包括 Zuul2 和 Springcloud Gateway 等框架,在國內的知名度都不低。沒錯,我稱呼這些為組件 Or 框架,而 Kong 則更襯的上產品這個詞。在此我們可以簡單對比下 Zuul 和 Kong。
舉例而言,如果選擇使用 Zuul,當需要為應用添加限流功能,由於 Zuul 只提供了基本的路由功能,開發者需要自己研發 Zuul Filter,可能你覺得一個功能還並不麻煩,但如果在此基礎上對 Zuul 提出更多的要求,很遺憾,Zuul 使用者需要自行承擔這些復雜性。而對於 Kong 來說,限流功能就是一個插件,只需要簡單的配置,即可開箱即用。
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 背后的 OpenResty,實際上,使用 Kong 之后,Nginx 可以完全摒棄,Kong 的功能是 Nginx 的父集。
而 Zuul 除了基礎的路由特性以及其本身和 SpringCloud 結合較為緊密之外,並無任何優勢。
Kong 的架構
從技術的角度講,Kong 可以認為是一個 OpenResty 應用程序。 OpenResty 運行在 Nginx 之上,使用 Lua 擴展了 Nginx。 Lua 是一種非常容易使用的腳本語言,可以讓你在 Nginx 中編寫一些邏輯操作。之前我們提到過一個概念 Kong = OpenResty + Nginx + Lua,但想要從全局視角了解 Kong 的工作原理,還是直接看源碼比較直接。我們定位到本地的 Kong 文件夾,按照上圖中的目錄層級來識識 Kong 的廬山真面目。
- Kong 文件下包含了全部源碼和必要組件,分析他們,我們便得到了 Kong 的架構。0.13.x 是目前 Kong 的最新版本。
- 從 2 號塊中可以看到 nginx.conf ,這其實便是一個標准的 Nginx 目錄結構,這也揭示了 Kong 其實就是運行在 Nginx 的基礎之上,而進行的二次封裝。由 share 文件夾向下展開下一次分析。
- share 文件夾中包含了 OpenResty 的相關內容,其實背后就是一堆 Lua 腳本,例如 lapis 包含了數據庫操作,Nginx 生命周期,緩存控制等必要的 Lua 腳本,logging 包含了日志相關的 Lua 腳本,resty 包含了 dns,健康檢查等相關功能的 Lua 腳本…而其中的 kong 目錄值得我們重點分析,他包含了 Kong 的核心對象。
- api 和 core 文件夾,封裝了 Kong 對 service,route,upstream,target 等核心對象的操作代碼(這四個核心對象將會在下面的小節重點介紹),而 plugins 文件夾則是 Kong 高可擴展性的根源,存放了 kong 的諸多擴展功能。
- plugins 文件夾包含了上一節提到的 Kong 的諸多插件功能,如權限控制插件,跨域插件,jwt 插件,oauth2 插件…如果需要自定義插件,則需要將代碼置於此處。
從上述文件夾瀏覽下來,大概可以看到它和 Nginx 的相似之處,並在此基礎之上借助於 Lua 對自身的功能進行了拓展,除了 nginx.conf 中的配置,和相對固定的文件層級,Kong 還需要連接一個數據庫來管理路由配置,服務配置,upstream 配置等信息,是的,由於 Kong 支持動態路由的特性,所以幾乎所有動態的配置都不是配置在文件中,而是借助於 Postgres 或者 Cassandra 進行管理。
Kong 對外暴露了 Restful API,最終的配置便是落地在了數據庫之中。
Kong 的管理方式
通過文件夾結構的分析,以及數據庫中的表結構,我們已經對 Kong 的整體架構有了一個基本的認識,但肯定還存在一個疑問:我會配置 Nginx 來控制路由,但這個 Kong 應當怎么配置才能達到相同的目的呢?莫急,下面來看看 Kong 如何管理配置。
Kong 簡單易用的背后,便是因為其所有的操作都是基於 HTTP Restful API 來進行的。
其中 8000/8443 分別是 Http 和 Https 的轉發端口,等價於 Nginx 默認的 80 端口,而 8001 端口便是默認的管理端口,我們可以通過 HTTP Restful API 來動態管理 Kong 的配置。
一個典型的 Nginx 配置
upstream helloUpstream { server localhost:3000 weight=100; } server { listen 80; location /hello { proxy_pass http://helloUpstream; } }
如上這個簡單的 Nginx 配置,便可以轉換為如下的 Http 請求。
對應的 Kong 配置
# 配置 upstream curl -X POST http://localhost:8001/upstreams --data "name=helloUpstream" # 配置 target curl -X POST http://localhost:8001/upstreams/hello/targets --data "target=localhost:3000" --data "weight=100" # 配置 service curl -X POST http://localhost:8001/services --data "name=hello" --data "host=helloUpstream" # 配置 route curl -X POST http://localhost:8001/routes --data "paths[]=/hello" --data "service.id=8695cc65-16c1-43b1-95a1-5d30d0a50409"
這一切都是動態的,無需手動 reload nginx.conf。
我們為 Kong 新增路由信息時涉及到了 upstream,target,service,route 等概念,他們便是 Kong 最最核心的四個對象。(你可能在其他 Kong 的文章中見到了 api 這個對象,在最新版本 0.13 中已經被棄用,api 已經由 service 和 route 替代)
從上面的配置以及他們的字面含義大概能夠推測出他們的職責:
upstream 是對上游服務器的抽象;
target 代表了一個物理服務,是 ip + port 的抽象;
service 是抽象層面的服務,他可以直接映射到一個物理服務(host 指向 ip + port),也可以指向一個 upstream 來做到負載均衡;
route 是路由的抽象,他負責將實際的 request 映射到 service。
他們的關系如下
upstream 和 target :1 對 n
service 和 upstream :1 對 1 或 1 對 0 (service 也可以直接指向具體的 target,相當於不做負載均衡)
service 和 route:1 對 n
高可擴展性的背后—插件機制
Kong 的另一大特色便是其插件機制,這也是我認為的 Kong 最優雅的一個設計。
文章開始時我們便提到一點,微服務架構中,網關應當承擔所有服務共同需要的那部分功能,這一節我們便來介紹下,Kong 如何添加 jwt 插件,限流插件。
插件(Plugins)裝在哪兒?對於部分插件,可能是全局的,影響范圍是整個 Kong 服務;大多數插件都是裝在 service 或者 route 之上。這使得插件的影響范圍非常靈活,我們可能只需要對核心接口進行限流控制,只需要對部分接口進行權限控制,這時候,對特定的 service 和 route 進行定向的配置即可。
為 hello 服務添加50次/秒的限流
curl -X POST http://localhost:8001/services/hello/plugins \ --data "name=rate-limiting" \ --data "config.second=50"
為 hello 服務添加 jwt 插件
curl -X POST http://localhost:8001/services/login/plugins \ --data "name=jwt"
同理,插件也可以安裝在 route 之上
curl -X POST http://localhost:8001/routes/{routeId}/plugins \ --data "name=rate-limiting" \ --data "config.second=50" curl -X POST http://localhost:8001/routes/{routeId}/plugins \ --data "name=jwt"
在官方文檔中,我們可以獲取全部的插件 https://konghq.com/plugins/,部分插件需要收費的企業版才可使用。
總結
Kong 是目前市場上相對較為成熟的開源 API 網關產品,無論是性能,擴展性,還是功能特性,都決定了它是一款優秀的產品,對 OpenResty 和 Lua 感興趣的同學,Kong 也是一個優秀的學習參考對象。基於 OpenResty,可以在現有 Kong 的基礎上進行一些擴展,從而實現更復雜的特性,比如我司內部的 ABTest 插件和定制化的認證插件,開發成本都相對較低。