欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~html
本文来自 云+社区翻译社,做者 ArrayZoneYour
Nginx每每是构建微服务中必不可缺的一部分,从本文中你能够习得如何使用Nginx做为API网关。nginx
HTTP API是现代应用架构的核心。HTTP协议使开发者能够更快地构建应用并使应用的维护变得更加容易。HTTP API提供了一套通用的接口,这使得在任意的应用规模下,咱们均可以借助HTTP API从一个基本的微服务开始构建出一个具备完备功能的总体。借助HTTP,普通的web应用程序也能够在规模巨大的互联网上提供高性能、高可用的API。git
若是你还不理解API网关对微服务应用的重要性,能够参阅Building Microservices: Using an API Gatewaygithub
做为领先的高性能、轻量级反向代理和负载均衡器解决方案,NGINX Plus具备处理API流量所需的高级HTTP处理能力。这使得NGINX Plus成为构建API网关的理想平台。在本文中,咱们将使用一些常见的API网关为例展现如何配置NGINX Plus来以高效、可扩展、易维护的方式处理它们。最后咱们会获得一套可做为生产环境部署基础的完整配置。web
注:除特殊注明外,本文中全部的配置同时适用于NGINX和NGINX Plus。正则表达式
API网关的主要功能是为不一样的API分别提供单独,一致的入口点,它的实现与后端的实现与部署方式无关。实际场景中,每每不是全部的API都是以微服务的方式实现的。咱们的API网关须要同时管理现有的API、巨无霸式的API(monoliths, 对与微服务相对的庞然大物的戏称)以及开始局部切换为微服务的应用等等。json
在本文中,咱们假想一个库存管理的API(WareHouse API)为例进行说明。咱们使用实例的配置代码来讲明不一样的用例。咱们假设的API是一个RESTful API,它接受JSON请求并生成JSON数据响应请求。虽然咱们本文中是以RESTful API为例进行讲解,可是NGINX Plus做为API网关部署时并不要求或者限制JSON的使用;NGINX Plus自己并不知道API使用的架构或者数据格式。后端
WareHouse API 做为一组独立的微服务之一被实现并做为一个单独的API进行发布。其下的inventory 和 pricing 资源分别做为单独的服务集成并部署在不一样的后端上。由此能够画出以下的API路径结构:api
api └── warehouse ├── inventory └── pricing
举例来讲,若是咱们想得到仓库的库存信息,则须要经过客户端发送一个 HTTP GET
请求到/api/warehouse/inventory.浏览器
API结构示意图
咱们使用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指令将处理权移交给策略块
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移交给策略部分的代码块进行处理。
location /api/warehouse/inventory
基于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上下载或查看。在本系列的下一篇文章中咱们将探讨更高级的用例以保护后端服务免受恶意或者非法操做的用户的侵害。
问答
如何用nginx编写url重写?
相关阅读
如何用Nginx快速搭建一个安全的微服务架构
Nginx 原理解析和配置摘要
使用API网关构建微服务
此文已由做者受权腾讯云+社区发布,原文连接:https://cloud.tencent.com/dev...
欢迎你们前往腾讯云+社区或关注云加社区微信公众号(QcloudCommunity),第一时间获取更多海量技术实践干货哦~