github地址: https://github.com/orlabs/orange

官网: http://orange.sumory.com/

简介

Orange是一个基于OpenResty的API Gateway,提供API及自定义规则的监控和管理,如访问统计、流量切分、API重定向等功能。

特点:

  • 提供了一套默认的Dashboard用于动态管理各种功能和配置

  • 提供了API接口用于实现第三方服务

  • 根据规范编写自定义插件扩展Orange功能

安装及使用(最新可用版0.8.1)

安装

先安装lor: https://github.com/orlabs/orange/blob/master/README_zh.md#2-%E5%AE%89%E8%A3%85-lor-framework

再进行依赖安装: https://github.com/orlabs/orange/blob/v0.8.1/docs/install-dependencies.md

最后,查看orange的安装和使用: https://github.com/orlabs/orange/blob/v0.8.1/README_zh.md

坑点

  1. 需要dev环境执行的话, 执行sudo ./bin/orange start可能会报错no such file..., 换成sudo resty ./bin/orange start就好了

  2. 修改orange-master-1.0-0.rockspec文件中的lua-resty-kafka属性为0.09-0, 0.06下载有问题

Dashboard 使用

通过Dashboard 管理内置插件

详见:http://orange.sumory.com/docs/concept/

选择器

它可能包括多个规则, 选择器内置了一个条件筛选模块(这个模块的概念和使用方法等同于规则里的条件判断模块),可以将流量进行初步划分后进入到规则。

选择器的目的是减少不必要的规则判断,进一步提高性能。

规则

规则rule是Orange中的一个概念,它一般用来筛选出部分请求并对其做各种处理。

条件判断模块

条件判断模块,是一系列匹配条件和使用方式的集合。它由以下几部分组成

  • 条件判断模块有四种类型:

    • 单一条件匹配: 只支持一个条件
  • and匹配: 对所有条件取与操作

  • or匹配: 对所有条件取或操作

  • 复杂匹配: 根据另行配置的表达式判断是否匹配

其中, 每个条件都支持8种数据来源的8种操作符运算, 可自由搭配。

  • expression
  • 当type为复杂匹配时,此字段不能为空,它的格式是一个lua的逻辑判断表达式。

  • 表达式中每个值的格式为v[index], 比如v[1]对应的就是第一个条件的值。

例:

1
(v[1] or v[2]) and v[3],即前两个条件至少一个为真并且第三个条件为真时,规则为真
  • conditions: 匹配条件集合
1
2
3
4
5
6
{
"type": "", // 即要匹配的数据来源 例如URI header IP HOST等
"operator": "", // 匹配符
"name":"", // 可能为空 例如Header时不能为空,type为KEY VALUE类型
"value": "" // 使用operator做匹配
}

变量提取模块

包含两种类型

  1. 索引式提取

索引式提取的含义是将提取出的所有变量按照顺序保存在一个数组中, 后续以下标的方式来使用。

比如我们在”变量提取模块”提取了三个值:

  • 那么之后就可以通过${1}、${2}、${3}来使用, 其中
  • ${1}指的是header头里的app_version字段, 如果header没有此字段, 则赋一个默认值v0.1
  • ${2}指的是query中的uid字段
  • ${3}指的是query中age字段, 若无则给予默认值18
  1. 模板式提取

模板时提取主要为了解决索引式提取必须要按序使用的问题, 并且当需要从uri中提取多个值时索引式提取方式并不友好。

例:我们需要提取四个值:

  • 指的是从query中提取出的uid字段
  • 指的是从query中提取出的age字段, 若无则给予默认值18
  • 指的是从格式为`^/start/(.*)/(.*)/end`的URI中提取出的第1个分段值

比如, 如果URI为/start/abc/123/end, 则此时值为abc

后续处理模块

它包含了一些后续处理需要的配置、参数等信息,比如是否需要记录日志、响应状态码、要跳转到的URL、要代理的下游upstream server等等。详细信息需要参看各个插件的配置。

这条规则对应的后续处理,如果这条规则有一个下游触发动作,此值不为空。不同的插件这个字段所包含的属性可能不同。

API 开发

开放API: 所有插件均开放API供第三方调用, 通过这些API可简单灵活得配置插件、查看运行状态、统计数据等。

详细:http://orange.sumory.com/docs/api/

说明

  • API Server默认在7777端口监听
  • 若无特别说明,API均以HTTP协议给出,返回值为json格式,数据格式如下:
1
2
3
4
5
6
7
8
9
10
11
//失败情况
{
success: false,
msg: "错误描述"
}
//成功情况
{
success: true,
msg: "描述信息",
data: {} //相关数据
}

插件基本信息API

请求方式

URIMethod
/pluginsGet

参数

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"success": true, //API请求是否成功
"data": {
"plugins": {
"monitor": {//插件名称
"enable": true, //是否开启
"active_selector_count": 1, //开启的选择器数目
"inactive_selector_count": 0, //关闭的选择器数目
"active_rule_count": 1, //开启的规则数目
"inactive_rule_count": 0, //开启的规则数目
"name": "monitor" // 插件名称
},
......
}
}
}

运行启动

通过Makefile文件,可以自动配置不同环境的各个服务的 配置文件模板、证书、日志文件

1
make dev && make install && resty bin/orange start

自定义插件(例如插件名为xxx)

orange的插件设计参考了kong

编写管理界面

  • 按现有插件(如monitor)修改dashboard/routes/dashboard.lua
1
2
3
dashboard_router:get("/xxx", function(req, res, next)
res:render("xxx")
end)
  • 按现有插件(如monitor)修改dashboard/views/common/left_nav.html
1
2
3
4
5
6
7
8
9
<li class="hidden-folded padder m-t m-b-sm text-muted text-xs">
<span class="ng-scope">定制</span>
</li>
<li id="nav-xxx">
<a href="/xxx">
<i class="fa fa fa-check-square"></i>
<span class="nav-label">xxx</span>
</a>
</li>
  • 按现有插件(如monitor)添加dashboard/views/xxx.html

  • 按现有插件(如monitor)添加dashboard/static/js/xxx.js

编写配置文件

  1. conf/orange.conf.tpl:用于配置插件、存储方式mysql和内部集成的默认Dashboard

配置plugins,添加插件名称xxx

  1. conf/nginx.conf.tpl:可配置插件共享内存 lua_shared_dict

添加lua_shared_dict xxx 10m; # used for xxx, see plugin: xxx

编写插件Handler

  • 创建文件夹orange/plugins/xxx
  • 按现有插件(如monitor)编写orange/plugins/xxx/api.lua

api.lua eg:

1
2
3
4
5
local BaseAPI = require("orange.plugins.base_api")
local common_api = require("orange.plugins.common_api")
local api = BaseAPI:new("xxx-api", 2)
api:merge_apis(common_api("xxx"))
return api
  • 按现有插件(如monitor)编写orange/plugins/xxx/handle.lua

handler.lua eg:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
local BasePlugin = require("orange.plugins.base_handler")
local json = require("orange.utils.json")
local orange_db = require("orange.store.orange_db")
local judge_util = require("orange.utils.judge")

local function filter_rules(sid, plugin, ngx_var_uri)
local rules = orange_db.get_json(plugin .. ".selector." .. sid .. ".rules")
if not rules or type(rules) ~= "table" or #rules <= 0 then
return false
end
for i, rule in ipairs(rules) do
if rule.enable == true then
-- judge阶段
local pass = judge_util.judge_rule(rule, plugin)
-- handle阶段
if pass then
-- log
local handle = rule.handle
if handle and handle.log == true then
ngx.log(ngx.ERR, "[XXX] start handling: ", rule.id, ":", ngx_var_uri)
end
if handle.continue == true then
-- 处理逻辑
else
-- 处理逻辑
return true
end
end
end
end
return false
end

local XXXHandler = BasePlugin:extend()
XXXHandler.PRIORITY = 4998

function XXXHandler:new(store)
XXXHandler.super.new(self, 'XXXHandler-plugin')
self.store = store
end

function XXXHandler:access(conf)
XXXHandler.super.access(self)
local enable = orange_db.get("xxx.enable")
local meta = orange_db.get_json("xxx.meta")
local selectors = orange_db.get_json("xxx.selectors")
local ordered_selectors = meta and meta.selectors
if not enable or enable ~= true or not meta or not ordered_selectors or not selectors then
return
end
local ngx_var_uri = ngx.var.uri
for i, sid in ipairs(ordered_selectors) do
ngx.log(ngx.ERR, "==[XXX][PASS THROUGH SELECTOR:", sid, "]")
local selector = selectors[sid]
if selector and selector.enable == true then
local selector_pass
if selector.type == 0 then -- 全流量选择器
selector_pass = true
else
selector_pass = judge_util.judge_selector(selector, "xxx")-- selector judge
end
if selector_pass then
if selector.handle and selector.handle.log == true then
ngx.log(ngx.ERR, "[XXX][PASS-SELECTOR:", sid, "] ", ngx_var_uri)
end
local doFilter = filter_rules(sid, "xxx", ngx_var_uri)
-- true则拦截,false则继续
if doFilter == true then
-- 不再执行此插件其他逻辑
-- 必须有print内容,否则只有exit不生效.
local res = json.encode({
code = 90001,
msg = "xxx - can not access!",
status = "fail"
})
ngx.print(res)
ngx.exit(ngx.OK)
return
end
else
if selector.handle and selector.handle.log == true then
ngx.log(ngx.ERR, "[XXX][NOT-PASS-SELECTOR:", sid, "] ", ngx_var_uri)
end
end
end
end
end
return XXXHandler

Openresty 处理一个请求的流程

Orange请求处理逻辑顺序

Orange 嵌入在 Openresty的个阶段