Fork me on GitHub

orange网关原理和源码分析

背景

Orange 属于流量网关(Api Geteway),项目托管在Githubhttps://github.com/orlabs/orange)上,目前活跃度较弱(2年未更新)。通常将`orange`和另一个流行的网关项目`Kong`进行比较,其实`orange`大部分组件都是参考`Kong`实现的,但是活跃度远不及`Kong`。

orangeKong本质上都是OpenResty 应用,Lua语言编写,Lua在葡萄牙语里代表美丽的月亮。OpenrestyLua语言的关系类似JVM虚拟机和Java语言的关系。OpenResty可以理解为一个集成了很多模块的定制版nginx

本文以orange 0.7.0版本介绍项目的整体架构和原理,最后通过案例介绍如何研发实现一个orange插件。

第一部分 知识准备

1.1 Openresty介绍

Nginx组件已经被广泛应用在web应用架构中,例如:负载均衡、反向代理、代理缓存以及流量限流等场景。Nginx开发则需要C/C++语言,还需要了解底层接口,学习门槛较高。2009年,淘宝的章亦春和王晓哲一起设计了第二代的 OpenResty。在王晓哲的提议下,选择了小而美的脚本语言 lua 进行开发。这就是ngx_lua 模块,并且将Nginx核心、LuaJITngx_lua模块、许多有用的Lua库和常用的第三方Nginx模块组合在一起成为OpenResty

Lua脚本语言,不需要编译就可以执行,更加灵活。而且 OpenResty 还把 Lua 自身的协程与 Nginx 的事件机制完美结合在一起,优雅地实现了许多其他语言所没有的同步非阻塞编程范式,能够轻松开发出高性能的 Web 应用。

我们知道Nginx的配置文件是文件存储,修改后需要reload。而Lua 有代码热加载特性,不需要重启进程,就能够从磁盘、Redis 或者任何其他地方加载数据,随时替换内存里的代码片段。这就带来了“动态配置”,让 OpenResty 能够永不停机,在微秒、毫秒级别实现配置和业务逻辑的实时更新,比起 Nginx 秒级的重启是一个极大的进步。

OpenResty 还选用了LuaJIT作为 Lua 语言的运行时(Runtime),提升效率。LuaJIT 是一个高效的 Lua 虚拟机,支持 JIT(Just In Time)技术,可以把 Lua 代码即时编译成“本地机器码”,这样就消除了脚本语言解释运行的劣势,让Lua 脚本跑得和原生 C 代码一样快。

1.2 Openresty阶段式处理

Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log

基于Nginx底层的阶段处理,OpenResty 也使用“流水线”来处理 HTTP 请求。Nginx 的处理流水线是由一个个 C 模块组成的,只能在静态文件里配置,开发困难,配置麻烦(相对而言)。而 OpenResty 的处理流水线则是由一个个的 Lua 脚本组成的,不仅可以从磁盘上加载,也可以从RedisMySQL 里加载,而且编写、调试的过程非常方便快捷。

Nginx 把一个请求分成了很多阶段,这样第三方模块就可以根据自己行为,挂载到不同阶段进行处理达到目的。OpenResty 也应用了同样的特性。所不同的是,OpenResty 挂载的是我们编写的 Lua 代码。OpenResty 的阶段,比起 Nginx,OpenResty 的阶段更注重对 HTTP 请求响应报文的加工和处理。

img

​ OpenResty 里有几个阶段与 Nginx 是相同的,比如 rewrite、access、content、filter,这些都是标准的 HTTP 处理。

​ 在这几个阶段里可以用“xxx_by_lua”指令嵌入 Lua 代码,执行重定向跳转、访问控制、产生响应、负载均衡、过滤报文等功能。因为 Lua 的脚本语言特性,不用考虑内存分配、资源回收释放等底层的细节问题,可以专注于编写非常复杂的业务逻辑,比 C 模块的开发效率高很多,即易于扩展又易于维护。

第二部分

版本是一个重构版本, 着重为了解决之前版本在有大量规则配置时性能损耗的问题。 基本的设计思路是将原来的规则细分成两层, 第一层叫做selector, 用于将流量进行第一步划分, 在进入某个selector后才按照之前的设计进行规则匹配, 匹配到后进行相关处理。

https://lengrongfu.github.io/2019/05/21/orange-%E5%8E%9F%E7%90%86/

https://book.aikaiyuan.com/openresty/understanding-orange.html

https://zhuanlan.zhihu.com/p/67481992

网关优化项目

https://github.com/starjiang/xorange

orange运行后根据nginx.conf文件中location配置来进行顺序匹配流量。

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
location / {
set $upstream_host $host;
set $upstream_request_uri '';
set $upstream_url '';
set $upstream_scheme '';
set $target '';

rewrite_by_lua_block {
local orange = context.orange
orange.redirect()
orange.rewrite()
}

access_by_lua_block {
local orange = context.orange
orange.access()
}

# proxy
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_pass $upstream_scheme$upstream_url$upstream_request_uri;


header_filter_by_lua_block {
local orange = context.orange
orange.header_filter()
}

body_filter_by_lua_block {
local orange = context.orange
orange.body_filter()
}

log_by_lua_block {
local orange = context.orange
orange.log()
}
}

arch

第一部分 orange 中设计概念

Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。按照它们执行时的先后顺序,依次是 rewrite 阶段、access 阶段以及 content 阶段(后面我们还有机会见到其他更多的处理阶段)。

orange 缓存

nginx.conf文件中:

1
2
3
4
5
6
7
8
9
10
11
lua_code_cache on;

lua_shared_dict orange_data 20m; # should not removed. used for orange data, e.g. plugins configurations..

lua_shared_dict status 1m; # used for global statistic, see plugin: stat
lua_shared_dict waf_status 1m; # used for waf statistic, see plugin: waf
lua_shared_dict monitor 10m; # used for url monitor statistic, see plugin: monitor
lua_shared_dict rate_limit 10m; # used for rate limiting count, see plugin: rate_limiting
lua_shared_dict property_rate_limiting 10m; # used for rate limiting count, see plugin: rate_limiting
lua_shared_dict consul_upstream 5m; # used for consul_upstream, see plugin consul_balancer
lua_shared_dict consul_upstream_watch 5m; # used for consul_upstream_watch, consul_balancer

这些配置是插件缓存数据。

orange中数据持久化

orange中插件基本构成

选择器(selector)

规则(rule)

第二部分 orange的启动过程

2.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
init_by_lua_block {
local orange = require("orange.orange")
local env_orange_conf = os.getenv("ORANGE_CONF")
print(string.char(27) .. "[34m" .. "[INFO]" .. string.char(27).. "[0m", [[the env[ORANGE_CONF] is ]], env_orange_conf)

local config_file = env_orange_conf or ngx.config.prefix().. "/conf/orange.conf"
local config, store = orange.init({
config = config_file
})

-- the orange context
context = {
orange = orange,
store = store,
config = config
}
}

第三部分 orange中的插件

第四部分 如何开发一个orange插件

通常一个插件的代码分为两个部分:后端和前端。

需求分析

业务逻辑介绍

插件对过滤后的目标流量访问的接口进行验签认证(SignatureAuth)。

  • 插件支持多用户,在实际线上环境,对于一个借口,通常是多用户需要访问该接口。就需要我们针对不同用户分配密钥(accessKey);
  • 插件支持用户将认证字段放在header还是query中;
  • 插件支持用户自由添加字段;

客户端包装认证参数

  • 传入参数:accessKey,accessSecret
  • 生成参数
    • paramStr:method=akauth&client=$UUID&rand=Math.rand()
    • timeStr:String.valueOf(System.currentTimeMillis()/1000)
    • sginStr:accessKey+timeStr+paramStr
  • 签名计算:signature=base64(HmacSHA1.init(accessSecret).doFinal(signStr))
  • 传递参数:accessKey,timeStr,paramStr,signature

参数放在header中。新增三个字段,分别是:timestamp、sign、appName

其中timestamp 作为判断密钥的有效期。

服务端转发认证参数

  • 接收参数:accessKey,timeStr,paramStr,signature
  • 请求 认证服务接口

后端

后端代码位于:orange/pligins中,在源码中创建新增插件名称命名的子目录,例如:signature_auth_header

每个插件的子目录中约定必须有两个文件(api.luahandler.lua):

1
2
3
4
signature_auth_header/
api.lua
handler.lua
README.md

其中

下面的函数可以从流量请求中提取header表。

1
2
local headers = ngx.req.get_headers()
real = headers[condition.name]

前端

插件前后端使用经典MVC模式。前端代码文件在dashboad中,目录结构如下:

1
2
3
routes # 路由
static # js及静态资源路径
views # 前端展示

views中主页中增加插件:

1
dashboard\views\common\left_nav.html

代码:

1
2
3
4
5
6
 <li id="nav-signature-auth-header">
<a href="/signature_auth_header">
<i class="fa fa-minus-circle"></i>
<span class="nav-label">Signature Auth In Header</span>
</a>
</li>

最后提交git的清单如下:

1
2


git

参考文献及资料

[1] Orange官网,链接:http://orange.sumory.com/

[2] Orange网关官网docker,链接:https://hub.docker.com/r/syhily/orange

[3] agentzhNginx 教程(版本 2020.03.19),链接:http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

[4] OpenResty最佳实践,链接:https://moonbingbing.gitbooks.io/openresty-best-practices/content/

0%