<返回更多

基于 OpenResty 的接口网关设计

2019-08-14    
加入收藏

本文讲述基于 OpenResty 的接口网关设计,主要谈及接口网关的请求路由安全认证IP 与 URI 白名单、加解密与验签名流程等)这两部分内容,其中涉及到的 NginxOpenResty等相关内容会作简单介绍。

〇、前言

笔者曾参与开发两个接口网关的项目,一个是基于 Tomcat 的应用提供的网关服务,另一个是基于 OpenResty 的 Nginx 应用提供的网关服务。经过两个网关项目的开发,笔者在接口网关开发方面稍微积累了一些经验,故在此把这些经验分享出来一起交流学习。由于基于 OpenResty 的 Nginx 网关普遍被认为是更优的方案,故本文主要针对基于 OpenResty 的 Nginx 网关进行讲述。当然,由于不同的并发数量级,不同的业务场景,接口网关的设计多种多样,本文所述其中较为简单且轻量级的一种。

一、什么是接口网关

 

基于 OpenResty 的接口网关设计

 

 

1.1 定位

接口网关,顾名思义,是企业 IT 在系统边界上提供给外部访问内部接口服务的统一入口。这里的外部可以指客户端、浏览器或者第三方应用等,在这种情况下,接口网关可以有多种定位:

在笔者的工作中,同样把面向客户端的网关称作 APIGateway,把作为开放平台提供给第三方服务的网关称作 OpenApi。本文主要以 OpenApi 作为接口网关为例来讲述。

1.2 功能

作为企业 IT 系统的统一入口,接口网关可提供请求路由与组合协议转换安全认证服务鉴权流量控制日志监控等服务。在笔者的工作中,主要在接口网关上实现了请求路由安全认证的功能,题目中所说的“设计”,主要是指请求路由与安全认证方面,暂不涉及流量控制或日志监控等其他方面的设计。

二、为什么需要接口网关

正如上文所言,网关接口为企业应用提供了丰富的功能,而笔者在工作中开发的接口网关主要提供请求路由与安全认证的功能,那么在回答“为什么需要接口网关”的时候,需要对这两者多加阐述。

2.1 请求路由

企业提供内外两网,在没有接口网关时,提供外部服务的应用需要部署在外网。随着服务的增多,部署在外网的应用越来越多,在服务的安全压力与维护成本增大的情况下,需要一个统一的接口网关“隔离”内外服务。企业提供的服务(无论内部服务还是外部服务)均部署在内网,而由部署在外网的网关接受请求,并路由到内网服务。在这种情况下,既有利于对外屏蔽企业内部服务部署细节,提供统一的服务访问地址,又便于管理与维护内外部服务接口,便于演进与重构服务。这是接口网关提供请求路由的作用。

2.2 安全认证

在没有接口网关时,企业对外服务直接由外部访问,身份验证与数据加解密等工作都需要每一个对外服务本身去处理增加了服务本不该有的职责,并且增加了服务开发的难度与工作量。实际在大多数情况下,可以将身份验证与数据加解密等安全工作可以从服务抽离,统一由接口网关负责处理。接口网关作为入口,对外验证调用方的 IP,身份以及接口访问权限等,并且可以解密数据后再将请求路由到服务。这是接口网关提供安全认证的功能。

以上是实际工作中涉及的为什么需要接口网关的其中两个原因,当然原因远不止此,有兴趣的读者可以阅读其他文章,比如 《谈API网关的背景、架构以及落地方案》 或者 《微服务:从设计到部署》(英文原文:Microservices: From Design to Deployment)。接下来的章节我们开始探讨如何开发接口网关。

三、如何开发接口网关

我们先看看工作中设计的提供请求路由安全认证功能的接口网关的架构。

不过在介绍接口网关的设计之前,我们先来了解一下关于 Nginx 与 OpenResty 的基础知识。

3.1 Nginx 与 OpenResty 简介

3.1.1 Nginx 简介

Nginx 是世界第二大 Web 服务器,仅次于 Apache,然而由于其极高的性能可处理海量的互联网请求,现在已经成为业界高性能 Web 服务器的代名词

它的主要特征是高性能、高扩展性、高可靠性、低内存消耗、单机支持 10 万以上的并发连接,支持热部署,以及使用最自由的 BSD 许可协议。其中,Nginx 可以处理高并发压力下的并发请求的原因如下:

除了基于事件驱动的架构使其支持百万级的 TCP 连接,另外高度模块化的设计自由的许可证使其拥有非常多扩展其功能的第三方模块,也是它的重要特性。所以,后来才会有 OpenResty 的诞生。

我们看一个 Nginx 作简单配置来提供服务的例子:

worker_processes 1;events { worker_connections 1024; } http { upstream backend { server 127.0.0.1:8080 } server { location / back { proxy_pass http://backend; } } }

 

上述配置文件中,分别在 event、http、server 以及 location 块配置项中做了一些简单的配置,当安装完并启动 Nginx 后(监听 80 端口),访问到 /back 路径下的请求会被转发到本地 127.0.0.1:8080 服务上。

3.1.1 OpenResty 简介

根据官网定义,OpenResty 是一个通过 Lua 扩展 Nginx 实现的可伸缩的 Web 平台。其核心是基于 Nginx 一个 C 模块将 Lua 语言嵌入到 Nginx 服务器中,对外提供一套完整的 Lua Web 的 API,并透明支持非阻塞 I/O,提供协程 —— “轻量级线程”、定时器等,从而极大地降低了高性能服务端的开发难度和开发周期

OpenResty 将两个极为优秀的组件 Nginx 与 Lua 进行糅合,一方面保留了 Nginx 高性能 web 服务特征,另一方面有提供 Lua 特性在极少损失性能情况下便于业务功能的开发。根据官网介绍,OpenResty 非常便于用来搭建能够处理超高并发、扩展性极高的动态 Web 应用Web 服务动态网关

我们也是因为 OpenResty 的这些特性,特别是它对搭建动态网关的友好支持,才选择了基于 OpenResty 来开发我们的接口网关 —— APIGateway 与 OpenApi。

开发接口网关使用到的 OpenResty 一个重要知识:OpenResty 对于一个请求的处理流程。Nginx 把一个请求分为不同的阶段,从而让第三方模块通过挂载行为在不同的阶段来定制自己的行为;OpenResty 拥有同样的特性,不过在不同阶段挂载的是 Lua 脚本。下图是基于《OpenResty 最佳实践》原图重绘而来:

基于 OpenResty 的接口网关设计

 

 

从上图可知,OpenResty 处理请求大致分为四个阶段:

我们看一个 OpenResty 作简单配置来提供服务的例子:

worker_processes 1;events { worker_connections 1024;}http { resolver 127.0.0.1; lua_package_path '$prefix/lua/?.lua;;'; init_by_lua_block { # ... } init_worker_by_lua_file lua/init_work_by_lua.lua; server { listen 80; location / { rewrite_by_lua_file lua/rewrite_by_lua.lua; access_by_lua_file lua/access_by_lua.lua; proxy_pass http://<url>; } }}

 

上述配置文件中,分别在 event、http、server 以及 location 块配置项中做了一些简单的配置,当安装完并启动 Nginx 后(监听 80 端口),首先执行 init_by_lua_block、init_worker_by_lua_file 进行初始化,接着接受请求,所有的请求都会匹配上 "/" 路径,进而执行 rewrite_by_lua_file、access_by_lua_file进行重写与访问,最后转发请求到本地 127.0.0.1 服务上。

在实际的接口网关开发中,我们主要是使用到了 OpenResty 中初始化阶段的 init_by_lua*、init_worker_by_lua*、重写与访问阶段 的 rewrite_by_lua*、access_by_lua* 以及内容生成阶段 content_by_lua* 过程。

3.2 接口网关的架构

这一节是本文的核心内容,重点讲述接口网关的架构设计。如前文所述,本文主要以 OpenApi 为例来讲述接口网关的架构设计。先看图:

基于 OpenResty 的接口网关设计

 

 

下面我们来一步步来分析架构图的各个部分,首先是两层的 HAProxy 。

3.2.1 两层 HAProxy 代理

根据维基百科定义,HAProxy 是一个使用 C 语言编写的自由及开放源代码软件,其提供高可用性负载均衡,以及基于 TCP 和 HTTP 的应用程序代理

基于 OpenResty 的接口网关设计

 

 

如图所示,隔离的内网与外网上分别提供了 HAProxy 代理, 外层暂且称为 HAProxy internet ,内层称为 HAProxy internal。外层暴露于外网中,使用统一地址如 http://openapi.company.com 来接受外部请求(这里指第三方的请求);中间是基于 OpenResty 的 Nginx 网关层,外部请求经过网关后通过 HAProxy internal 转发到内网的服务上,内网服务遵循 Restful 风格,网关转发到内网的地址由接口网关控制。

然而,目前的代理架构受到了当前整体架构的约束,实际上两层的 HAProxy 代理并不是必需的

在我们当前的系统量级下,这两层 HAProxy 转发消耗非常小可以被接受,所以调整架构的优先级还不高,以后再慢慢演进。

3.2.2 接口网关

接下来这一节是最为重点的接口网关的设计。接口网关主要利用前文所述的 OpenResty 执行阶段请求与响应进行流程处理,包括接口地址的重写IP 与资源白名单的控制请求的解密与验签请求的路由以及响应的签名与加密等。

这里分成主流程配置服务安全服务三部分进行讲述。

3.2.2.1 主流程设计

主流程是网关的核心,是请求处理的控制中心;它是通过 OpenResty 的 Lua 脚本处理流程来实现对请求的处理。

基于 OpenResty 的接口网关设计

 

 

A. 主流程

  1. 在 OpenResty 服务启动之后,首先通过 init_by_lua_block 阶段初始化常量(包括调用配置服务以及安全服务所需的主机地址、端口、URL 地址等)、引入依赖(包括常用的 http 以及 cjson 依赖等)等作为全局使用;
  2. 接着通过 init_worker_by_lua_file 阶段设置定时任务调用内网配置服务来缓存配置,为处理第三方的请求做准备,其中加载的配置可供 URL 重写(即接口映射)、IP 以及资源(URI)白名单限制、请求的解密验签以及响应的签名加密使用,详情查看配置服务一节。
  3. 当第三方请求通过 HAProxy Internet 进入到网关后,根据配置通过 rewrite_by_lua_file 阶段做 URL 重写(即接口映射)。
--openapi --lua --access_by_lua.lua --cache_management.lua --content_by_lua.lua --init_work_by_lua.lua --rewrite_by_lua.lua --security.lua --prod --Dockerfile --nginx.conf --sit --Dockerfile --nginx.conf --README.md
# Nginx worker 进程个数,直接影响性能。# 如果确认不会出现阻塞式调用,那么有多少 CPU 内核设置多少个进程# 如果有可能出现阻塞式调用,需要配置多一些进程worker_processes 1; events { worker_connections 1024;}http { # 内网地址 resolver xxx.x.x.xxx yyy.y.y.yyy; # 日志格式配置 log_format graylog2_format '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '<msec=$msec|connection=$connection|connection_requests=$connection_requests|millis=$request_time>'; # 日志路径配置 access_log syslog:server=<host>:<port> graylog2_format; error_log syslog:server=<host>:<port> warn; # 配置 Lua 包地址 lua_package_path '$prefix/lua/?.lua;;'; init_by_lua_block { # 引入依赖(可能会污染全局环境,待研究) http = require "resty.http" cjson = require "cjson" cache_management = require "cache_management" ... } # 设置定时任务缓存配置,及上面的 cache_management 模块 init_worker_by_lua_file lua/init_work_by_lua.lua; # Nginx Web 服务配置 server { listen 80; # ngx.location.capture 子请求代理,转发原请求到接口服务 location = /ngx_proxy/ { internal; proxy_set_header Accept-Encoding ''; proxy_pass http://$context$http_host_suffix$proxy_uri; } # 匹配所有请求,进行 URL 重写、访问控制、转发请求以及响应处理(各阶段的处理在此配置)。 location / { set $context ''; ... rewrite_by_lua_file lua/rewrite_by_lua.lua; access_by_lua_file lua/access_by_lua.lua; content_by_lua_file lua/content_by_lua.lua; } }}
  1. 初始化常量和依赖等
  2. 通过 CampA 与 user Context 获取第三方配置
  3. HAProxy Internet 接收请求发到 OpenApi 接口网关,OpenApi 把 /user/users/27/info URI 重写为 /user/users/27/user-info/
  4. 校验第三方请求 IP,在 IP 白名单中,校验通过;校验 URI /user/users/27/user-info 在授权的 URI 中,校验通过
  5. 调用安全服务对请求进行解密与验签,解密成功,验签通过,获取明文
  6. 将拥有明文的请求转发到开放接口服务
  7. 获取响应,调用安全服务对响应报文进行签名与加密,返回给第三方 CampA。
基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

{ # 接口映射配置 "apiMapping":{ "$context":{ "$fromApi":"$toApi" } }, # 接口白名单配置、加解密配置 "apiConfig":{ "$channel $context":{ "$httpMethod $uri":{ "reqNeedDecrypt":false, "respNeedEncrypt":false } } }, # IP 白名单配置,验签名配置 "channelConfig":{ "$channel":{ ips:{ "$ip":1 }, "reqNeedVerifySign":false, "respNeedSign":false, "needCheckIp":false } }}
基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

基于 OpenResty 的接口网关设计

 

  1. 两层 HAProxy 代理:在使用更优产品替代 Openshift 架构的情况下,直接部署接口网关到公网,可消除外层 HAProxy 代理;在实现服务治理的情况下,由接口网关直接转发请求到服务,可消除内层 HAProxy 代理。
  2. 安全服务性能:加解密验签名等安全服务是以内部服务的方式提供给接口网关,而且使用了性能不太好的 ngx.location.capture 转发原请求,在系统量级增大后会遇到性能瓶颈,可通过使用高性能的 Lua 脚本在接口网关层提供安全服务,从而提升安全服务性能。
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>