透過實例理解API網關的主要功能特性

2023.12.06

透過實例理解API網關的主要功能特性

本文對已經相對成熟的API網關技術做了回顧,對API網關的演進階段、主流特性以及當前市面上的主流API網關進行了簡要說明,並以Go實現的Tyk Gateway社區開源版為例,以示例方式對API網關的主要功能做了介紹。

在當今的科技領域中,「下雲」的概念正逐漸抬頭,像David Heinemeier Hansson [1] (37signals公司的聯合創始人, Ruby on Rails的Creator)就直接將公司所有的業務都從公有雲搬遷到了自建的資料中心[2]中。雖說大多數企業不會這麼“極端”,但隨著企業對雲端原生架構採用的廣泛與深入,不可避免地面臨對雲端服務的依賴。雲端服務在過去的幾年中被廣泛應用於建立靈活、可擴展的應用程式和基礎設施,為企業提供了許多便利和創新機會。然而,隨著業務規模的成長和資料量的增加,雲端服務的成本也隨之上升。企業開始意識到,對雲端服務的依賴已經成為一個值得重新評估的議題。雲端服務的開銷可能佔據了企業可用的預算的相當大部分。為了保持競爭力並更好地控製成本,企業需要尋找方法來減少對雲端服務的依賴,尋找更經濟的解決方案,同時確保仍能獲得所需的效能、安全性和可擴展性。

在這樣的背景下,我們的重點是選擇一款適宜的API網關,從主流功能特性的角度來評估候選人的支援。API閘道作為現代雲端原生應用架構中的關鍵元件,扮演連接前端應用和後端服務的中間層,負責管理、控制和保護API的存取。它的功能特性對於確保API的安全性、可靠性和可擴充性至關重要。

儘管API網關並不是一個新鮮事物了,但對於那些長期依賴雲端供應商的服務的人來說,它似乎變得有些「陌生」。因此,本文旨在幫助我們重新理解API網關的主要特性,並獲得對API網關選型的能力,以便在停止使用雲端供應商服務之前,找到一個合適的替代品^_^。

1. API網關回顧

API網關是現代應用架構中的關鍵元件之一,它的存在簡化了應用程式的架構,並為客戶端提供一個單一的存取入口,並進行相關的控制、最佳化和管理。API閘道可以幫助企業實現微服務架構、提高系統的可擴充性和安全性,並提供更好的開發者體驗和使用者體驗。

1.1 API網關的演化

隨著互聯網的快速發展和企業對API的需求不斷增長,API網關作為一種關鍵的中間層技術逐漸嶄露頭角並經歷了一系列的演進和發展。這裡將API網關的演進歷史粗略分為以下幾個階段:

  • API網關之前的早期階段

在網路發​​展的早期階段,大多數應用程式都是以單體應用的形式存在[3]。後來隨著應用程式規模的擴大和業務複雜性的增加,單體應用的架構變得不夠靈活和可擴展,面向服務架構(Service-Oriented Architecture,SOA)逐漸興起,企業開始將應用程式拆分成一組獨立的服務。這個時期,每個服務都是獨立對外暴露API,客戶端也是透過這些API直接存取服務,但這會導致一些安全性、維運和擴充性的問題。之後,企業也開始意識到需要一種中間層來管理和控制這種客戶端到服務的通訊行為,並確保服務的可靠性和安全性,於是開始有了API網關的概念。

  • API網關的興起

早期的API網關,其主要功能就是單純的路由與轉送。API網關將請求從客戶端轉發到後端服務,並將後端服務的回應傳回給客戶端。在這個階段,API網關的功能非常簡單,主要用於解決客戶端和後端服務之間的通訊問題。

  • API閘道的成熟

隨著微服務架構的興起和API應用的不斷發展,企業開始將應用程式進一步拆分成更小的、獨立部署的微服務。每個對外暴露的微服務都有自己的API,並透過API網關進行統一管理和存取。API閘道在微服務架構中扮演的角色變得更加重要,它的功能也逐漸豐富了。

在這一階段,它不僅負責路由和轉送請求,API網關還增加了安全性和治理的功能,可以滿足幾個不同領域的微服務需求。例如:API網關可以透過身分認證、授權、存取控制等功能來保護API的安全;透過基於重試、逾時、熔斷的容錯機制等來對API的存取進行治理;透過日誌記錄、基於指標收集以及Tracing等對API的存取進行觀測與監控;支援即時的服務發現等。

API網關(圖來自網路)API網關(圖來自網路)

  • API閘道的雲端原生化

隨著雲端原生技術的發展,如容器化和服務網格(Service Mesh)等,API網關也不斷演進和適應新的環境。在雲端原生環境中,API網關實現了與容器編排系統(如Kubernetes)和服務網格集成,其本身也可以作為一個雲端原生服務來部署,以實現更高的可伸縮性、彈性和自動化。同時,新的技術和標準也不斷湧現,如GraphQL和gRPC等,API網關也增加了這些新技術的整合和支援。

1.2 API網關的主要功能特性

從上面的演化歷史我們看到:API網關的演進使其從最初簡單的請求轉發角色,逐漸成為整個API管理和微服務架構中的關鍵元件。它不僅扮演API管理層與後端服務層之間的適配器,也是雲端原生架構中不可或缺的基礎設施,使微服務管理更加智慧和自動化。以下是現代API閘道承擔的主要功能特性,我們後續也會基於這些特性進行範例說明:

  • 請求轉送和路由
  • 身份認證和授權
  • 流量控制和限速
  • 高可用與容錯處理
  • 監控和可觀測性

2. 那些主流的API網關

以下是來自CNCF Landscape [4]中的主流API網關集合(截至2023.11月),圖中展示了關於各個網關的一些細節,包括star數量和背後開發的公司或組織:

圖片圖片

主流的API網關還有各大公有雲供應商的實現,例如:Amazon的API Gateway [5]、Google Cloud的API Gateway [6]以及上圖中的Azure API Management等,但它們不在我們選擇範圍之內;雖然被CNCF收錄,但多數API網關受到的關注並不高,超過1k star的不到30%,這些不是很受關注或dev不是那麼active的項目也無法在生產環境擔當關鍵角色;而像APISIX [7]、Kong [8]這兩個受關注很高的網關,它們是建構在Nginx之上實現的,技術棧與我們不契合;而像EMISSARY INGRESS [9]、Gloo等則是完全雲原生化或Kubernetes Native的,無法在無Kubernetes的基於VM或裸金屬的環境下部署和運作。

好吧,剩下的只有幾個Go實現的API Gateway了,在它們之中,我們選擇用Tyk API網關[10]來作為後續API功能演示的範例。

註:這不代表Tyk API網關就要比其他Go實現的API Gateway優秀[11],只是它的資料比較齊全,適合在本文中作演示罷了。

3. API網關主要功能特性範例(Tyk API網關版本)

3.1 Tyk API網關簡介

記得至少5年前就知道Tyk API網關[12]的存在,印像中它是使用Go語言開發的早期的那批API網關之一。Tyk從最初的純開源項目,到如今由背後商業公司支持,以Open Core模式開源[13]的網關,一直保持了active dev的狀態。經過多年的演進,它已經是一款功能強大的開源兼商業API管理和網關解決方案[14] ,提供了全面的功能和工具,幫助開發者有效地管理、保護和監控API。同時,Tyk API閘道支援多種安裝部署方式,即可以單一程式的方式放在實體機或VM上運行,也可以支援容器部署,透過docker-compose [ 15]拉起,也可以透過Kubernetes Operator [16 ]將其部署在Kubernetes中,這也讓Tyk API閘道具備了在各大公有雲上平滑遷移的能力。

圖片圖片

關於Tyk API閘道開源版本的功能詳情[17],可以點選左邊超連結到其官網查閱,這裡不贅述。

3.2 安裝Tyk API網關

下面我們就來安裝一下Tyk API網關,我們直接在VM上安裝,VM上的環境是CentOS 7.9。Tyk API提供了許多中安裝方法,這裡使用CentOS的yum套件管理工具安裝Tyk API網關[18],大體步驟如下(演示皆以root權限操作)。

3.2.1 建立tyk gateway軟體來源

預設的yum repo中是不包含tyk gateway的,我們需要在/etc/yum.repos.d下方建立一個新的來源,即新建一個tyk_tyk-gateway.repo文件,其內容如下:

[tyk_tyk-gateway]
name=tyk_tyk-gateway
baseurl=https://packagecloud.io/tyk/tyk-gateway/el/7/$basearch
repo_gpgcheck=1
gpgcheck=0
enabled=1
gpgkey=https://packagecloud.io/tyk/tyk-gateway/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300

[tyk_tyk-gateway-source]
name=tyk_tyk-gateway-source
baseurl=https://packagecloud.io/tyk/tyk-gateway/el/7/SRPMS
repo_gpgcheck=1
gpgcheck=0
enabled=1
gpgkey=https://packagecloud.io/tyk/tyk-gateway/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

接下來我們執行下面指令來建立tyk_tyk-gateway這個repo的YUM快取:

$yum -q makecache -y --disablerepo='*' --enablerepo='tyk_tyk-gateway'
导入 GPG key 0x5FB83118:
 用户ID     : "https://packagecloud.io/tyk/tyk-gateway (https://packagecloud.io/docs#gpg_signing) <support@packagecloud.io>"
 指纹       : 9179 6215 a875 8c40 ab57 5f03 87be 71bd 5fb8 3118
 来自       : https://packagecloud.io/tyk/tyk-gateway/gpgkey
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

repo設定和快取完畢後​​,我們就可以安裝Tyk API Gateway了:

$yum install -y tyk-gateway
  • 1.

安裝後的tky-gateway將以一個systemd daemon服務[19]的形式存在於主機上,程式意外退出或虛機重新啟動後,該服務也會被systemd自動拉起。透過systemctl status指令可以查看服務的運作狀態:

# systemctl status tyk-gateway
● tyk-gateway.service - Tyk API Gateway
   Loaded: loaded (/usr/lib/systemd/system/tyk-gateway.service; enabled; vendor preset: disabled)
   Active: active (running) since 日 2023-11-19 20:22:44 CST; 12min ago
 Main PID: 29306 (tyk)
    Tasks: 13
   Memory: 19.6M
   CGroup: /system.slice/tyk-gateway.service
           └─29306 /opt/tyk-gateway/tyk --conf /opt/tyk-gateway/tyk.conf

11月 19 20:34:54 iZ2ze18rmx2avqb5xgb4omZ tyk[29306]: time="Nov 19 20:34:54" level=error msg="Connection to Redis faile...b-sub
11月 19 20:35:04 iZ2ze18rmx2avqb5xgb4omZ tyk[29306]: time="Nov 19 20:35:04" level=error msg="cannot set key in pollerC...ured"
11月 19 20:35:04 iZ2ze18rmx2avqb5xgb4omZ tyk[29306]: time="Nov 19 20:35:04" level=error msg="Redis health check failed...=main
Hint: Some lines were ellipsized, use -l to show in full.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

3.2.2 安裝redis

我們看到tyk-gateway已經成功啟動,但從其服務日誌來看,它在連接redis時報錯了!tyk gateway預設資料儲存在redis中,為了讓tyk gateway正常運行,我們還需要安裝redis!這裡我們使用容器的方式來安裝和執行一個redis服務:

$docker pull redis:6.2.14-alpine3.18
$docker run -d --name my-redis -p 6379:6379 redis:6.2.14-alpine3.18 
e5d1ec8d5f5c09023d1a4dd7d31d293b2d7147f1d9a01cff8eff077c93a9dab7
  • 1.
  • 2.
  • 3.

拉取並執行redis後,我們透過redis-cli驗證一下與redis server的連線:

# docker run -it --rm redis:6.2.14-alpine3.18  redis-cli -h 192.168.0.24
192.168.0.24:6379>
  • 1.
  • 2.

我們看到可以正常連線!但此時Tyk Gateway仍無法與redis正常連接,我們還需要對Tyk Gateway做一些設定調整!

3.2.3 配置Tyk Gateway

yum預設將Tyk Gateway安裝到/opt/tyk-gateway下面,這個路徑下的檔案佈局如下:

$tree -F -L 2 .
.
├── apps/
│   └── app_sample.json
├── coprocess/
│   ├── api.h
│   ├── bindings/
│   ├── coprocess_common.pb.go
│   ├── coprocess_mini_request_object.pb.go
│   ├── coprocess_object_grpc.pb.go
│   ├── coprocess_object.pb.go
│   ├── coprocess_response_object.pb.go
│   ├── coprocess_return_overrides.pb.go
│   ├── coprocess_session_state.pb.go
│   ├── coprocess_test.go
│   ├── dispatcher.go
│   ├── grpc/
│   ├── lua/
│   ├── proto/
│   ├── python/
│   └── README.md
├── event_handlers/
│   └── sample/
├── install/
│   ├── before_install.sh*
│   ├── data/
│   ├── init_local.sh
│   ├── inits/
│   ├── post_install.sh*
│   ├── post_remove.sh*
│   ├── post_trans.sh
│   └── setup.sh*
├── middleware/
│   ├── ottoAuthExample.js
│   ├── sampleMiddleware.js
│   ├── samplePostProcessMiddleware.js
│   ├── samplePreProcessMiddleware.js
│   ├── testPostVirtual.js
│   ├── testVirtual.js
│   └── waf.js
├── policies/
│   └── policies.json
├── templates/
│   ├── breaker_webhook.json
│   ├── default_webhook.json
│   ├── error.json
│   ├── monitor_template.json
│   └── playground/
├── tyk*
└── tyk.conf
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

其中tyk.conf就是tyk gateway的設定文件,我們先來看看其預設的內容:

$cat /opt/tyk-gateway/tyk.conf
{
  "listen_address": "",
  "listen_port": 8080,
  "secret": "xxxxxx",
  "template_path": "/opt/tyk-gateway/templates",
  "use_db_app_configs": false,
  "app_path": "/opt/tyk-gateway/apps",
  "middleware_path": "/opt/tyk-gateway/middleware",
  "storage": {
    "type": "redis",
    "host": "redis",
    "port": 6379,
    "username": "",
    "password": "",
    "database": 0,
    "optimisation_max_idle": 2000,
    "optimisation_max_active": 4000
  },
  "enable_analytics": false,
  "analytics_config": {
    "type": "",
    "ignored_ips": []
  },
  "dns_cache": {
    "enabled": false,
    "ttl": 3600,
    "check_interval": 60
  },
  "allow_master_keys": false,
  "policies": {
    "policy_source": "file"
  },
  "hash_keys": true,
  "hash_key_function": "murmur64",
  "suppress_redis_signal_reload": false,
  "force_global_session_lifetime": false,
  "max_idle_connections_per_host": 500
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

我們看到:storage下面儲存了redis的設定訊息,我們需要將redis的host設定修改為我們的VM位址:

"host": "192.168.0.24",
  • 1.

然後重啟Tyk Gateway服務:

$systemctl daemon-reload
$systemctl restart tyk-gateway
  • 1.
  • 2.

之後,我們再查看tyk gateway的運作狀態:

systemctl status tyk-gateway
● tyk-gateway.service - Tyk API Gateway
   Loaded: loaded (/usr/lib/systemd/system/tyk-gateway.service; enabled; vendor preset: disabled)
   Active: active (running) since 一 2023-11-20 06:54:07 CST; 41s ago
 Main PID: 20827 (tyk)
    Tasks: 15
   Memory: 24.8M
   CGroup: /system.slice/tyk-gateway.service
           └─20827 /opt/tyk-gateway/tyk --conf /opt/tyk-gateway/tyk.conf

11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Loading API configurations...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Tracking hostname" api_nam...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Initialising Tyk REST API ...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="API bind on custom port:0"...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Checking security policy: ...fault
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="API Loaded" api_id=1 api_n...ip=--
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Loading uptime tests..." p...k-mgr
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="Initialised API Definition...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=warning msg="All APIs are protected ...=main
11月 20 06:54:07 iZ2ze18rmx2avqb5xgb4omZ tyk[20827]: time="Nov 20 06:54:07" level=info msg="API reload complete" prefix=main
Hint: Some lines were ellipsized, use -l to show in full.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

從服務日誌來看,現在Tyk Gateway可以正常連線redis並提供服務了!我們也可以透過下面的命令來驗證網關的運作狀態:

$curl localhost:8080/hello
{"status":"pass","version":"5.2.1","description":"Tyk GW","details":{"redis":{"status":"pass","componentType":"datastore","time":"2023-11-20T06:58:57+08:00"}}}
  • 1.
  • 2.

「/hello」是Tyk Gateway的內建路由,由Tyk網關自己提供服務。

到這裡Tyk Gateway的安裝和簡單設定就結束了,接下來,我們就來看看API Gateway的主要功能特性,並藉助Tyk Gateway來展示一下這些功能特性。

註:查看Tyk Gateway的運行日誌,可以使用journalctl -u tyk-gateway -f指令即時follow最新日誌輸出。

3.3 功能特性:請求轉送與路由

請求轉送和路由是API Gateway的主要功能特性之一,API Gateway可以根據請求的路徑、方法、查詢參數等資訊將請求轉送到對應的後端服務,其核心與反向代理類似,不同之處在於API Gateway增加了「API」這層抽象,更專注於建置、管理和增強API。

下面我們來看看Tyk如何設定API路由,我們先建立一個新API。

3.3.1 建立一個新API

Tyk開源版支援兩種建立API的方式,一種是透過呼叫Tyk的控制類API [20],一種則是透過傳統的設定文件,放入特定目錄下[21]。無論哪種方式新增完API,最終都要透過Tyk Gateway熱載入(hot reload)或重新啟動才能生效。

註:Tyk Gateway的商業版本提供Dashboard,可以以圖形化的方式管理API,商業版本的API定義會放在Postgres或MongoDB中,我們這裡用開源版本,只能手動管理了,並且API定義只能放在文件中。

下面,我們就來在Tyk上建立一個新的API路由,該路由範例的示意圖如下:

圖片圖片

在未新增API之前,我們使用curl來存取該API路徑:

$curl localhost:8080/api/v1/no-authn
Not Found
  • 1.
  • 2.

Tyk Gateway由於找不到API路由,回傳Not Found。接下來,我們採用呼叫tyk gateway API的方式來新增路由:

$curl -v -H "x-tyk-authorization: {tyk gateway secret}" \
  -s \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{
    "name": "no-authn-v1",
    "slug": "no-authn-v1",
    "api_id": "no-authn-v1",
    "org_id": "1",   
    "use_keyless": true,
    "auth": {         
      "auth_header_name": "Authorization"
    },                
    "definition": {   
      "location": "header",
      "key": "x-api-version"
    },                
    "version_data": { 
      "not_versioned": true,    
      "versions": {             
        "Default": {            
          "name": "Default",    
          "use_extended_paths": true
        }                       
      }                         
    },                          
    "proxy": {                  
      "listen_path": "/api/v1/no-authn",
      "target_url": "http://localhost:18081/",
      "strip_listen_path": true
    },   
    "active": true
}' http://localhost:8080/tyk/apis | python -mjson.tool 

* About to connect() to localhost port 8080 (#0)
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /tyk/apis HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
> x-tyk-authorization: {tyk gateway secret}
> Content-Type: application/json
> Content-Length: 797
> 
} [data not shown]
* upload completely sent off: 797 out of 797 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Wed, 22 Nov 2023 05:38:40 GMT
< Content-Length: 53
< 
{ [data not shown]
* Connection #0 to host localhost left intact
{
    "action": "added",
    "key": "no-authn-v1",
    "status": "ok"
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.

從curl回傳結果我們看到:API已經成功加入。這時tyk gateway的安裝目錄/opt/tyk-gateway的子目錄apps下會新增一個名為no-authn-v1.json的設定文件,這個檔案內容較多,有300行,這裡就不貼出來了,這個檔案就是新增的no-authn  API的定義檔[22]

不過此刻,Tyk Gateway還需熱載入後才能為新的API提供服務,呼叫下面API可以觸發Tyk Gateway的熱載入:

$curl -H "x-tyk-authorization: {tyk gateway secret}" -s http://localhost:8080/tyk/reload/group | python -mjson.tool
{
    "message": "",
    "status": "ok"
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

註:即便觸發熱載入成功,但如果body中的json格式錯,例如多了一個結尾逗號,Tyk Gateway是不會報錯的!

API路由建立完畢並生效後,我們再來訪問API:

$ curl localhost:8080/api/v1/no-authn
{
    "error": "There was a problem proxying the request"
}
  • 1.
  • 2.
  • 3.
  • 4.

我們看到:Tyk Gateway回傳的已經不是「Not Found」了!現在我們建立一下no-authn這個API服務,考慮到適配更多後續範例,這裡建立這樣一個http server:

// api-gateway-examples/httpserver

func main() {    
    // 解析命令行参数   
    port := flag.Int("p", 8080, "Port number")  
    apiVersion := flag.String("v", "v1", "API version")  
    apiName := flag.String("n", "example", "API name")   
    flag.Parse()                                         
                                                         
    // 注册处理程序                                     
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {  
        fmt.Println(*r)                                                  
        fmt.Fprintf(w, "Welcome api: localhost:%d/%s/%s\n", *port, *apiVersion, *apiName)  
    })                                                                                     
                                                                                           
    // 启动HTTP服务器                                                                      
    addr := fmt.Sprintf(":%d", *port)  
    log.Printf("Server listening on port %d\n", *port)  
    log.Fatal(http.ListenAndServe(addr, nil))           
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

我們啟動一個該http server的實例:

$go run main.go -p 18081 -v v1 -n no-authn
2023/11/22 22:02:42 Server listening on port 18081
  • 1.
  • 2.

現在我們再透過tyk gateway呼叫一下no-authn這個API:

$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
  • 1.
  • 2.

我們看到這次路由通了!no-authn API回傳了期望的結果!

3.3.2 負載平衡

如果no-authn API存在多個服務實例,Tyk Gateway也可以將請求流量負載平衡到多個no-authn服務實例上去,下圖是Tyk Gateway進行請求流量負載平衡[ 23 ]的示意圖:


要實現負責均衡,我們需要調整no-authn API的定義,這次我們直接修改/opt/tyk-gateway/apps/no-authn-v1.json,變更的配置主要有三項:

// /opt/tyk-gateway/apps/no-authn-v1.json

  "proxy": {
    "preserve_host_header": false,
    "listen_path": "/api/v1/no-authn",
    "target_url": "",                  // (1) 改为""
    "disable_strip_slash": false,
    "strip_listen_path": true,
    "enable_load_balancing": true,     // (2) 改为true
    "target_list": [                   // (3) 填写no-authn服务实例列表
      "http://localhost:18081/",
      "http://localhost:18082/",
      "http://localhost:18083/"
    ],
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

修改完配置後,呼叫Tyk的控制類別API使之生效,然後我們啟動三個no-authn的API實例:

$go run main.go -p 18081 -v v1 -n no-authn
$go run main.go -p 18082 -v v1 -n no-authn
$go run main.go -p 18083 -v v1 -n no-authn
  • 1.
  • 2.
  • 3.

接下來,我們多次呼叫curl存取no-authn API:

$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18082/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18083/v1/no-authn

$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18082/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18083/v1/no-authn
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

我們看到:Tyk Gateway在no-authn API的各個實例之間做了一個等權重的輪詢。如果我們停掉實例3,再來存取該API,我們將得到下面結果:

$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18082/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Bad Request

$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Welcome api: localhost:18082/v1/no-authn
$curl localhost:8080/api/v1/no-authn
Bad Request
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

註:Tyk Gateway商業版透過Dashboard支援配置權重的RR負載平衡演算法[24]

我們看到:實例3已經下線,但Tyk Gateway並不會跳過該已經下線的實例,這在生產環境會為客戶端帶來不一致的回應。

3.3.3 服務實例存活檢測(uptime test)

Tyk Gateway在開啟負載平衡的時候,也提供了對後端服務實例的存活檢測機制,當某個服務實例down了後,負載平衡機制會繞過該實例將請求發送到下一個處於存活狀態的實例;而當down機實例恢復後,Tyk Gateway也能及時偵測到服務執行個體上線,並將其加入流量負載調度。

支援存活檢測(uptime test)的API定義配置如下:

// /opt/tyk-gateway/apps/no-authn-v1.json

"uptime_tests": {
    "disable": false,
    "poller_group":"",
    "check_list": [
      {
        "url": "http://localhost:18081/"
      },
      {
        "url": "http://localhost:18082/"
      },
      {
        "url": "http://localhost:18083/"
      }
    ],
    "config": {
      "enable_uptime_analytics": true,
      "failure_trigger_sample_size": 3,
      "time_wait": 300,
      "checker_pool_size": 50,
      "expire_utime_after": 0,
      "service_discovery": {
        "use_discovery_service": false,
        "query_endpoint": "",
        "use_nested_query": false,
        "parent_data_path": "",
        "data_path": "",
        "port_data_path": "",
        "target_path": "",
        "use_target_list": false,
        "cache_disabled": false,
        "cache_timeout": 0,
        "endpoint_returns_list": false
      },
      "recheck_wait": 0
    }
}

"proxy": {
    ... ...
    "enable_load_balancing": true,
    "target_list": [
      "http://localhost:18081/",
      "http://localhost:18082/",
      "http://localhost:18083/"
    ],
    "check_host_against_uptime_tests": true,
    ... ...
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

我們新增了uptime_tests的配置,uptime_tests的check_list中的url的值要與proxy中target_list中的值完全一樣,這樣Tyk Gateway才能將二者對應。另外proxy的check_host_against_uptime_tests要設定為true。

這樣配置並生效後,等我們將服務實例3停用後,後續到no-authn的請求就只會轉送到實例1和實例2了。而當恢復實例3運作後,Tyk Gateway又會將流量分擔到實例3上。

3.3.4 動態負載平衡

上面負載平衡範例中target_list中的目標實例的IP和連接埠的手動配置的,而在雲端原生時代,我們經常會基於容器承載API服務實例,當容器因故退出,並重新啟動一個新容器時,IP可能會發生變化,這樣上述的手動配置就無法滿足要求,這就對API Gateway提出了與服務發現組件集成的要求:透過服務發現組件動態獲取服務實例的訪問列表,進而實現動態負載平衡[ 25]

Tyk Gateway內建了主流服務發現元件(如Etcd、Consul、ZooKeeper等)的對接能力,鑑於環境所限,這裡就不舉例了,大家可以在Tyk Gateway的服務發現範例文件頁[26]找到與不同服務發現組件對接時的設定範例。

3.3.5 IP存取限制

針對每個API,API網關也提供IP存取限制的特性,例如Tyk Gateway就提供了IP白名單[27]和IP黑名單[28]功能,通常二選一開啟一種限制即可。

以白名單為例,凡是在白名單中的IP才被允許存取該API。下面是白名單配置範例:

// /opt/tyk-gateway/apps/no-authn-v1.json

  "enable_ip_whitelisting": true,
  "allowed_ips": ["12.12.12.12", "12.12.12.13", "12.12.12.14"],
  • 1.
  • 2.
  • 3.
  • 4.

生效後,當我們存取no-authn API時,會得到下面錯誤:

$curl localhost:8080/api/v1/no-authn
{
    "error": "access from this IP has been disallowed"
}
  • 1.
  • 2.
  • 3.
  • 4.

如果開啟的是黑名單,那麼凡是在黑名單中的IP都被禁止存取該API,以下是黑名單設定範例:

// /opt/tyk-gateway/apps/no-authn-v1.json

  "enable_ip_blacklisting": true,
  "blacklisted_ips": ["12.12.12.12", "12.12.12.13", "12.12.12.14", "127.0.0.1"],
  • 1.
  • 2.
  • 3.
  • 4.

生效後,當我們存取no-authn API時,會得到以下結果:

$curl 127.0.0.1:8080/api/v1/no-authn
{
    "error": "access from this IP has been disallowed"
}
  • 1.
  • 2.
  • 3.
  • 4.

到目前為止,我們的API網關和定義的API都處於「裸奔」狀態,因為沒有對客戶端進行身份認證,任何客戶端都可以存取到我們的API,顯然這不是我們期望的,接下來,我們就來看看API網關的一個重要功能特性:身分認證與授權。

3.4 功能特性:身分認證與授權

在《透過實例理解Go Web身份認證的幾種方式[29]》一文中,我們提到:建立全域的安全通道是任何身份認證方式的前提

3.4.1 建立安全通道,卸載TLS憑證

Tyk Gateway支援在Gateway層面統一設定TLS憑證[30],同時也扮演在Gateway卸載TLS憑證的角色:

圖片圖片

這次我們要在tyk.conf中進行配置,才能在Gateway層級生效。這裡我們借用《透過實例理解Go Web身分認證的幾種方式[31]》一文中產生的幾個憑證(大家可以在https://github.com/bigwhite/experiments/tree/master/authn-examples/ tls-authn/make_certs下載),並將它們放到/opt/tyk-gateway/certs/下面:

$ls /opt/tyk-gateway/certs/
server-cert.pem  server-key.pem
  • 1.
  • 2.

然後,我們在/opt/tyk-gateway/tyk.conf檔案中增加下面配置:

// /opt/tyk-gateway/tyk.conf 

  "http_server_options": {
    "use_ssl": true,
    "certificates": [
      {
        "domain_name": "server.com",
        "cert_file": "./certs/server-cert.pem",
        "key_file": "./certs/server-key.pem"
      }
    ]
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

之後,重啟tyk gateway服務,使得tyk.conf的配置修改生效。

註:在/etc/hosts中設定server.com為127.0.0.1。

現在我們用之前的http方式來存取no-authn的API:

$curl server.com:8080/api/v1/no-authn
Client sent an HTTP request to an HTTPS server.
  • 1.
  • 2.

由於全域啟用了HTTPS,採用http方式的請求將被拒絕。我們換成https方式存取:

// 不验证服务端证书
$curl -k https://server.com:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn

// 验证服务端的自签证书
$curl --cacert ./inter-cert.pem https://server.com:8080/api/v1/no-authn
Welcome api: localhost:18081/v1/no-authn
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

3.4.2 Mutual TLS雙向認證

在《透過實例理解Go Web身份認證的幾種方式[32]》一文中,我們介紹的第一種身份認證方式就是TLS雙向認證,那麼Tyk Gateway對MTLS的支援如何呢?Tyk官方文件[33]提到它既支援client mTLS [34],也支援upstream mTLS [35]

我們比較在意的是client mTLS,也就是客戶端在與Gateway建連後,Gateway會使用Client CA驗證客戶端的憑證!我最初認為這個Client CA的配置是在tyk.conf中,但找了許久,也沒有發現配置Client CA的地方。

在no-authn API的定義檔(no-authn-v1.json)中,我們做如下組態變更:

"use_mutual_tls_auth": true,
  "client_certificates": [
      "/opt/tyk-gateway/certs/inter-cert.pem"
  ],
  • 1.
  • 2.
  • 3.
  • 4.

但使用下面命令存取API時報錯:

$curl --key ./client-key.pem --cert ./client-cert.pem --cacert ./inter-cert.pem https://server.com:8080/api/v1/no-authn
{
    "error": "Certificate with SHA256 bc8717c0f2ea5a0b81813abb3ec42ef8f9bf60da251b87243627d65fb0e3887b not allowed"
}
  • 1.
  • 2.
  • 3.
  • 4.

如果將"client_certificates"的配置中的inter-cert.pem改為client-cert.pem,則是可以的,但個人感覺這很奇怪,不符合邏輯,將tyk gateway的文檔、issue甚至代碼翻了又翻,也沒找到合理的設定client CA的位置。

Tyk Gateway支援多種身分認證方式[36],以下我們來看一種使用較為廣泛的方式:JWT Auth。

主要JWT身份認證方式的原理和詳情,可以參考我之前的文章《透過實例理解Go Web身份認證的幾種方式[37]》。

3.4.3 JWT Token Auth

下面是我為這個範例做的一個示意圖:

圖片圖片

這是我們日常開發中經常遇到的場景,即透過portal用用戶名和密碼登入後便可以拿到一個jwt token,然後後續的訪問功能API的請求僅​​攜帶該jwt token即可。API Gateway對於portal/login API不做任何認證;而對後續的功能API請求,透過共用的secret(也稱為static secret)對請求中攜帶的jwt token進行簽章驗證。

portal/login API由於不進行authn,因此其配置與前面的no-authn API幾乎一致,只是API名稱、路徑和target_list有不同:

// apps/portal-login-v1.json

{
  "name": "portal-login-v1",
  "slug": "portal-login-v1",
  "listen_port": 0,
  "protocol": "",
  "enable_proxy_protocol": false,
  "api_id": "portal-login-v1",
  "org_id": "1",
  "use_keyless": true,
  ... ...
  "proxy": {
    "preserve_host_header": false,
    "listen_path": "/api/v1/portal/login",
    "target_url": "",
    "disable_strip_slash": false,
    "strip_listen_path": true,
    "enable_load_balancing": true,
    "target_list": [
      "http://localhost:28084"
    ],
    "check_host_against_uptime_tests": true,
  ... ... 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

對應的portal login API也不複雜:

// api-gateway-examples/portal-login/main.go

package main

import (
 "log"
 "net/http"
 "time"

 "github.com/golang-jwt/jwt/v5"
)

func main() {
 // 创建一个基本的HTTP服务器
 mux := http.NewServeMux()

 username := "admin"
 password := "123456"
 key := "iamtonybai"

 // for uptime test
 mux.HandleFunc("/health", func(w http.ResponseWriter, req *http.Request) {
  w.WriteHeader(http.StatusOK)
 })

 // login handler
 mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
  // 从请求头中获取Basic Auth认证信息
  user, pass, ok := req.BasicAuth()
  if !ok {
   // 认证失败
   w.WriteHeader(http.StatusUnauthorized)
   return
  }

  // 验证用户名密码
  if user == username && pass == password {
   // 认证成功,生成token
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "username": username,
    "iat":      jwt.NewNumericDate(time.Now()),
   })
   signedToken, _ := token.SignedString([]byte(key))
   w.Write([]byte(signedToken))
  } else {
   // 认证失败
   http.Error(w, "Invalid username or password", http.StatusUnauthorized)
  }
 })

 // 监听28084端口
 err := http.ListenAndServe(":28084", mux)
 if err != nil {
  log.Fatal(err)
 }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.

執行該login API服務後,我們用curl指令取得jwt token:

$curl -u 'admin:123456' -k https://server.com:8080/api/v1/portal/login
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDA3NTEyODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.-wC8uPsLHDxSXcEMxIxJ8O2l3aWtWtWKvhtmuHmgIMA
  • 1.
  • 2.

現在我們再來建立protected API:

// apps/protected-v1.json

{
  "name": "protected-v1",
  "slug": "protected-v1",
  "listen_port": 0,
  "protocol": "",
  "enable_proxy_protocol": false,
  "api_id": "protected-v1",
  "org_id": "1",
  "use_keyless": false,    // 设置为false, gateway才会进行jwt的验证
  ... ...
  "enable_jwt": true,      // 开启jwt
  "use_standard_auth": false,
  "use_go_plugin_auth": false,
  "enable_coprocess_auth": false,
  "custom_plugin_auth_enabled": false,
  "jwt_signing_method": "hmac",        // 设置alg为hs256
  "jwt_source": "aWFtdG9ueWJhaQ==",    // 设置共享secret: base64("iamtonybai")
  "jwt_identity_base_field": "username", // 设置代表请求中的用户身份的字段,这里我们用username
  "jwt_client_base_field": "",
  "jwt_policy_field_name": "",
  "jwt_default_policies": [
     "5e189590801287e42a6cf5ce"        // 设置security policy,这个似乎是jwt auth必须的
  ],
  "jwt_issued_at_validation_skew": 0,
  "jwt_expires_at_validation_skew": 0,
  "jwt_not_before_validation_skew": 0,
  "jwt_skip_kid": false,
  ... ...
  "version_data": {
    "not_versioned": true,
    "default_version": "",
    "versions": {
      "Default": {
        "name": "Default",
        "expires": "",
        "paths": {
          "ignored": null,
          "white_list": null,
          "black_list": null
        },
        "use_extended_paths": true,
        "extended_paths": {
          "persist_graphql": null
        },
        "global_headers": {
          "username": "$tyk_context.jwt_claims_username" // 设置转发到upstream的请求中的header字段username
        },
        "global_headers_remove": null,
        "global_response_headers": null,
        "global_response_headers_remove": null,
        "ignore_endpoint_case": false,
        "global_size_limit": 0,
        "override_target": ""
      }
    }
  },
  ... ...
  "enable_context_vars": true, // 开启上下文变量
  "config_data": null,
  "config_data_disabled": false,
  "tag_headers": ["username"], // 设置header
  ... ...
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

這個配置相對複雜許多,也是翻閱了很久資料才驗證通過的配置。JWT Auth必須有關聯的policy設置,我們在tyk gateway開源版中要想設定policy,需要現在tyk.conf中做以下設定:

// /opt/tyk-gateway/tyk.conf

  "policies": {
    "policy_source": "file",
    "policy_record_name": "./policies/policies.json"
  },
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

而policies/policies.json的內容如下:

// /opt/tyk-gateway/policies/policies.json
{
 "5e189590801287e42a6cf5ce": {
  "rate": 1000,
  "per": 1,
  "quota_max": 100,
  "quota_renewal_rate": 60,
  "access_rights": {
   "protected-v1": {
    "api_name": "protected-v1",
    "api_id": "protected-v1",
    "versions": [
     "Default"
    ]
   }
  },
  "org_id": "1",
  "hmac_enabled": false
 }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

上述設定完畢並重新啟動tyk gateway生效後,且protected api服務也已啟動時,我們造訪一下該API服務:

$curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDA3NTEyODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.-wC8uPsLHDxSXcEMxIxJ8O2l3aWtWtWKvhtmuHmgIMA" -k https://server.com:8080/api/v1/protected
invoke protected api ok
  • 1.
  • 2.

我們看到curl發出的請求成功通過了Gateway的驗證!並且透過protected API輸出的請求資訊來看,Gateway成功解析出username,並將其作為Header中的欄位傳遞給了protected API服務實例:

http.Request{Method:"GET", URL:(*url.URL)(0xc0002f6240), Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Accept":[]string{"*/*"}, "Accept-Encoding":[]string{"gzip"}, "Authorization":[]string{"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDA3NTEyODEsInVzZXJuYW1lIjoiYWRtaW4ifQ.-wC8uPsLHDxSXcEMxIxJ8O2l3aWtWtWKvhtmuHmgIMA"}, "User-Agent":[]string{"curl/7.29.0"}, "Username":[]string{"admin"}, "X-Forwarded-For":[]string{"127.0.0.1"}}, Body:http.noBody{}, GetBody:(func() (io.ReadCloser, error))(nil), ContentLength:0, TransferEncoding:[]string(nil), Close:false, Host:"localhost:28085", Form:url.Values(nil), PostForm:url.Values(nil), MultipartForm:(*multipart.Form)(nil), Trailer:http.Header(nil), RemoteAddr:"[::1]:55583", RequestURI:"/", TLS:(*tls.ConnectionState)(nil), Cancel:(<-chan struct {})(nil), Response:(*http.Response)(nil), ctx:(*context.cancelCtx)(0xc0002e34f0)}
  • 1.

如果不攜帶Authorization頭字段或jwt的token是錯誤的,那麼結果將如下所示:

$ curl -k https://server.com:8080/api/v1/protected
{
    "error": "Authorization field missing"
}

$ curl -k -H "Authorization: Bearer xxx" https://server.com:8080/api/v1/protected
{
    "error": "Key not authorized"
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

一旦通過API Gateway的身份認證,上游的API服務就會拿到客戶端身份,有了唯一身份後,就可以進行授權操作[38]了,其實policy設定本身也是一種授權存取控制。Tyk Gateway本身也支援RBAC等模型[39],也支援與OPA(open policy agent)等的集成,但更多是在商業版的tyk dashboard下完成的,這裡也就不重點說明了。

以下的Gateway的幾個主要功能特性由於試驗環境受限以及文章篇幅考慮,我不會像上述例子這麼細緻的說明了,只會簡單說明一下。

3.5 功能特性:流量控制與限速

Tyk Gateway內建提供了強大的流量控制功能,可透過全域等級和API等級的限速[40]來管理請求流量。此外,Tyk Gateway 也支援請求配額(request quota)[41]來限制每個使用者或應用程式在一個時間週期內的請求次數。

流量不僅和請求速度和數量有關係,與請求的大小也有關係,Tyk Gateway還支援在全局層面和API層面設定Request的size limit [42],以避免超大包對網關運行造成不良影響。

3.6 功能特性:高可用與容錯處理

在許多情況下,我們要為客戶確保服務水準(service level),例如:最大往返時間、最大回應延遲等。Tyk Gateway提供了一系列功能,可協助我們確保閘道器的高可用運作和SLA服務等級。

Tyk支援健康檢查[43],這對於確定Tyk Gateway的狀態極為重要,沒有健康檢查,就很難知道網關的實際運作狀態如何。

Tyk Gateway也內建了斷路器(circuit breaker) [44],這個斷路器是基於比例的,因此如果y個請求中的x請求都失敗了,斷路器就會跳閘,例如,如果x = 10,y = 100,則閾值百分比為10%。當失敗比例到達10%時,斷路器就會切斷流量,同時跳閘也會觸發一個事件,我們可以記錄和處理該事件。

當upstream的服務回應遲遲不歸時,Tyk Gateway也可以設定強制逾時[45],可確保服務始終在給定時間內回應。這在高可用性系統中非常重要,因為在這種系統中,反應效能至關重要,這樣才能乾淨利落地處理錯誤。

3.7 功能特性:監控與可觀測性

微服務時代,可觀測性對運維以及系統高可用的重要性不言而喻。Tyk Gateway在多年的演化過程中,也逐漸增加了對可觀測的支持,

可觀測主要分三大塊:

  • log

Tyk Gateway支援設定輸出日誌的等級(log level),預設是info等級。Tyk輸出的是結構化日誌,這使得它可以很好的與其他日誌收集查詢系統集成,Tyk支援與主流的日誌收集工具對接[46],包括:logstash、sentry、Graylog、Syslog等。

  • metrics

度量資料是反映網關係統健康狀況、錯誤計數和類型、IT基礎設施(伺服器、虛擬機器、容器、資料庫和其他後端元件)及其他流程的硬體資源資料的重要參考。維運團隊可以透過使用監控工具來利用即時度量的資料[47],識別運作趨勢、在系統故障時設定警報、確定問題的根本原因並緩解問題。

Tyk Gateway內建了對主流metrics採集方案Prometheus+Grafana的支援[48],可以在網關層面以及對API進行即時度量資料收集和展示。


  • tracing

Tyk Gateway從5.2版本開始支援了與服務Tracing界的標準:OpenTelemetry的整合[49],這樣你可以使用多種支援OpenTelemetry的Tracing後端,例如Jaeger、Datadog等。Tracing可在Gateway層級開啟,也可延展至API層級。

4. 小結

本文對已經相對成熟的API網關技術做了回顧,對API網關的演進階段、主流特性以及當前市面上的主流API網關進行了簡要說明,並以Go實現的Tyk Gateway社區開源版為例,以示例方式對API網關的主要功能做了介紹。

整體而言,Tyk Gateway是一款功能強大,社群相對活躍且有商業公司支援的產品,文件很豐富,但從實際使用層面,這些文件對Tyk社群版本的用戶來說並不友好,指導性不足(更多用商業版的Dashboard說明,與配置文件難於對應),就像本文例子中那樣,為了搞定JWT認證,筆者著實花了不少時間查閱資料,甚至閱讀源碼。

Tyk Gateway的配置設計平坦,沒有層次和邏輯,感覺是隨著時間隨意「堆砌」上去的。且設定檔更新時,如果出現格式問題,Tyk Gateway並不報錯,讓人難於確定設定是否真正生效了,只能用Tyk Gateway的控制API [50]去查詢結果來驗證,非常繁瑣低效。

本文涉及的源碼可以在這裡[51]下載,文中涉及的一些tyk gateway api和security policy的配置也可以在其中查看。

5. 參考資料

  • Leaving the Cloud [52]  - https://37signals.com/podcast/leaving-the-cloud/
  • The Past, Present, and Future of API Gateways [53]  - https://www.infoq.com/articles/past-present-future-api-gateways/
  • How moving from AWS to Bare-Metal saved us 230,000/yr [54]  - https://blog.oneuptime.com/moving-from-aws-to-bare-metal/
  • A Comprehensive Guide to API Gateways, Kubernetes Gateways, and Service Meshes [55]  - https://navendu.me/posts/gateway-and-mesh/
  • Use API gateways in microservices [56]  - https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway
  • The Tyk API Gateway and Postman [57]  - https://blog.postman.com/the-tyk-api-gateway-and-postman/
  • Getting Started with Tyk API Gateway with Keycloak [58]  - https://javascript.plainenglish.io/getting-started-to-tyk-api-gateway-with-keycloak-16307435584a
  • Observing your API traffic with Tyk, Elasticsearch & Kibana [59]  - https://medium.com/@asoorm/observing-your-api-metrics-with-tyk-elasticsearch-kibana-74e8fd946c39
  • Set up JWT token in tyk gateway [60]  - https://community.tyk.io/t/set-up-jwt-token-in-tyk-gateway/6572/9


參考資料

[1]  David Heinemeier Hansson: https://dhh.dk/ 

[2]將公司所有的業務都從公有雲搬遷到了自建的資料中心: https://37signals.com/podcast/leaving-the-cloud/  

[3]以單體應用的形式存在: https://tonybai.com/2023/10/09/service-weaver-coding-in-monolithic-deploy-in-microservices/  

[4] CNCF Landscape: https://https://landscape.cncf.io  

[5] Amazon的API Gateway: https://aws.amazon.com/cn/api-gateway/  

[6] Google Cloud的API Gateway: https://cloud.google.com/api-gateway  

[7] APISIX: https://apisix.apache.org/  

[8] Kong: https://konghq.com/  

[9] EMISSARY INGRESS: https://github.com/emissary-ingress/emissary  

[10] Tyk API閘道: https://tyk.io/blog/res-api-management-vendor-comparisons/  

[11]不代表Tyk API閘道就要比其他Go實現的API Gateway優秀: https://tyk.io/blog/enter-the-leader-tyk-recognised-as-a-leader-in-gartners-2023 -magic-quadrant-for-api-management/  

[12] Tyk API閘道: https://github.com/TykTechnologies/tyk  

[13] Open Core模式開源: https://opensource.com/article/21/11/open-core-vs-open-source  

[14]開源兼商業API管理和網關解決方案: https://tyk.io/docs/tyk-oss-gateway/  

[15] docker-compose: https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose  

[16] Kubernetes Operator: https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1  

[17] Tyk API閘道開源版本的功能詳情: https://tyk.io/docs/tyk-oss-gateway/  

[18]使用CentOS的yum套件管理工具安裝Tyk API閘道: https://tyk.io/docs/tyk-oss/ce-redhat-rhel-centos/  

[19] systemd daemon服務: https://tonybai.com/2016/12/27/when-docker-meets-systemd/  

[20]呼叫Tyk的控制類別API: https://tyk.io/docs/getting-started/create-api/#tutorial-create-an-api-with-the-tyk-gateway-api  

[21]透過傳統的設定文件,放入特定目錄下: https://tyk.io/docs/getting-started/create-api/#tutorial-create-an-api-in-file-based-mode  

[22] API的定義檔: https://tyk.io/docs/tyk-gateway-api/api-definition-objects/  

[23]請求流量負載平衡: https://tyk.io/docs/planning-for-production/ensure-high-availability/load-balancing/  

[24]支援配置權重的RR負載平衡演算法: https://tyk.io/docs/planning-for-production/ensure-high-availability/load-balancing/  

[25]動態負載平衡: https://tyk.io/docs/planning-for-production/ensure-high-availability/service-discovery/  

[26]服務發現範例文件頁面: https://tyk.io/docs/planning-for-production/ensure-high-availability/service-discovery/examples/  

[27] IP白名單: https://tyk.io/docs/tyk-apis/tyk-gateway-api/api-definition-objects/ip-whitelisting/  

[28] IP黑名單: https://tyk.io/docs/tyk-apis/tyk-gateway-api/api-definition-objects/ip-blacklisting/  

[29]透過實例理解Go Web認證的幾種方式: https://tonybai.com/2023/10/23/understand-go-web-authn-by-example  

[30]統一配置TLS憑證: https://tyk.io/docs/basic-config-and-security/security/tls-and-ssl/  

[31]透過實例理解Go Web認證的幾種方式: https://tonybai.com/2023/10/23/understand-go-web-authn-by-example/  

[32]透過實例理解Go Web認證的幾種方式: https://tonybai.com/2023/10/23/understand-go-web-authn-by-example  

[33]官方文件 Tyk:https://tyk.io/docs/basic-config-and-security/security/mutual-tls/  

[34] client mTLS: https://tyk.io/docs/basic-config-and-security/security/mutual-tls/client-mtls  

[35] upstream mTLS: https://tyk.io/docs/basic-config-and-security/security/mutual-tls/upstream-mtls  

[36] Tyk Gateway支援多種身分認證方式: https://tyk.io/docs/apim-best-practice/api-security-best-practice/authentication/  

[37]透過實例理解Go Web認證的幾種方式: https://tonybai.com/2023/10/23/understand-go-web-authn-by-example/  

[38]授權作業: https://tonybai.com/2023/11/04/understand-go-web-authz-by-example/  

[39]支持RBAC等模型: https://tyk.io/docs/tyk-dashboard/rbac/#understanding-the-concept-of-users-and-permissions  

[40]全域等級與API等級的速率限制: https://tyk.io/docs/basic-config-and-security/control-limit-traffic/rate-limiting/  

[41]支援請求配額(request quota): https://tyk.io/docs/basic-config-and-security/control-limit-traffic/request-quotas/  

[42]設定Request的size limit: https://tyk.io/docs/basic-config-and-security/control-limit-traffic/request-size-limits/  

[43] Tyk支持健康檢查: https://tyk.io/docs/planning-for-production/ensure-high-availability/health-check/  

[44]內建了斷路器(circuit breaker): https://tyk.io/docs/planning-for-production/ensure-high-availability/circuit-breakers/  

[45]設定強制逾時: https://tyk.io/docs/planning-for-production/ensure-high-availability/enforced-timeouts/  

[46] Tyk支援與主流的日誌收集工具對接: https://tyk.io/docs/log-data/#logging  

[47]使用監控工具來利用即時度量的資料: https://tyk.io/docs/planning-for-production/monitoring/  

[48]對主流metrics採集方案Prometheus+Grafana的支援: https://tyk.io/blog/service-level-objectives-for-your-apis-with-tyk-prometheus-and-grafana/  

[49]支援了與服務Tracing界的標準:OpenTelemetry的整合: https://tyk.io/docs/product-stack/tyk-gateway/advanced-configurations/distributed-tracing/open-telemetry/open-telemetry- overview/  

[50] Tyk Gateway的控制API: https://tyk.io/docs/tyk-gateway-api/  

[51]這裡: https://github.com/bigwhite/experiments/tree/master/api-gateway-examples  

[52] Leaving the Cloud: https://37signals.com/podcast/leaving-the-cloud/  

[53] The Past, Present, and Future of API Gateways: https://www.infoq.com/articles/past-present-future-api-gateways/  

[54] How moving from AWS to Bare-Metal saved us 230,000/yr: https://blog.oneuptime.com/moving-from-aws-to-bare-metal/  

[55] A Comprehensive Guide to API Gateways, Kubernetes Gateways, and Service Meshes: https://navendu.me/posts/gateway-and-mesh/  

[56] Use API gateways in microservices: https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway  

[57] The Tyk API Gateway and Postman: https://blog.postman.com/the-tyk-api-gateway-and-postman/  

[58] Getting Started with Tyk API Gateway with Keycloak: https://javascript.plainenglish.io/getting-started-to-tyk-api-gateway-with-keycloak-16307435584a  

[59] Observing your API traffic with Tyk, Elasticsearch & Kibana: https://medium.com/@asoorm/observing-your-api-metrics-with-tyk-elasticsearch-kibana-74e8fd946c39  

[60] Set up JWT token in tyk gateway: https://community.tyk.io/t/set-up-jwt-token-in-tyk-gateway/6572/9  

[61] “Gopher部落”知識星球: https://public.zsxq.com/groups/51284458844544  

[62]連結網址: https://m.do.co/c/bff6eed92687