歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~html
本文來自雲+社區翻譯社,做者ArrayZoneYournginx
Nginx每每是構建微服務中必不可缺的一部分,從本文中你能夠習得如何使用Nginx做爲API網關。git
HTTP API是現代應用架構的核心。HTTP協議使開發者能夠更快地構建應用並使應用的維護變得更加容易。HTTP API提供了一套通用的接口,這使得在任意的應用規模下,咱們均可以藉助HTTP API從一個基本的微服務開始構建出一個具備完備功能的總體。藉助HTTP,普通的web應用程序也能夠在規模巨大的互聯網上提供高性能、高可用的API。github
若是你還不理解API網關對微服務應用的重要性,能夠參閱Building Microservices: Using an API Gatewayweb
做爲領先的高性能、輕量級反向代理和負載均衡器解決方案,NGINX Plus具備處理API流量所需的高級HTTP處理能力。這使得NGINX Plus成爲構建API網關的理想平臺。在本文中,咱們將使用一些常見的API網關爲例展現如何配置NGINX Plus來以高效、可擴展、易維護的方式處理它們。最後咱們會獲得一套可做爲生產環境部署基礎的完整配置。正則表達式
注:除特殊註明外,本文中全部的配置同時適用於NGINX和NGINX Plus。json
API網關的主要功能是爲不一樣的API分別提供單獨,一致的入口點,它的實現與後端的實現與部署方式無關。實際場景中,每每不是全部的API都是以微服務的方式實現的。咱們的API網關須要同時管理現有的API、巨無霸式的API(monoliths, 對與微服務相對的龐然大物的戲稱)以及開始局部切換爲微服務的應用等等。後端
在本文中,咱們假想一個庫存管理的API(WareHouse API)爲例進行說明。咱們使用實例的配置代碼來講明不一樣的用例。咱們假設的API是一個RESTful API,它接受JSON請求並生成JSON數據響應請求。雖然咱們本文中是以RESTful API爲例進行講解,可是NGINX Plus做爲API網關部署時並不要求或者限制JSON的使用;NGINX Plus自己並不知道API使用的架構或者數據格式。api
WareHouse API 做爲一組獨立的微服務之一被實現並做爲一個單獨的API進行發佈。其下的inventory 和 pricing 資源分別做爲單獨的服務集成並部署在不一樣的後端上。由此能夠畫出以下的API路徑結構:瀏覽器
api
└── warehouse
├── inventory
└── pricing
複製代碼
舉例來講,若是咱們想得到倉庫的庫存信息,則須要經過客戶端發送一個 HTTP GET
請求到/api/warehouse/inventory
咱們使用NGINX Plus做爲API網關的好處是它能夠同時扮演反向代理、負載均衡器以及現有HTTP流量所需的web服務器這三個角色。若是NGINX Plus已是你的應用交付棧的一部分,那麼你不須要再用它部署一個單獨的API網關。不過,API網關預期的默認行爲與基於瀏覽器的流量所指望的默認行爲不一樣,所以咱們須要將API網關配置與現存(將來)的基於瀏覽器所需的流量對應的配置文件分來。
爲了實現上述需求,咱們爲配置文件建立了如下目錄結構來支持多用途的NGINX Plus實例,這也爲經過CI / CD 管道自動配置並部署提供了便利。
etc/
└── nginx/
├── api_conf.d/ ....................................... API配置的子目錄
│ └── warehouse_api.conf ...... Warehouse API 的定義及配置
├── api_backends.conf ..................... 後端服務配置 (upstreams)
├── api_gateway.conf ........................ API網關服務器的頂級配置
├── api_json_errors.conf ............ JSON格式的HTTP錯誤響應配置
├── conf.d/
│ ├── ...
│ └── existing_apps.conf
└── nginx.conf
複製代碼
API網關配置的目錄和文件名都加了**api_**前綴。上面的每一個目錄和文件都對應着API網關的不一樣功能和特性,咱們在下面會逐個詳細解釋。
NGINX讀取配置將從主配置文件nginx.conf開始。爲了讀取API網關配置,咱們須要在nginx.conf中http
塊中添加一條指令來引用包含網關配置的文件api_gateway.conf (大概在28行附近)。從文件內容中咱們能夠看到nginx.conf中默認從conf.d子目錄中讀取基於瀏覽器的HTTP配置。本文中將普遍使用include
命令來提升可讀性並實現部分配置的自動化。
include /etc/nginx/api_gateway.conf; # 全部的API網關配置
include /etc/nginx/conf.d/*.conf; # 正常的web流量配置
複製代碼
api_gateway.conf文件定義了將NGINX Plus做爲API網關暴露給客戶端的虛擬服務器的配置。該配置將暴露全部由API網關發佈的API,入口位於https://api.example.com/,用TLS協議加密保護。注意這裏使用的配置文件是針對HTTPS的——並無使用明文傳輸的HTTP。這表明着咱們默認並要求API客戶端知道正確的入口點並使用HTTPS鏈接。
log_format api_main '$remote_addr - $remote_user [$time_local] "$request"'
'$status $body_bytes_sent "$http_referer" "$http_user_agent"'
'"$http_x_forwarded_for" "$api_name"';
include api_backends.conf;
include api_keys.conf;
server {
set $api_name -; # Start with an undefined API name, each API will update this value
access_log /var/log/nginx/api_access.log api_main; # Each API may also log to a separate file
listen 443 ssl;
server_name api.example.com;
# TLS 配置
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1.1 TLSv1.2;
# API 定義, 每一個文件對應一個
include api_conf.d/*.conf;
# 錯誤響應
error_page 404 = @400; # 處理非法URI路徑的請求
proxy_intercept_errors on; # 不將後端的錯誤消息發送給客戶端
include api_json_errors.conf; # 定義返回給客戶端的JSON響應數據
default_type application/json; # 若是不指定 content-type 則默認爲 JSON
}
複製代碼
以上配置是靜態的,表如今每一個獨立API的細節以及響應的後端服務是經過include
命令引用相應的文件實現的。上面文件的最後四行負責處理默認的日誌輸出以及錯誤處理。咱們將在後面的 錯誤響應 一節中單獨討論。
一些API能夠經過單個後端實現,可是出於彈性或者負載均衡等緣由,咱們一般指望有不止一個後端。經過微服務的API,咱們能夠爲每一個服務定義單獨的後端,將他們組合在一塊兒就造成了完整的API。在本文中,咱們的倉儲API被部署爲兩個獨立的服務,每個都有多個後端。
upstream warehouse_inventory {
zone inventory_service 64k;
server 10.0.0.1:80;
server 10.0.0.2:80;
server 10.0.0.3:80;
}
upstream warehouse_pricing {
zone pricing_service 64k;
server 10.0.0.7:80;
server 10.0.0.8:80;
server 10.0.0.9:80;
}
複製代碼
由API網關發佈的全部API的全部後端API服務均在api_backends.conf中被定義。這裏咱們在每一個塊中使用了多個IP地址-端口對來指示API代碼的部署位置,咱們也可使用主機名來替換IP地址。NGINX Plus 的訂閱用戶還可使用動態的DNS負載均衡功能自動地將新的後端添加至在線運行配置。
這部分配置首先定義了Warehouse API的有效URI,而後定義了處理Warehouse API請求所用的通用策略。
# API 定義
#
location /api/warehouse/inventory {
set $upstream warehouse_inventory;
rewrite ^ /_warehouse last;
}
location /api/warehouse/pricing {
set $upstream warehouse_pricing;
rewrite ^ /_warehouse last;
}
# 策略
#
location = /_warehouse {
internal;
set $api_name "Warehouse";
# 在這裏配置相應的策略 (認證, 限速, 日誌記錄, ...)
proxy_pass http://$upstream$request_uri;
}
複製代碼
Warehouse API 經過一系列配置塊來定義。NGINX Plus具備靈活和高效的系統,這使得它能夠將請求的URI與相應的配置塊匹配。通常來講請求會經過具體的路徑前綴進行匹配,location
指令的順序並不重要。在上面的配置中咱們在第三行和第八行定義了兩個路徑前綴。在每一個配置中,$upstream
變量被設定爲分別表明 inventory 和 pricing 的後端API服務。
此處這樣配置的目的是將API的定義與API的交付邏輯分離。爲了實現這一目標,咱們儘可能減小了API定義部分的配置內容。當咱們爲每一個 location 肯定了合適的 upstream 組以後,能夠使用指令來查找相應的API策略。
rewrite
指令的結果是NGINX Plus搜索開頭爲**/_warehouse**的URI對應的 location 塊。上面的配置中使用了 = 修飾符來進行精確匹配,這提高了處理的速度。
在這個階段,咱們的策略塊內容很是簡單。在配置中的 iternal 意味着客戶端不能直接向它發出請求。$api_name
變量被從新定義爲匹配API的名稱,以便它能夠在日誌文件中正常顯示。最後請求會經過使用 $request_uri 變量(包含未修改的原始請求URI)代理至API定義部分中指定的 upstreame 組。
API的定義有兩種方法——寬鬆的或者精確的。每一個API最適合的方法取決於API的安全要求以及後端服務是否須要處理無效的URI。
在warehouse_api.simple.conf文件中,咱們使用了寬鬆的方式來定義Warehouse API。這意味着任何前綴知足要求的URI都會被代理到相應的後端服務,即如下URI的API請求都會被做爲有效URI進行處理:
若是咱們只須要考慮將每一個請求代理到正確的後端服務,那麼寬鬆的定義能夠提供最快的處理速度和最緊湊的配置。相對地,使用精確的定義方法能夠經過明肯定義每一個可用API資源的URI路徑來了解API的完整URI空間。Warehouse API 的下列配置結合使用徹底匹配 ( = ) 和正則表達式 ( ~ ) 實現了對每一個URI的精確匹配。
location = /api/warehouse/inventory { # Complete inventory
set $upstream inventory_service;
rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]*$ { # Shelf inventory
set $upstream inventory_service;
rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/inventory/shelf/[^/]*/box/[^/]*$ { # Box on shelf
set $upstream inventory_service;
rewrite ^ /_warehouse last;
}
location ~ ^/api/warehouse/pricing/[^/]*$ { # Price for specific item
set $upstream pricing_service;
rewrite ^ /_warehouse last;
}
複製代碼
上面的配置雖然囉嗦一點,可是更準確地描述了後端服務實現的資源。這可使後端服務免受惡意用戶請求的影響,可是會增長額外的開銷來處理正則表達式的匹配。在這種配置下,NGINX Plus會接受部分URI,其他的會被視爲無效而被拒絕:
使用精確的API定義能夠利用現有的API文檔格式驅動API網關的配置,使OpenAPI規範(過去稱爲Swagger)下的NGINX Plus API定義自動化。本文配套提供了相應的示例腳本。
隨着API的發展,有時出現的突發狀況或變化要求更新客戶端的請求。一個典型的例子就是原有的API資源被重命名或者移除。與web瀏覽器不一樣,API網關並不能向客戶端發送帶有API新的命名的重定向。不過幸運的是,咱們能夠經過重寫客戶端請求來解決這個問題。
在下面的代碼中,咱們能夠看到在第三行的位置,pricing
服務以前是做爲inventory
服務的一部分實現的。因此如今咱們使用rewrite
指令來將舊的pricing
資源請求切換至了對新的pricing
資源的請求。
# 重寫規則
#
rewrite ^/api/warehouse/inventory/item/price/(.*) /api/warehouse/pricing/$1;
# API 定義
#
location /api/warehouse/inventory {
set $upstream inventory_service;
rewrite ^(.*)$ /_warehouse$1 last;
}
location /api/warehouse/pricing {
set $upstream pricing_service;
rewrite ^(.*) /_warehouse$1 last;
}
# 處理策略
#
location /_warehouse {
internal;
set $api_name "Warehouse";
# 在這裏配置相應的策略 (認證, 限速, 日誌記錄, ...)
rewrite ^/_warehouse/(.*)$ /$1 break; # 移除 /_warehouse 前綴
proxy_pass http://$upstream; # 代理重寫後的URI
}
複製代碼
不過使用重寫URI也意味着在上面代碼的倒數第二行咱們處理代理請求的時候不能再使用$request_uri
變量(像warehouse_api_simple.conf的第21行的作法同樣)。因此咱們須要在上述代碼的第9行和第14行的位置使用不一樣的rewrite
指令以後將URI移交給策略部分的代碼塊進行處理。
基於HTTP API和瀏覽器的流量之間的一個關鍵區別是錯誤傳遞給客戶端的方式。當咱們配置NGINX Plus做爲API網關時,咱們將其配置其以最適合API客戶端的方式返回錯誤信息。
# 錯誤響應
error_page 404 = @400; # 處理非法URI路徑的請求
proxy_intercept_errors on; # 不將後端的錯誤消息發送給客戶端
include api_json_errors.conf; # 定義返回給客戶端的JSON響應數據
default_type application/json; # 若是不指定 content-type 則默認爲 JSON
複製代碼
上面的代碼展現了咱們在頂層的API網關中關於錯誤響應的配置。
因爲上面第二行的配置,當請求不可以匹配到任何的API定義時,咱們將返回該行定義的錯誤而不是NGINX Plus默認的錯誤響應給客戶端。這個可選的行爲要求客戶端按照知足API文檔規範的方式進行請求,這避免了未經受權的用戶經過API網關發現API的URI結構。
proxy_interceprt_errors
指的是後端服務生成的錯誤信息。原始的錯誤信息可能包含着錯誤的堆棧信息或者其餘以及一些其餘咱們不但願客戶端看到的敏感信息。打開這一配置以後,咱們將錯誤信息標準化以後再發送給客戶端,從而進一步提高信息的安全級別。
再下一行,咱們經過include
指令引入了錯誤響應的完整列表,下面展現了其中的前幾行。若是你想採用JSON之外的其餘錯誤格式,那麼你能夠修改最後一行default_type
指定的內容。你還能夠在每一個API的策略塊中使用include
指令來導入列表覆蓋默認的錯誤響應。
error_page 400 = @400;
location @400 { return 400 '{"status":400,"message":"Bad request"}\n'; }
error_page 401 = @401;
location @401 { return 401 '{"status":401,"message":"Unauthorized"}\n'; }
error_page 403 = @403;
location @403 { return 403 '{"status":403,"message":"Forbidden"}\n'; }
error_page 404 = @404;
location @404 { return 404 '{"status":404,"message":"Resource not found"}\n'; }
複製代碼
在配置完成以後,此時客戶端發送無效的URI請求時會獲得以下響應:
$ curl -i https://api.example.com/foo
HTTP/1.1 400 Bad Request
Server: nginx/1.13.10
Content-Type: application/json
Content-Length: 39
Connection: keep-alive
{"status":400,"message":"Bad request"}
複製代碼
在發佈API時,咱們一般都會經過身份認證來保護它們。NGINX Plus提供了幾種方法來保護API以及驗證API客戶端。相關的具體信息能夠參閱NGINX官方文檔中的IP address‑based access control lists,digital certificate authentication以及HTTP Basic authentication部分。在本文中,咱們將專一於適用於API的認證方法。
API祕鑰是客戶端和API網關同時掌握其內容的共享祕鑰。其本質就是一個長度很長的複雜密碼,它一般做爲一個長期憑證提供給API客戶端。建立API祕鑰的操做十分簡單,你只須要像下面同樣編碼一個隨機數便可。
$ openssl rand -base64 18 7B5zIqmRGXmrJTFmKa99vcit
複製代碼
如今回到頂層的API網關配置文件api_gateway.conf,能夠看到第6行咱們include
了一個名爲api_key.conf的文件,它包含着每一個API客戶端的API祕鑰信息以及相匹配的客戶端名稱或相關描述。
map $http_apikey $api_client_name {
default "";
"7B5zIqmRGXmrJTFmKa99vcit" "client_one";
"QzVV6y1EmQFbbxOfRCwyJs35" "client_two";
"mGcjH8Fv6U9y3BVF9H3Ypb9T" "client_six";
}
複製代碼
能夠看到API祕鑰被定義在上面展現的代碼塊當中。其中的map
指令接受了兩個參數。第一個參數定義了尋找API祕鑰的位置,這裏咱們經過獲取客戶端HTTP請求頭中的apikey
做爲變量$http_api_key
接收。第二個參數建立了一個新變量$api_client_name
而且將其與第一個參數即同行的API祕鑰相匹配。
此時,若是客戶端提供了API祕鑰7B5zIqmRGXmrJTFmKa99vcit
是,變量$api_client_name
會被設置爲client_one
。這個變量能夠用於檢驗經過身份驗證的客戶端以及對日誌的進一步審計。
能夠看到map
塊的格式很是簡單,這使得咱們能夠很容易地將api_keys.conf的生成集成到自動化的工做流當中。以後能夠在API的策略塊中完成API祕鑰的校驗邏輯。
# 策略塊
#
location = /_warehouse {
internal;
set $api_name "Warehouse";
if ($http_apikey = "") {
return 401; # Unauthorized (please authenticate)
}
if ($api_client_name = "") {
return 403; # Forbidden (invalid API key)
}
proxy_pass http://$upstream$request_uri;
}
複製代碼
咱們但願發送請求的客戶端都在它們的HTTP頭部中指定apikey
內容爲客戶端持有的API祕鑰。若是沒有HTTP頭信息或者其中沒有apikey
,咱們將返回給客戶端401
狀態碼要求其完成認證。若是客戶端發送的API祕鑰不存在於api_keys.conf當中,$api_client_name
會被設置爲默認值即空字符串——此時咱們將返回403
狀態碼來告訴客戶端其認證無效。
完成以上配置以後,Warehouse API如今已經能夠支持API祕鑰校驗了。
$ curl https://api.example.com/api/warehouse/pricing/item001
{"status":401,"message":"Unauthorized"}
$ curl -H "apikey: thisIsInvalid" https://api.example.com/api/warehouse/pricing/item001
{"status":403,"message":"Forbidden"}
$ curl -H "apikey: 7B5zIqmRGXmrJTFmKa99vcit" https://api.example.com/api/warehouse/pricing/item001
{"sku":"item001","price":179.99}
複製代碼
如今,JSON Web Token ( JWT )已經愈來愈普遍地被應用於API認證。不過要注意的是原生JWT支持是NGINX Plus纔有的特性。關於如何啓用JWT支持能夠參閱Authenticating API Clients with JWT and NGINX Plus。
本文是部署NIGNX Plus做爲API網關係列文章中的第一篇。本文中使用到的全部文件能夠在咱們的GitHub Gist repo上下載或查看。在本系列的下一篇文章中咱們將探討更高級的用例以保護後端服務免受惡意或者非法操做的用戶的侵害。
問答
相關閱讀
此文已由做者受權騰訊雲+社區發佈,原文連接:https://cloud.tencent.com/developer/article/1149103?fromSource=waitui
歡迎你們前往騰訊雲+社區或關注雲加社區微信公衆號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~