From 8a2736e29a45b72f8584ad0c9cad1134df49154d Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 3 Dec 2020 00:50:03 +0800 Subject: [PATCH 01/30] feat: traffic split plugin. close #2303 close #2603 --- apisix/plugins/dynamic-upstream.lua | 308 +++++++++++++++++++++++++ conf/config-default.yaml | 1 + doc/plugins/dynamic-upstream.md | 20 ++ doc/zh-cn/plugins/dynamic-upstream.md | 311 ++++++++++++++++++++++++++ t/plugin/dynamic-upstream.t | 213 ++++++++++++++++++ 5 files changed, 853 insertions(+) create mode 100644 apisix/plugins/dynamic-upstream.lua create mode 100644 doc/plugins/dynamic-upstream.md create mode 100644 doc/zh-cn/plugins/dynamic-upstream.md create mode 100644 t/plugin/dynamic-upstream.t diff --git a/apisix/plugins/dynamic-upstream.lua b/apisix/plugins/dynamic-upstream.lua new file mode 100644 index 000000000000..16058f626cf7 --- /dev/null +++ b/apisix/plugins/dynamic-upstream.lua @@ -0,0 +1,308 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local upstream = require("apisix.upstream") +local roundrobin = require("resty.roundrobin") +local ipmatcher = require("resty.ipmatcher") +local expr = require("resty.expr.v1") +local table_insert = table.insert + +local lrucache_rr_obj = core.lrucache.new({ + ttl = 0, count = 512, +}) + + +local match_def = { + type = "array", + items = { + type = "object", + properties = { + vars = { + type = "array", + items = { + type = "array", + items = { + { + type = "string" + }, + { + type = "string" + } + }, + additionalItems = { + anyOf = { + { + type = "string" + }, + { + type = "number" + }, + { + type = "boolean" + } + } + }, + minItems = 0, + maxItems = 10 + } + } + } + }, + -- When there is no `match` rule, the default rule passes. + -- Perform upstream logic of plugin configuration. + default = {{ vars = {{"server_port", ">", 0}}}} +} + +local upstream_def = { + type = "object", + additionalProperties = false, + properties = { + name = { type = "string" }, + type = { + type = "string", + enum = { + "roundrobin", + "chash" + }, + default = "roundrobin" + }, + nodes = { type = "object" }, + timeout = { type = "object" }, + enable_websocket = { type = "boolean" }, + pass_host = { + type = "string", + enum = { + "pass", "node", "rewrite" + } + }, + upstream_host = { type = "string" } + }, + dependencies = { + pass_host = { + anyOf = { + { + properties = { + pass_host = { enum = { "rewrite" }} + }, + required = { "upstream_host" } + }, + { + properties = { + pass_host = { enum = { "pass", "node" }} + }, + } + } + } + } +} + +local upstreams_def = { + type = "array", + items = { + type = "object", + properties = { + upstream_id = { type = "string" }, + upstream = upstream_def, + weight = { + type = "integer", + default = 1, + minimum = 0 + } + } + }, + minItems = 1, + maxItems = 20 +} + +local schema = { + type = "object", + properties = { + rules = { + type = "array", + items = { + type = "object", + properties = { + match = match_def, + upstreams = upstreams_def + } + } + } + } +} + +local plugin_name = "dynamic-upstream" + +local _M = { + version = 0.1, + priority = 2523, -- TODO: add a type field, may be a good idea + name = plugin_name, + schema = schema +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + + if not ok then + return false, err + end + + return true +end + + +local function parse_domain(host) + local ip_info, err = core.utils.dns_parse(host) + if not ip_info then + core.log.error("failed to parse domain: ", host, ", error: ",err) + return nil, err + end + + core.log.info("parse addr: ", core.json.delay_encode(ip_info)) + core.log.info("host: ", host) + if not ip_info.address then + return nil, "failed to parse domain" + end + + core.log.info("dns resolver domain: ", host, " to ", ip_info.address) + return ip_info.address +end + + +local function parse_domain_for_node(node) + if not ipmatcher.parse_ipv4(node) and not ipmatcher.parse_ipv6(node) then + local ip, err = parse_domain(node) + if ip then + return ip + end + + if err then + return nil, err + end + end + + return node +end + + +local function set_upstream_host(upstream_info, ctx) + local nodes = upstream_info["nodes"] + local new_nodes = {} + for addr, weight in pairs(nodes) do + local node = {} + local ip, port, host + host, port = core.utils.parse_addr(addr) + ip = parse_domain_for_node(host) + node.host = ip + node.port = port + node.weight = weight + table_insert(new_nodes, node) + core.log.info("node: ", core.json.delay_encode(node)) + + if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) then + if upstream_info["pass_host"] == "pass" then -- TODO: support rewrite method + ctx.var.upstream_host = ctx.var.host + + elseif upstream_info["pass_host"] == "node" then + ctx.var.upstream_host = host + end + + core.log.info("upstream_host: ", ctx.var.upstream_host) + break + end + end + + core.log.info("new_node: ", core.json.delay_encode(new_nodes)) + + local up_conf = { + name = upstream_info["name"], + type = upstream_info["type"], + nodes = new_nodes + } + + local ok, err = upstream.check_schema(up_conf) + if not ok then + return 500, err + end + + local matched_route = ctx.matched_route + upstream.set(ctx, up_conf.type .. "#route_" .. matched_route.value.id, + ctx.conf_version, up_conf, matched_route) + return +end + + +local function new_rr_obj(upstreams) + local server_list = {} + for _, upstream_obj in ipairs(upstreams) do + if not upstream_obj.upstream then + -- If the `upstream` object has only the `weight` value, it means that + -- the `upstream` weight value on the default `route` has been reached. + -- Need to set an identifier to mark the empty upstream. + upstream_obj.upstream = "empty_upstream" + end + server_list[upstream_obj.upstream] = upstream_obj.weight + end + + return roundrobin:new(server_list) +end + + +function _M.access(conf, ctx) + local upstreams, match_flag + for _, rule in pairs(conf.rules) do + match_flag = true + for _, single_match in pairs(rule.match) do + local expr, err = expr.new(single_match.vars) + if err then + return 500, {"message: expr failed: ", err} + end + + match_flag = expr:eval() + if match_flag then + break + end + end + + if match_flag then + upstreams = rule.upstreams + break + end + end + + core.log.info("match_flag: ", match_flag) + + if not match_flag then + return + end + + local rr_up, err = lrucache_rr_obj(upstreams, nil, new_rr_obj, upstreams) + if not rr_up then + core.log.error("lrucache_rr_obj faild: ", err) + return 500 + end + + local upstream = rr_up:find() + if upstream and upstream ~= "empty_upstream" then + core.log.info("upstream: ", core.json.encode(upstream)) + return set_upstream_host(upstream, ctx) + end + + return +end + + +return _M diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 91c08a3e0d3a..994e2b3e568b 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -240,6 +240,7 @@ plugins: # plugin list (sorted in alphabetical order) - wolf-rbac - zipkin #- server-info + - dynamic-upstream stream_plugins: - mqtt-proxy diff --git a/doc/plugins/dynamic-upstream.md b/doc/plugins/dynamic-upstream.md new file mode 100644 index 000000000000..cbe3758374b1 --- /dev/null +++ b/doc/plugins/dynamic-upstream.md @@ -0,0 +1,20 @@ + + +- [中文](../zh-cn/plugins/dynamic-upstream.md) diff --git a/doc/zh-cn/plugins/dynamic-upstream.md b/doc/zh-cn/plugins/dynamic-upstream.md new file mode 100644 index 000000000000..f7dc55dfd5b8 --- /dev/null +++ b/doc/zh-cn/plugins/dynamic-upstream.md @@ -0,0 +1,311 @@ + + +- [English](../../plugins/dynamic-upstream.md) + +# 目录 + +- [名字](#名字) +- [属性](#属性) +- [如何启用](#如何启用) + - [灰度发布](#灰度发布) + - [蓝绿发布](#蓝绿发布) + - [自定义发布](#自定义发布) +- [测试插件](#测试插件) + - [灰度测试](#灰度测试) + - [蓝绿测试](#蓝绿测试) + - [自定义测试](#自定义测试) +- [禁用插件](#禁用插件) + + +## 名字 + +流量分割插件,对请求流量按指定的比例划分,并将其分流到对应的 upstream。通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 + +## 属性 + +| 参数名 | 类型 | 可选项 | 默认值 | 有效值 | 描述 | +| ------------ | ------------- | ------ | ------ | ------ | -------------------- | +| rules | array[object] | 必需 | | | 插件的配置列表 | +| rules.match | array[object] | 可选 | | | 匹配规则列表 | +| rules.match.vars | array | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看下文的 `运算符列表` 部分。 | +| rules.upstreams | array[object] | 可选 | | | 上游配置规则列表。 | +| rules.upstreams.upstream_id | string | 可选 | | | 通过上游 id 绑定对应上游。 | +| rules.upstreams.upstream | object | 可选 | | | 上游配置信息。 | +| rules.upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的。 | +| rules.upstreams.upstream.nodes | object | 可选 | | | 哈希表,内部元素的 key 是上游机器地址 列表,格式为地址 + Port,其中地址部 分可以是 IP 也可以是域名,⽐如 192.168.1.100:80、foo.com:80等。 value 则是节点的权重,特别的,当权重 值为 0 有特殊含义,通常代表该上游节点 失效,永远不希望被选中。 | +| rules.upstreams.upstream.timeout | object | 可选 | | | 设置连接、发送消息、接收消息的超时时 间 | +| rules.upstreams.upstream.enable_websocket | boolean | 可选 | | | 是否启用 websocket,默认不启用。 | +| rules.upstreams.upstream.pass_host | enum | 可选 | | ["pass", "node", "rewrite"] | pass 透传客户端请求的 host, node 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite 使用 upstream_host 配置的值重写 host 。 | +| rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | +| rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | +| rules.upstreams.weight | integer | 可选 | weight = 1 | | 根据 weight 值来做流量切分。默认使用 roundrobin 算法,且目前只支持 roundrobin 算法。| + +### 运算符列表 + +| 运算符 | 描述 | 示例 | +|-------|------------|--------------------------------------------------------------| +| == | 相等 | {"arg_name", "==", "json"} | +| ~= | 不等 | {"arg_name", "~=", "json"} | +| ~~ | 正则匹配 | {"arg_name", "~~", "[a-z]+"} | +| ~* | 不区分大小写的正则匹配 | {"arg_name", "~*", "[a-z]+"} | +| < | 小于 | {"arg_age", "<", 24} | +| > | 大于 | {"arg_age", ">", 24} | +| in | 在数组中查找 | {"arg_name", "in",{"1","2"}} | +| has | 在左边的数组值中有右边的值 | {"graphql_root_fields", "has", "repo"} | +| ! | 结果值取反 | {"arg_name", "!", "~~", "[a-z]+"} | + +## 如何启用 + +### 灰度发布 + +根据插件中 upstreams 配置的 weight 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 + +```json +{ + "weight": 2 +} +``` + +在插件 upstreams 中只有 `weight` 值,表示到达 route 上的 upstream 流量权重值。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "dynamic-upstream": { + "rules": [ + { + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +### 蓝绿发布 + +通过请求头获取蓝绿条件(也可以通过请求参数获取或NGINX变量),在 `match` 规则匹配通过后,表示所有请求都命中到插件配置的 upstream ,否则所以请求只命中 `route` 上配置的 upstream 。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "dynamic-upstream": { + "rules": [ + { + "match": [ + { + "vars": [ + [ "http_new-release","==","blue" ] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + } + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +### 自定义发布 + +`match` 中可以设置多个匹配规则(`vars` 中的多个条件是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过), 这里就只配置了一个, 根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "dynamic-upstream": { + "rules": [ + { + "match": [ + { + "vars": [ + [ "arg_name","==","jack" ], + [ "http_user-id",">=","23" ], + [ "http_apisix-key","~~","[a-z]+" ] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 + +## 测试插件 + +### 灰度测试 + +**2/3 的请求命中到1981端口的upstream, 1/3 的流量命中到1980端口的upstream。** + +```shell +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 + +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +### 蓝绿测试 + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'new-release: blue' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +当 `match` 匹配通过后,所有请求都命中到插件配置的 `upstream`,否则命中 `route` 上配置的 upstream 。 + +### 自定义测试 + +**在`match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +match 校验成功,但是命中默认端口为`1980`的 upstream。 + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +match 校验成功, 命中端口为`1981`的 upstream。 + +**`match` 规则校验失败(缺少请求头 `apisix-key` ), 响应都为默认 upstream 的数据 `hello 1980`** + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +## 禁用插件 + +当你想去掉 dynamic-upstream 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效: + +```shell +$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` diff --git a/t/plugin/dynamic-upstream.t b/t/plugin/dynamic-upstream.t new file mode 100644 index 000000000000..10a24066e0e9 --- /dev/null +++ b/t/plugin/dynamic-upstream.t @@ -0,0 +1,213 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: a `vars` rule and a plugin `upstream` +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "dynamic-upstream": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name", "==", "jack"], + ["arg_age", "!","<", "16"] + ] + } + ], + "upstreams": [ + { + "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, + "weight": 2 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 2: expression validation failed, return to the default `route` upstream port `1980` +--- request +GET /server_port?name=jack&age=14 +--- response_body eval +1980 +--- no_error_log +[error] + + + +=== TEST 3: the expression passes and returns to the `1981` port +--- request +GET /server_port?name=jack&age=16 +--- response_body eval +1981 +--- no_error_log +[error] + + + +=== TEST 4: the expression passes and initiated multiple requests +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port?name=jack&age=16', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1981, 1981, 1981, 1981 +--- no_error_log +[error] + + + +=== TEST 5: Multiple vars rules and multiple plugin upstream +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "dynamic-upstream": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name", "==", "jack"], + ["arg_age", "~~", "[1-9]*"] + ] + }, + { + "vars": [ + ["arg_name2", "~*=", "[A-Z]*"], + ["arg_age", "!", "<", 18] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":20 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 2 + }, + { + "upstream": { + "name": "upstream_B", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1982":10 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 1 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] From 4d49ef5a8f5c780df8781e990b950e6b55ec00ea Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 3 Dec 2020 01:08:29 +0800 Subject: [PATCH 02/30] fix test case global ipairs and pairs. --- apisix/plugins/dynamic-upstream.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apisix/plugins/dynamic-upstream.lua b/apisix/plugins/dynamic-upstream.lua index 16058f626cf7..bebae66e60b1 100644 --- a/apisix/plugins/dynamic-upstream.lua +++ b/apisix/plugins/dynamic-upstream.lua @@ -19,6 +19,8 @@ local upstream = require("apisix.upstream") local roundrobin = require("resty.roundrobin") local ipmatcher = require("resty.ipmatcher") local expr = require("resty.expr.v1") +local pairs = pairs +local ipairs = ipairs local table_insert = table.insert local lrucache_rr_obj = core.lrucache.new({ From fcec69e33ef65f0293624df8c89b3c70c94c61b3 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 7 Dec 2020 16:35:32 +0800 Subject: [PATCH 03/30] update code, docs and test cases. --- ...dynamic-upstream.lua => traffic-split.lua} | 171 ++-- conf/config-default.yaml | 1 + doc/plugins/dynamic-upstream.md | 20 - doc/plugins/traffic-split.md | 308 ++++++ .../{dynamic-upstream.md => traffic-split.md} | 27 +- t/admin/plugins.t | 2 +- t/debug/debug-mode.t | 1 + t/plugin/dynamic-upstream.t | 213 ---- t/plugin/traffic-split.t | 964 ++++++++++++++++++ 9 files changed, 1374 insertions(+), 333 deletions(-) rename apisix/plugins/{dynamic-upstream.lua => traffic-split.lua} (67%) delete mode 100644 doc/plugins/dynamic-upstream.md create mode 100644 doc/plugins/traffic-split.md rename doc/zh-cn/plugins/{dynamic-upstream.md => traffic-split.md} (87%) delete mode 100644 t/plugin/dynamic-upstream.t create mode 100644 t/plugin/traffic-split.t diff --git a/apisix/plugins/dynamic-upstream.lua b/apisix/plugins/traffic-split.lua similarity index 67% rename from apisix/plugins/dynamic-upstream.lua rename to apisix/plugins/traffic-split.lua index bebae66e60b1..2a63f9c9920d 100644 --- a/apisix/plugins/dynamic-upstream.lua +++ b/apisix/plugins/traffic-split.lua @@ -16,6 +16,7 @@ -- local core = require("apisix.core") local upstream = require("apisix.upstream") +local schema_def = require("apisix.schema_def") local roundrobin = require("resty.roundrobin") local ipmatcher = require("resty.ipmatcher") local expr = require("resty.expr.v1") @@ -23,32 +24,39 @@ local pairs = pairs local ipairs = ipairs local table_insert = table.insert -local lrucache_rr_obj = core.lrucache.new({ - ttl = 0, count = 512, +local lrucache = core.lrucache.new({ + ttl = 0, count = 512 }) -local match_def = { +local vars_schema = { type = "array", items = { - type = "object", - properties = { - vars = { - type = "array", - items = { + type = "array", + items = { + { + type = "string", + minLength = 1, + maxLength = 100 + }, + { + type = "string", + minLength = 1, + maxLength = 2 + } + }, + additionalItems = { + anyOf = { + {type = "string"}, + {type = "number"}, + {type = "boolean"}, + { type = "array", items = { - { - type = "string" - }, - { - type = "string" - } - }, - additionalItems = { anyOf = { { - type = "string" + type = "string", + minLength = 1, maxLength = 100 }, { type = "number" @@ -58,10 +66,22 @@ local match_def = { } } }, - minItems = 0, - maxItems = 10 + uniqueItems = true } } + }, + minItems = 0, + maxItems = 10 + } +} + + +local match_schema = { + type = "array", + items = { + type = "object", + properties = { + vars = vars_schema } }, -- When there is no `match` rule, the default rule passes. @@ -69,67 +89,35 @@ local match_def = { default = {{ vars = {{"server_port", ">", 0}}}} } -local upstream_def = { - type = "object", - additionalProperties = false, - properties = { - name = { type = "string" }, - type = { - type = "string", - enum = { - "roundrobin", - "chash" - }, - default = "roundrobin" - }, - nodes = { type = "object" }, - timeout = { type = "object" }, - enable_websocket = { type = "boolean" }, - pass_host = { - type = "string", - enum = { - "pass", "node", "rewrite" - } - }, - upstream_host = { type = "string" } - }, - dependencies = { - pass_host = { - anyOf = { - { - properties = { - pass_host = { enum = { "rewrite" }} - }, - required = { "upstream_host" } - }, - { - properties = { - pass_host = { enum = { "pass", "node" }} - }, - } - } - } - } -} -local upstreams_def = { +local upstreams_schema = { type = "array", items = { type = "object", properties = { - upstream_id = { type = "string" }, - upstream = upstream_def, + upstream_id = schema_def.id_schema, -- todo: support upstream_id method + upstream = schema_def.upstream, weight = { + description = "used to split traffic between different" .. + "upstreams for plugin configuration", type = "integer", default = 1, minimum = 0 } } }, + -- When the upstream configuration of the plugin is missing, + -- the upstream of `route` is used by default. + default = { + { + weight = 1 + } + }, minItems = 1, maxItems = 20 } + local schema = { type = "object", properties = { @@ -138,19 +126,19 @@ local schema = { items = { type = "object", properties = { - match = match_def, - upstreams = upstreams_def + match = match_schema, + upstreams = upstreams_schema } } } } } -local plugin_name = "dynamic-upstream" +local plugin_name = "traffic-split" local _M = { version = 0.1, - priority = 2523, -- TODO: add a type field, may be a good idea + priority = 966, name = plugin_name, schema = schema } @@ -200,8 +188,8 @@ local function parse_domain_for_node(node) end -local function set_upstream_host(upstream_info, ctx) - local nodes = upstream_info["nodes"] +local function set_upstream(upstream_info, ctx) + local nodes = upstream_info.nodes local new_nodes = {} for addr, weight in pairs(nodes) do local node = {} @@ -212,27 +200,36 @@ local function set_upstream_host(upstream_info, ctx) node.port = port node.weight = weight table_insert(new_nodes, node) - core.log.info("node: ", core.json.delay_encode(node)) + -- Currently only supports a single upstream of the domain name. + -- When the upstream is `IP`, do not do any `pass_host` operation. if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) then - if upstream_info["pass_host"] == "pass" then -- TODO: support rewrite method + local pass_host = upstream_info.pass_host or "pass" + if pass_host == "pass" then ctx.var.upstream_host = ctx.var.host + break + end - elseif upstream_info["pass_host"] == "node" then - ctx.var.upstream_host = host + if pass_host == "rewrite" then + ctx.var.upstream_host = upstream_info.upstream_host + break end - core.log.info("upstream_host: ", ctx.var.upstream_host) + ctx.var.upstream_host = host break end end - - core.log.info("new_node: ", core.json.delay_encode(new_nodes)) + core.log.info("upstream_host: ", ctx.var.upstream_host) local up_conf = { - name = upstream_info["name"], - type = upstream_info["type"], - nodes = new_nodes + name = upstream_info.name, + type = upstream_info.type, + nodes = new_nodes, + timeout = { + send = upstream_info.timeout and upstream_info.timeout.send or 15, + read = upstream_info.timeout and upstream_info.timeout.read or 15, + connect = upstream_info.timeout and upstream_info.timeout.connect or 15 + } } local ok, err = upstream.check_schema(up_conf) @@ -253,7 +250,7 @@ local function new_rr_obj(upstreams) if not upstream_obj.upstream then -- If the `upstream` object has only the `weight` value, it means that -- the `upstream` weight value on the default `route` has been reached. - -- Need to set an identifier to mark the empty upstream. + -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" end server_list[upstream_obj.upstream] = upstream_obj.weight @@ -264,13 +261,17 @@ end function _M.access(conf, ctx) + if not conf or not conf.rules then + return + end + local upstreams, match_flag for _, rule in pairs(conf.rules) do match_flag = true for _, single_match in pairs(rule.match) do local expr, err = expr.new(single_match.vars) if err then - return 500, {"message: expr failed: ", err} + return 500, err end match_flag = expr:eval() @@ -291,16 +292,16 @@ function _M.access(conf, ctx) return end - local rr_up, err = lrucache_rr_obj(upstreams, nil, new_rr_obj, upstreams) + local rr_up, err = lrucache(upstreams, nil, new_rr_obj, upstreams) if not rr_up then - core.log.error("lrucache_rr_obj faild: ", err) + core.log.error("lrucache roundrobin failed: ", err) return 500 end local upstream = rr_up:find() if upstream and upstream ~= "empty_upstream" then core.log.info("upstream: ", core.json.encode(upstream)) - return set_upstream_host(upstream, ctx) + return set_upstream(upstream, ctx) end return diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 994e2b3e568b..3982907f2be5 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -241,6 +241,7 @@ plugins: # plugin list (sorted in alphabetical order) - zipkin #- server-info - dynamic-upstream + - traffic-split stream_plugins: - mqtt-proxy diff --git a/doc/plugins/dynamic-upstream.md b/doc/plugins/dynamic-upstream.md deleted file mode 100644 index cbe3758374b1..000000000000 --- a/doc/plugins/dynamic-upstream.md +++ /dev/null @@ -1,20 +0,0 @@ - - -- [中文](../zh-cn/plugins/dynamic-upstream.md) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md new file mode 100644 index 000000000000..ba14a5fc4c00 --- /dev/null +++ b/doc/plugins/traffic-split.md @@ -0,0 +1,308 @@ + + +- [中文](../zh-cn/plugins/traffic-split.md) + +# Summary +- [**Name**](#name) +- [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) + - [**Grayscale Release**](#grayscale-release) + - [**Blue-green Release**](#blue-green-release) + - [**Custom Release**](#custom-release) +- [**Test Plugin**](#test-plugin) + - [**Grayscale Test**](#grayscale-test) + - [**Blue-green Test**](#blue-green-test) + - [**Custom Test**](#custom-test) +- [**Disable Plugin**](#disable-plugin) + +## Name + +The traffic splitting plug-in divides the request traffic according to a specified ratio and diverts it to the corresponding upstream. The plug-in can realize the functions of gray release, blue-green release and custom release. + +## Attributes + +| Name | Type | Requirement | Default | Valid | Description | +| ---------------- | ------- | ----------- | ------- | ------- | ---------------------------------------------------------------------------------------- | +| rules.match | array[object] | optional | | | List of matching rules. | +| rules.match.vars | array[array] | optional | | | A list consisting of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the section `Operator List` below. | +| rules.upstreams | array[object] | optional | | | List of upstream configuration rules. | +| rules.upstreams.upstream_id | string or integer | optional | | | The upstream id is bound to the corresponding upstream. | +| rules.upstreams.upstream | object | optional | | | Upstream configuration information. | +| rules.upstreams.upstream.type | enum | optional | roundrobin | [roundrobin, chash] | roundrobin supports weighted load, chash consistent hashing, the two are alternatives. | +| rules.upstreams.upstream.nodes | object | optional | | | In the hash table, the key of the internal element is the list of upstream machine addresses, in the format of address + Port, where the address part can be an IP or a domain name, such as 192.168.1.100:80, foo.com:80, etc. value is the weight of the node. In particular, when the weight value is 0, it has special meaning, which usually means that the upstream node is invalid and never wants to be selected. | +| rules.upstreams.upstream.timeout | object | optional | | | Set the timeout period for connecting, sending and receiving messages. | +| rules.upstreams.upstream.enable_websocket | boolean | optional | | | Whether to enable websocket, it is not enabled by default. | +| rules.upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | +| rules.upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | +| rules.upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | +| rules.upstreams.weight | integer | optional | weight = 1 | | According to the weight value to do traffic segmentation. The roundrobin algorithm is used by default, and currently only the roundrobin algorithm is supported. | + +### Operator List + +|operator| description | example | +|--------|---------------------------------|-------------------------------------| +|== |equal |{"arg_name", "==", "json"} | +|~= |not equal |{"arg_name", "~=", "json"} | +|> |greater than | {"arg_age", ">", 24} | +|< |less than |{"arg_age", "<", 24} | +|~~ |Regular match |{"arg_name", "~~", "[a-z]+"} | +|~* |Case insensitive regular match |{"arg_name", "~*", "[a-z]+"} | +|in |find in array |{"arg_name", "in", {"1","2"}} | +|has |left value array has value in the right |{"graphql_root_fields", "has", "repo"}| +|! |reverse the result |{"arg_name", "!", "~~", "[a-z]+"} | + +## How To Enable + +### Grayscale Release + +Traffic is split according to the weight value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. + +```json +{ + "weight": 2 +} +``` + +There is only a `weight` value in the plugin upstreams, which represents the weight value of the upstream traffic arriving on the route. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +### Blue-green Release + +Get the blue and green conditions through the request header (you can also get through the request parameters or NGINX variables). After the `match` rule is matched, it means that all requests hit the upstream configured by the plugin, otherwise the request only hits the configuration on the route upstream. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["http_new-release","==","blue"] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + } + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +### Custom Release + +Multiple matching rules can be set in `match` (multiple conditions in `vars` are the relationship of `add`, and the relationship between multiple `vars` rules is the relationship of `or`; as long as one of the vars rules passes, it means `match` passed), only one is configured here, and the traffic is divided into 4:2 according to the value of `weight`. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name","==","jack"], + ["http_user-id",">","23"], + ["http_apisix-key","~~","[a-z]+"] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +The plug-in sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. + +## Test Plugin + +### Grayscale Test + +**2/3 of the requests hit the upstream on port 1981, and 1/3 of the traffic hit the upstream on port 1980.** + +```shell +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 + +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +### Blue-green Test + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'new-release: blue' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +When the match is passed, all requests will hit the upstream configured by the plugin, otherwise hit the upstream configured on the route. + +### Custom Test + +**After the verification of the `match` rule passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +The match check succeeds, but it hits the upstream of the default port of `1980`. + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +The match check succeeds and it hits the upstream port of `1981`. + +**The `match` rule verification failed (missing request header `apisix-key`), the response is the default upstream data `hello 1980`** + +```shell +$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +## Disable Plugin + +When you want to remove the traffic-split plug-in, it's very simple, just delete the corresponding json configuration in the plug-in configuration, no need to restart the service, it will take effect immediately: + +```shell +$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` diff --git a/doc/zh-cn/plugins/dynamic-upstream.md b/doc/zh-cn/plugins/traffic-split.md similarity index 87% rename from doc/zh-cn/plugins/dynamic-upstream.md rename to doc/zh-cn/plugins/traffic-split.md index f7dc55dfd5b8..828fad9921c8 100644 --- a/doc/zh-cn/plugins/dynamic-upstream.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -17,7 +17,7 @@ # --> -- [English](../../plugins/dynamic-upstream.md) +- [English](../../plugins/traffic-split.md) # 目录 @@ -42,17 +42,16 @@ | 参数名 | 类型 | 可选项 | 默认值 | 有效值 | 描述 | | ------------ | ------------- | ------ | ------ | ------ | -------------------- | -| rules | array[object] | 必需 | | | 插件的配置列表 | | rules.match | array[object] | 可选 | | | 匹配规则列表 | -| rules.match.vars | array | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看下文的 `运算符列表` 部分。 | +| rules.match.vars | array[array] | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看下文的 `运算符列表` 部分。 | | rules.upstreams | array[object] | 可选 | | | 上游配置规则列表。 | -| rules.upstreams.upstream_id | string | 可选 | | | 通过上游 id 绑定对应上游。 | +| rules.upstreams.upstream_id | string or integer | 可选 | | | 通过上游 id 绑定对应上游。 | | rules.upstreams.upstream | object | 可选 | | | 上游配置信息。 | | rules.upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的。 | | rules.upstreams.upstream.nodes | object | 可选 | | | 哈希表,内部元素的 key 是上游机器地址 列表,格式为地址 + Port,其中地址部 分可以是 IP 也可以是域名,⽐如 192.168.1.100:80、foo.com:80等。 value 则是节点的权重,特别的,当权重 值为 0 有特殊含义,通常代表该上游节点 失效,永远不希望被选中。 | -| rules.upstreams.upstream.timeout | object | 可选 | | | 设置连接、发送消息、接收消息的超时时 间 | +| rules.upstreams.upstream.timeout | object | 可选 | | | 设置连接、发送消息、接收消息的超时时间 | | rules.upstreams.upstream.enable_websocket | boolean | 可选 | | | 是否启用 websocket,默认不启用。 | -| rules.upstreams.upstream.pass_host | enum | 可选 | | ["pass", "node", "rewrite"] | pass 透传客户端请求的 host, node 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite 使用 upstream_host 配置的值重写 host 。 | +| rules.upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | | rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | | rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | | rules.upstreams.weight | integer | 可选 | weight = 1 | | 根据 weight 值来做流量切分。默认使用 roundrobin 算法,且目前只支持 roundrobin 算法。| @@ -90,7 +89,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 { "uri": "/index.html", "plugins": { - "dynamic-upstream": { + "traffic-split": { "rules": [ { "upstreams": [ @@ -135,13 +134,13 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 { "uri": "/index.html", "plugins": { - "dynamic-upstream": { + "traffic-split": { "rules": [ { "match": [ { "vars": [ - [ "http_new-release","==","blue" ] + ["http_new-release","==","blue"] ] } ], @@ -178,15 +177,15 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 { "uri": "/index.html", "plugins": { - "dynamic-upstream": { + "traffic-split": { "rules": [ { "match": [ { "vars": [ - [ "arg_name","==","jack" ], - [ "http_user-id",">=","23" ], - [ "http_apisix-key","~~","[a-z]+" ] + ["arg_name","==","jack"], + ["http_user-id",">","23"], + ["http_apisix-key","~~","[a-z]+"] ] } ], @@ -294,7 +293,7 @@ hello 1980 ## 禁用插件 -当你想去掉 dynamic-upstream 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效: +当你想去掉 traffic-split 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效: ```shell $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' diff --git a/t/admin/plugins.t b/t/admin/plugins.t index f81697a95fa1..db19467885ba 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -40,7 +40,7 @@ __DATA__ --- request GET /apisix/admin/plugins/list --- response_body_like eval -qr/\["zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","serverless-post-function"\]/ +qr/\["zipkin","request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","referer-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","hmac-auth","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","api-breaker","limit-conn","limit-count","limit-req","traffic-split","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","sls-logger","tcp-logger","kafka-logger","syslog","udp-logger","example-plugin","serverless-post-function"\]/ --- no_error_log [error] diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t index 8917ed3a9402..b700d0ed57c5 100644 --- a/t/debug/debug-mode.t +++ b/t/debug/debug-mode.t @@ -71,6 +71,7 @@ loaded plugin and sort by priority: 1005 name: api-breaker loaded plugin and sort by priority: 1003 name: limit-conn loaded plugin and sort by priority: 1002 name: limit-count loaded plugin and sort by priority: 1001 name: limit-req +loaded plugin and sort by priority: 966 name: traffic-split loaded plugin and sort by priority: 900 name: redirect loaded plugin and sort by priority: 899 name: response-rewrite loaded plugin and sort by priority: 506 name: grpc-transcode diff --git a/t/plugin/dynamic-upstream.t b/t/plugin/dynamic-upstream.t deleted file mode 100644 index 10a24066e0e9..000000000000 --- a/t/plugin/dynamic-upstream.t +++ /dev/null @@ -1,213 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -use t::APISIX 'no_plan'; - -repeat_each(1); -no_long_string(); -no_root_location(); -log_level("info"); - -run_tests; - -__DATA__ - -=== TEST 1: a `vars` rule and a plugin `upstream` ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "uri": "/server_port", - "plugins": { - "dynamic-upstream": { - "rules": [ - { - "match": [ - { - "vars": [ - ["arg_name", "==", "jack"], - ["arg_age", "!","<", "16"] - ] - } - ], - "upstreams": [ - { - "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, - "weight": 2 - }, - { - "weight": 1 - } - ] - } - ] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "127.0.0.1:1980": 1 - } - } - }]] - ) - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 2: expression validation failed, return to the default `route` upstream port `1980` ---- request -GET /server_port?name=jack&age=14 ---- response_body eval -1980 ---- no_error_log -[error] - - - -=== TEST 3: the expression passes and returns to the `1981` port ---- request -GET /server_port?name=jack&age=16 ---- response_body eval -1981 ---- no_error_log -[error] - - - -=== TEST 4: the expression passes and initiated multiple requests ---- config -location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local bodys = {} - for i = 1, 6 do - local _, _, body = t('/server_port?name=jack&age=16', ngx.HTTP_GET) - bodys[i] = body - end - table.sort(bodys) - ngx.say(table.concat(bodys, ", ")) - } -} ---- request -GET /t ---- response_body -1980, 1980, 1981, 1981, 1981, 1981 ---- no_error_log -[error] - - - -=== TEST 5: Multiple vars rules and multiple plugin upstream ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [=[{ - "uri": "/server_port", - "plugins": { - "dynamic-upstream": { - "rules": [ - { - "match": [ - { - "vars": [ - ["arg_name", "==", "jack"], - ["arg_age", "~~", "[1-9]*"] - ] - }, - { - "vars": [ - ["arg_name2", "~*=", "[A-Z]*"], - ["arg_age", "!", "<", 18] - ] - } - ], - "upstreams": [ - { - "upstream": { - "name": "upstream_A", - "type": "roundrobin", - "nodes": { - "127.0.0.1:1981":20 - }, - "timeout": { - "connect": 15, - "send": 15, - "read": 15 - } - }, - "weight": 2 - }, - { - "upstream": { - "name": "upstream_B", - "type": "roundrobin", - "nodes": { - "127.0.0.1:1982":10 - }, - "timeout": { - "connect": 15, - "send": 15, - "read": 15 - } - }, - "weight": 1 - }, - { - "weight": 1 - } - ] - } - ] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "127.0.0.1:1980": 1 - } - } - }]=] - ) - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t new file mode 100644 index 000000000000..a3e51b029f72 --- /dev/null +++ b/t/plugin/traffic-split.t @@ -0,0 +1,964 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: schema validation passed +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.traffic-split") + local ok, err = plugin.check_schema({ + rules = { + { + match = { + { + vars = { + {"arg_name", "==", "jack"}, + {"arg_age", "!", "<", "16"} + } + }, + { + vars = { + {"arg_name", "==", "rose"}, + {"arg_age", "!", ">", "32"} + } + } + }, + upstreams = { + { + upstream = { + name = "upstream_A", + type = "roundrobin", + nodes = {["127.0.0.1:1981"]=2}, + timeout = {connect = 15, send = 15, read = 15} + }, + weight = 2 + }, + { + upstream = { + name = "upstream_B", + type = "roundrobin", + nodes = {["127.0.0.1:1982"]=2}, + timeout = {connect = 15, send = 15, read = 15} + }, + weight = 2 + }, + { + weight = 1 + } + } + } + } + }) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 2: schema validation passed, and `match` configuration is missing +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.traffic-split") + local ok, err = plugin.check_schema({ + rules = { + { + upstreams = { + { + upstream = { + name = "upstream_A", + type = "roundrobin", + nodes = {["127.0.0.1:1981"]=2}, + timeout = {connect = 15, send = 15, read = 15} + }, + weight = 2 + }, + { + weight = 1 + } + } + } + } + }) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 3: schema validation failed, `vars` expression operator type is wrong +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.traffic-split") + local ok, err = plugin.check_schema({ + rules = { + { + match = { + { + vars = { + {"arg_name", 123, "jack"} + } + } + }, + upstreams = { + { + upstream = { + name = "upstream_A", + type = "roundrobin", + nodes = {["127.0.0.1:1981"]=2}, + timeout = {connect = 15, send = 15, read = 15} + }, + weight = 2 + }, + { + weight = 1 + } + } + } + } + }) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body eval +qr/property "rules" validation failed:.* failed to validate item 2: wrong type: expected string, got number/ +--- no_error_log +[error] + + + +=== TEST 4: missing `rules` configuration, the upstream of the default `route` takes effect +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": {} + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 5: the upstream of the default `route` takes effect +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1980, 1980, 1980, 1980 +--- no_error_log +[error] + + + +=== TEST 6: when `upstreams` is empty, the upstream of `route` is used by default +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [{}] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 7: the upstream of the default `route` takes effect +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1980, 1980, 1980, 1980 +--- no_error_log +[error] + + + +=== TEST 8: single `vars` expression and single plugin `upstream`, and the upstream traffic on `route` accounts for 1/3 +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name", "==", "jack"], + ["arg_age", "!","<", "16"] + ] + } + ], + "upstreams": [ + { + "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, + "weight": 2 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 9: expression validation failed, return to the default `route` upstream port `1980` +--- request +GET /server_port?name=jack&age=14 +--- response_body eval +1980 +--- no_error_log +[error] + + + +=== TEST 10: the expression passes and initiated multiple requests, the upstream traffic of `route` accounts for 1/3, and the upstream traffic of plugins accounts for 2/3 +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port?name=jack&age=16', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1981, 1981, 1981, 1981 +--- no_error_log +[error] + + + +=== TEST 11: Multiple vars rules and multiple plugin upstream +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] + }, + { + "vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, + {"weight": 1} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 12: expression validation failed, return to the default `route` upstream port `1980` +--- request +GET /server_port?name=jack&age=0 +--- response_body eval +1980 +--- no_error_log +[error] + + + +=== TEST 13: the expression passes and initiated multiple requests, the upstream traffic of `route` accounts for 1/5, and the upstream traffic of plugins accounts for 4/5 +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 5 do + local _, _, body = t('/server_port?name=jack&age=22', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981, 1981, 1982, 1982 +--- no_error_log +[error] + + + +=== TEST 14: Multiple vars rules and multiple plugin upstream, do not split traffic to the upstream of `route` +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] + }, + { + "vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 15: the expression passes and initiated multiple requests, do not split traffic to the upstream of `route` +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port?name=jack&age=22', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1981, 1981, 1981, 1982, 1982, 1982 +--- no_error_log +[error] + + + +=== TEST 16: support multiple ip configuration of `nodes`, and missing upstream configuration on `route` +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 1} + ] + } + ] + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 17: the expression passes and initiated multiple requests, roundrobin the ip of nodes +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 5 do + local _, _, body = t('/server_port?name=jack&age=22', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981, 1981, 1982, 1982 +--- no_error_log + + + +=== TEST 18: host is domain name +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "foo.com:80": 0 + } + }, + "weight": 2 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 19: domain name resolved successfully +--- request +GET /server_port +--- error_code: 502 +--- error_log eval +qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ + + + +=== TEST 20: mock Grayscale Release +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + }, + "weight": 2 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 21: 2/3 request traffic hits the upstream of the plugin, 1/3 request traffic hits the upstream of `route` +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 6 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1981, 1981, 1981, 1981 +--- no_error_log +[error] + + + +=== TEST 22: mock Blue-green Release +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + [ "http_release","==","blue" ] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + } + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 23: release is equal to `blue` +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["release"] = "blue" + for i = 1, 6 do + local _, _, body = t('/server_port', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1981, 1981, 1981, 1981, 1981, 1981 +--- no_error_log +[error] + + + +=== TEST 24: release is equal to `green` +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["release"] = "green" + for i = 1, 6 do + local _, _, body = t('/server_port', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1980, 1980, 1980, 1980 +--- no_error_log +[error] + + + +=== TEST 25: mock Custom Release +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, + {"weight": 1} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 26: `match` rule passed +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["appkey"] = "hello" + for i = 1, 5 do + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981, 1981, 1982, 1982 +--- no_error_log +[error] + + + +=== TEST 27: `match` rule failed, `age` condition did not match +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["release"] = "green" + for i = 1, 6 do + local _, _, body = t('/server_port?name=jack&age=16', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1980, 1980, 1980, 1980, 1980 +--- no_error_log +[error] From 2edd06d74318517e8f235e4c27731f63e23b7cfa Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Wed, 9 Dec 2020 15:56:30 +0800 Subject: [PATCH 04/30] resolve review. --- apisix/init.lua | 1 + apisix/plugins/traffic-split.lua | 21 ++--------- doc/plugins/traffic-split.md | 53 ++++++++++------------------ doc/zh-cn/plugins/traffic-split.md | 56 +++++++++++------------------- 4 files changed, 42 insertions(+), 89 deletions(-) diff --git a/apisix/init.lua b/apisix/init.lua index 30b729ee1078..bee5ae5291f7 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -201,6 +201,7 @@ local function parse_domain(host) return nil, "failed to parse domain" end end +_M.parse_domain = parse_domain local function parse_domain_for_nodes(nodes) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 2a63f9c9920d..4ca96671cb3d 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -17,6 +17,7 @@ local core = require("apisix.core") local upstream = require("apisix.upstream") local schema_def = require("apisix.schema_def") +local init = require("apisix.init") local roundrobin = require("resty.roundrobin") local ipmatcher = require("resty.ipmatcher") local expr = require("resty.expr.v1") @@ -154,27 +155,9 @@ function _M.check_schema(conf) end -local function parse_domain(host) - local ip_info, err = core.utils.dns_parse(host) - if not ip_info then - core.log.error("failed to parse domain: ", host, ", error: ",err) - return nil, err - end - - core.log.info("parse addr: ", core.json.delay_encode(ip_info)) - core.log.info("host: ", host) - if not ip_info.address then - return nil, "failed to parse domain" - end - - core.log.info("dns resolver domain: ", host, " to ", ip_info.address) - return ip_info.address -end - - local function parse_domain_for_node(node) if not ipmatcher.parse_ipv4(node) and not ipmatcher.parse_ipv6(node) then - local ip, err = parse_domain(node) + local ip, err = init.parse_domain(node) if ip then return ip end diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index ba14a5fc4c00..aed196addbe8 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -20,17 +20,17 @@ - [中文](../zh-cn/plugins/traffic-split.md) # Summary -- [**Name**](#name) -- [**Attributes**](#attributes) -- [**How To Enable**](#how-to-enable) - - [**Grayscale Release**](#grayscale-release) - - [**Blue-green Release**](#blue-green-release) - - [**Custom Release**](#custom-release) -- [**Test Plugin**](#test-plugin) - - [**Grayscale Test**](#grayscale-test) - - [**Blue-green Test**](#blue-green-test) - - [**Custom Test**](#custom-test) -- [**Disable Plugin**](#disable-plugin) + - [**Name**](#name) + - [**Attributes**](#attributes) + - [**How To Enable**](#how-to-enable) + - [**Grayscale Release**](#grayscale-release) + - [**Blue-green Release**](#blue-green-release) + - [**Custom Release**](#custom-release) + - [**Test Plugin**](#test-plugin) + - [**Grayscale Test**](#grayscale-test) + - [**Blue-green Test**](#blue-green-test) + - [**Custom Test**](#custom-test) + - [**Disable Plugin**](#disable-plugin) ## Name @@ -41,32 +41,17 @@ The traffic splitting plug-in divides the request traffic according to a specifi | Name | Type | Requirement | Default | Valid | Description | | ---------------- | ------- | ----------- | ------- | ------- | ---------------------------------------------------------------------------------------- | | rules.match | array[object] | optional | | | List of matching rules. | -| rules.match.vars | array[array] | optional | | | A list consisting of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the section `Operator List` below. | +| rules.match.vars | array[array] | optional | | | A list consisting of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | | rules.upstreams | array[object] | optional | | | List of upstream configuration rules. | -| rules.upstreams.upstream_id | string or integer | optional | | | The upstream id is bound to the corresponding upstream. | +| rules.upstreams.upstream_id | string or integer | optional | | | The upstream id is bound to the corresponding upstream(not currently supported). | | rules.upstreams.upstream | object | optional | | | Upstream configuration information. | | rules.upstreams.upstream.type | enum | optional | roundrobin | [roundrobin, chash] | roundrobin supports weighted load, chash consistent hashing, the two are alternatives. | | rules.upstreams.upstream.nodes | object | optional | | | In the hash table, the key of the internal element is the list of upstream machine addresses, in the format of address + Port, where the address part can be an IP or a domain name, such as 192.168.1.100:80, foo.com:80, etc. value is the weight of the node. In particular, when the weight value is 0, it has special meaning, which usually means that the upstream node is invalid and never wants to be selected. | -| rules.upstreams.upstream.timeout | object | optional | | | Set the timeout period for connecting, sending and receiving messages. | -| rules.upstreams.upstream.enable_websocket | boolean | optional | | | Whether to enable websocket, it is not enabled by default. | +| rules.upstreams.upstream.timeout | object | optional | 15 | | Set the timeout period for connecting, sending and receiving messages (time unit: second, all default to 15 seconds). | | rules.upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | | rules.upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | | rules.upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | -| rules.upstreams.weight | integer | optional | weight = 1 | | According to the weight value to do traffic segmentation. The roundrobin algorithm is used by default, and currently only the roundrobin algorithm is supported. | - -### Operator List - -|operator| description | example | -|--------|---------------------------------|-------------------------------------| -|== |equal |{"arg_name", "==", "json"} | -|~= |not equal |{"arg_name", "~=", "json"} | -|> |greater than | {"arg_age", ">", 24} | -|< |less than |{"arg_age", "<", 24} | -|~~ |Regular match |{"arg_name", "~~", "[a-z]+"} | -|~* |Case insensitive regular match |{"arg_name", "~*", "[a-z]+"} | -|in |find in array |{"arg_name", "in", {"1","2"}} | -|has |left value array has value in the right |{"graphql_root_fields", "has", "repo"}| -|! |reverse the result |{"arg_name", "!", "~~", "[a-z]+"} | +| rules.upstreams.weight | integer | optional | weight = 1 | | The traffic is divided according to the weight value, and the roundrobin algorithm is used to divide multiple weights. | ## How To Enable @@ -242,7 +227,7 @@ world 1981 ### Blue-green Test ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'new-release: blue' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'new-release: blue' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -257,7 +242,7 @@ When the match is passed, all requests will hit the upstream configured by the p **After the verification of the `match` rule passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -268,7 +253,7 @@ hello 1980 The match check succeeds, but it hits the upstream of the default port of `1980`. ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -281,7 +266,7 @@ The match check succeeds and it hits the upstream port of `1981`. **The `match` rule verification failed (missing request header `apisix-key`), the response is the default upstream data `hello 1980`** ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 828fad9921c8..f77ed60ba449 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -21,18 +21,17 @@ # 目录 -- [名字](#名字) -- [属性](#属性) -- [如何启用](#如何启用) - - [灰度发布](#灰度发布) - - [蓝绿发布](#蓝绿发布) - - [自定义发布](#自定义发布) -- [测试插件](#测试插件) - - [灰度测试](#灰度测试) - - [蓝绿测试](#蓝绿测试) - - [自定义测试](#自定义测试) -- [禁用插件](#禁用插件) - + - [名字](#名字) + - [属性](#属性) + - [如何启用](#如何启用) + - [灰度发布](#灰度发布) + - [蓝绿发布](#蓝绿发布) + - [自定义发布](#自定义发布) + - [测试插件](#测试插件) + - [灰度测试](#灰度测试) + - [蓝绿测试](#蓝绿测试) + - [自定义测试](#自定义测试) + - [禁用插件](#禁用插件) ## 名字 @@ -43,32 +42,17 @@ | 参数名 | 类型 | 可选项 | 默认值 | 有效值 | 描述 | | ------------ | ------------- | ------ | ------ | ------ | -------------------- | | rules.match | array[object] | 可选 | | | 匹配规则列表 | -| rules.match.vars | array[array] | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看下文的 `运算符列表` 部分。 | +| rules.match.vars | array[array] | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | | rules.upstreams | array[object] | 可选 | | | 上游配置规则列表。 | -| rules.upstreams.upstream_id | string or integer | 可选 | | | 通过上游 id 绑定对应上游。 | +| rules.upstreams.upstream_id | string or integer | 可选 | | | 通过上游 id 绑定对应上游(暂不支持)。 | | rules.upstreams.upstream | object | 可选 | | | 上游配置信息。 | -| rules.upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的。 | +| rules.upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的(目前只支持 `roundrobin`)。 | | rules.upstreams.upstream.nodes | object | 可选 | | | 哈希表,内部元素的 key 是上游机器地址 列表,格式为地址 + Port,其中地址部 分可以是 IP 也可以是域名,⽐如 192.168.1.100:80、foo.com:80等。 value 则是节点的权重,特别的,当权重 值为 0 有特殊含义,通常代表该上游节点 失效,永远不希望被选中。 | -| rules.upstreams.upstream.timeout | object | 可选 | | | 设置连接、发送消息、接收消息的超时时间 | -| rules.upstreams.upstream.enable_websocket | boolean | 可选 | | | 是否启用 websocket,默认不启用。 | +| rules.upstreams.upstream.timeout | object | 可选 | 15 | | 设置连接、发送消息、接收消息的超时时间(时间单位:秒,都默认为 15 秒)。 | | rules.upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | | rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | | rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | -| rules.upstreams.weight | integer | 可选 | weight = 1 | | 根据 weight 值来做流量切分。默认使用 roundrobin 算法,且目前只支持 roundrobin 算法。| - -### 运算符列表 - -| 运算符 | 描述 | 示例 | -|-------|------------|--------------------------------------------------------------| -| == | 相等 | {"arg_name", "==", "json"} | -| ~= | 不等 | {"arg_name", "~=", "json"} | -| ~~ | 正则匹配 | {"arg_name", "~~", "[a-z]+"} | -| ~* | 不区分大小写的正则匹配 | {"arg_name", "~*", "[a-z]+"} | -| < | 小于 | {"arg_age", "<", 24} | -| > | 大于 | {"arg_age", ">", 24} | -| in | 在数组中查找 | {"arg_name", "in",{"1","2"}} | -| has | 在左边的数组值中有右边的值 | {"graphql_root_fields", "has", "repo"} | -| ! | 结果值取反 | {"arg_name", "!", "~~", "[a-z]+"} | +| rules.upstreams.weight | integer | 可选 | weight = 1 | | 根据 weight 值做流量划分,多个 weight 之间使用 roundrobin 算法划分。| ## 如何启用 @@ -244,7 +228,7 @@ world 1981 ### 蓝绿测试 ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'new-release: blue' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'new-release: blue' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -259,7 +243,7 @@ world 1981 **在`match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -270,7 +254,7 @@ hello 1980 match 校验成功,但是命中默认端口为`1980`的 upstream。 ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -H 'apisix-key: hello' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... @@ -283,7 +267,7 @@ match 校验成功, 命中端口为`1981`的 upstream。 **`match` 规则校验失败(缺少请求头 `apisix-key` ), 响应都为默认 upstream 的数据 `hello 1980`** ```shell -$ curl http://127.0.0.1:9080/index.html?name=jack -H 'user-id:30' -i +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... From 4a9cd43d8ced14d44adf6a71cd4dc067f1355502 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 10 Dec 2020 00:41:07 +0800 Subject: [PATCH 05/30] resolve review and update docs. --- apisix/plugins/traffic-split.lua | 2 +- doc/plugins/traffic-split.md | 127 +++++++++++++++++++++++++++-- doc/zh-cn/plugins/traffic-split.md | 127 +++++++++++++++++++++++++++-- 3 files changed, 245 insertions(+), 11 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 4ca96671cb3d..e91ae2cce734 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -100,7 +100,7 @@ local upstreams_schema = { upstream = schema_def.upstream, weight = { description = "used to split traffic between different" .. - "upstreams for plugin configuration", + "upstreams for plugin configuration", type = "integer", default = 1, minimum = 0 diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index aed196addbe8..7f099b6966d2 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -153,7 +153,58 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ### Custom Release -Multiple matching rules can be set in `match` (multiple conditions in `vars` are the relationship of `add`, and the relationship between multiple `vars` rules is the relationship of `or`; as long as one of the vars rules passes, it means `match` passed), only one is configured here, and the traffic is divided into 4:2 according to the value of `weight`. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Multiple matching rules can be set in `match`, multiple expressions in `vars` are in the relationship of `add`, and multiple `vars` rules are in the relationship of `or`; as long as one of the vars rules is passed, then Indicates that `match` passed. + +Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name","==","jack"], + ["http_user-id",">","23"], + ["http_apisix-key","~~","[a-z]+"] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. + +Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -169,6 +220,11 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ["arg_name","==","jack"], ["http_user-id",">","23"], ["http_apisix-key","~~","[a-z]+"] + ], + "vars": [ + ["arg_name2","==","rose"], + ["http_user-id2","!",">","33"], + ["http_apisix-key2","~~","[a-z]+"] ] } ], @@ -200,7 +256,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` -The plug-in sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. +The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. ## Test Plugin @@ -239,15 +295,19 @@ When the match is passed, all requests will hit the upstream configured by the p ### Custom Test +**Example 1:** + **After the verification of the `match` rule passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** +The match check succeeds and it hits the upstream port of `1981`. + ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... -hello 1980 +world 1981 ``` The match check succeeds, but it hits the upstream of the default port of `1980`. @@ -258,10 +318,10 @@ HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... -world 1981 +hello 1980 ``` -The match check succeeds and it hits the upstream port of `1981`. +After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. **The `match` rule verification failed (missing request header `apisix-key`), the response is the default upstream data `hello 1980`** @@ -274,6 +334,63 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` +**Example 2:** + +**The expressions of the two `vars` match successfully. After the `match` rule is passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. + +**The second `vars` expression failed (missing the `name2` request parameter). After the `match` rule was verified, 2/3 of the requests hit the upstream of port 1981, and 1/3 of the request hits the port 1980 upstream.** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. + +**Two `vars` expressions failed (missing `name` and `name2` request parameters), `match` rule verification failed, and the response is the default upstream data `hello 1980`** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + ## Disable Plugin When you want to remove the traffic-split plug-in, it's very simple, just delete the corresponding json configuration in the plug-in configuration, no need to restart the service, it will take effect immediately: diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index f77ed60ba449..a47a579533db 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -154,7 +154,9 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ### 自定义发布 -`match` 中可以设置多个匹配规则(`vars` 中的多个条件是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过), 这里就只配置了一个, 根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +`match` 中可以设置多个匹配规则,`vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过。 + +示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -203,6 +205,60 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 +示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["arg_name","==","jack"], + ["http_user-id",">","23"], + ["http_apisix-key","~~","[a-z]+"] + ], + "vars": [ + ["arg_name2","==","rose"], + ["http_user-id2","!",">","33"], + ["http_apisix-key2","~~","[a-z]+"] + ] + } + ], + "upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + } + }, + "weight": 4 + }, + { + "weight": 2 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 + ## 测试插件 ### 灰度测试 @@ -240,18 +296,22 @@ world 1981 ### 自定义测试 +**示例1:** + **在`match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** +match 校验成功, 命中端口为`1981`的 upstream。 + ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... -hello 1980 +world 1981 ``` -match 校验成功,但是命中默认端口为`1980`的 upstream。 +match 校验成功,命中默认端口为`1980`的 upstream。 ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i @@ -259,10 +319,10 @@ HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ...... -world 1981 +hello 1980 ``` -match 校验成功, 命中端口为`1981`的 upstream。 +在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 **`match` 规则校验失败(缺少请求头 `apisix-key` ), 响应都为默认 upstream 的数据 `hello 1980`** @@ -275,6 +335,63 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` +**示例2:** + +**两个 `vars` 的表达式匹配成功, `match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 + +**第二个 `vars` 的表达式匹配失败(缺少 `name2` 请求参数), `match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 + +**两个 `vars` 的表达式匹配失败(缺少 `name` 和 `name2` 请求参数),`match` 规则校验失败, 响应都为默认 upstream 的数据 `hello 1980`** + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + ## 禁用插件 当你想去掉 traffic-split 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效: From 9b200afb2deb0608ad697e4b04ffbb62931a076b Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 10 Dec 2020 00:59:33 +0800 Subject: [PATCH 06/30] resolve conflicts. --- conf/config-default.yaml | 2 +- doc/plugins/traffic-split.md | 22 +++++++++++----------- doc/zh-cn/plugins/traffic-split.md | 22 +++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 3982907f2be5..3662f7f957ce 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -149,7 +149,6 @@ nginx_config: # config for render the template to genarate n stream_configuration_snippet: | # Add custom Nginx stream configuration to nginx.conf. # The configuration should be well indented! - http: enable_access_log: true # enable access log or not, default true access_log: "logs/access.log" @@ -259,3 +258,4 @@ plugin_attr: server-info: report_interval: 60 # server info report interval (unit: second) report_ttl: 3600 # live time for server info in etcd (unit: second) + report_ttl: 3600 # live time for server info in etcd (unit: second) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 7f099b6966d2..af0057014ced 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -20,17 +20,17 @@ - [中文](../zh-cn/plugins/traffic-split.md) # Summary - - [**Name**](#name) - - [**Attributes**](#attributes) - - [**How To Enable**](#how-to-enable) - - [**Grayscale Release**](#grayscale-release) - - [**Blue-green Release**](#blue-green-release) - - [**Custom Release**](#custom-release) - - [**Test Plugin**](#test-plugin) - - [**Grayscale Test**](#grayscale-test) - - [**Blue-green Test**](#blue-green-test) - - [**Custom Test**](#custom-test) - - [**Disable Plugin**](#disable-plugin) +- [**Name**](#name) +- [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) + - [**Grayscale Release**](#grayscale-release) + - [**Blue-green Release**](#blue-green-release) + - [**Custom Release**](#custom-release) +- [**Test Plugin**](#test-plugin) + - [**Grayscale Test**](#grayscale-test) + - [**Blue-green Test**](#blue-green-test) + - [**Custom Test**](#custom-test) +- [**Disable Plugin**](#disable-plugin) ## Name diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index a47a579533db..d6904a4e7ef3 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -21,17 +21,17 @@ # 目录 - - [名字](#名字) - - [属性](#属性) - - [如何启用](#如何启用) - - [灰度发布](#灰度发布) - - [蓝绿发布](#蓝绿发布) - - [自定义发布](#自定义发布) - - [测试插件](#测试插件) - - [灰度测试](#灰度测试) - - [蓝绿测试](#蓝绿测试) - - [自定义测试](#自定义测试) - - [禁用插件](#禁用插件) +- [名字](#名字) +- [属性](#属性) +- [如何启用](#如何启用) + - [灰度发布](#灰度发布) + - [蓝绿发布](#蓝绿发布) + - [自定义发布](#自定义发布) +- [测试插件](#测试插件) + - [灰度测试](#灰度测试) + - [蓝绿测试](#蓝绿测试) + - [自定义测试](#自定义测试) +- [禁用插件](#禁用插件) ## 名字 From 42d74f9f544cc880459c27e510c2144d0cf1cdbf Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 10 Dec 2020 01:15:45 +0800 Subject: [PATCH 07/30] update docs. --- apisix/plugins/traffic-split.lua | 2 +- conf/config-default.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index e91ae2cce734..734f653d8202 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -251,7 +251,7 @@ function _M.access(conf, ctx) local upstreams, match_flag for _, rule in pairs(conf.rules) do match_flag = true - for _, single_match in pairs(rule.match) do + for _, single_match in ipairs(rule.match) do local expr, err = expr.new(single_match.vars) if err then return 500, err diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 3662f7f957ce..3035dbc3ce5e 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -239,7 +239,6 @@ plugins: # plugin list (sorted in alphabetical order) - wolf-rbac - zipkin #- server-info - - dynamic-upstream - traffic-split stream_plugins: From 0dcecf51cd70aed54f1761eae4da2f87cc624144 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Sat, 12 Dec 2020 00:28:33 +0800 Subject: [PATCH 08/30] update README docs and resolve review. --- README.md | 1 + apisix/plugins/traffic-split.lua | 8 +++--- doc/README.md | 1 + doc/plugins/traffic-split.md | 31 +++++++++++---------- doc/zh-cn/README.md | 1 + doc/zh-cn/plugins/traffic-split.md | 30 ++++++++++---------- t/plugin/traffic-split.t | 44 +++++++++++++++--------------- 7 files changed, 60 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 6688a2124d8f..ad3519d98c13 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against - [Health Checks](doc/health-check.md): Enable health check on the upstream node, and will automatically filter unhealthy nodes during load balancing to ensure system stability. - Circuit-Breaker: Intelligent tracking of unhealthy upstream services. - [Proxy Mirror](doc/plugins/proxy-mirror.md): Provides the ability to mirror client requests. + - [Traffic Split](doc/plugins/traffic-split.md): Supports dividing the request traffic according to the specified proportional relationship. - **Fine-grained routing** diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 734f653d8202..3e43d5d45603 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -98,7 +98,7 @@ local upstreams_schema = { properties = { upstream_id = schema_def.id_schema, -- todo: support upstream_id method upstream = schema_def.upstream, - weight = { + weighted_upstreams = { description = "used to split traffic between different" .. "upstreams for plugin configuration", type = "integer", @@ -111,7 +111,7 @@ local upstreams_schema = { -- the upstream of `route` is used by default. default = { { - weight = 1 + weighted_upstreams = 1 } }, minItems = 1, @@ -231,12 +231,12 @@ local function new_rr_obj(upstreams) local server_list = {} for _, upstream_obj in ipairs(upstreams) do if not upstream_obj.upstream then - -- If the `upstream` object has only the `weight` value, it means that + -- If the `upstream` object has only the `weighted_upstreams` value, it means that -- the `upstream` weight value on the default `route` has been reached. -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" end - server_list[upstream_obj.upstream] = upstream_obj.weight + server_list[upstream_obj.upstream] = upstream_obj.weighted_upstreams end return roundrobin:new(server_list) diff --git a/doc/README.md b/doc/README.md index 00226c84ae39..426ce358d0df 100644 --- a/doc/README.md +++ b/doc/README.md @@ -80,6 +80,7 @@ * [request-validation](plugins/request-validation.md): Validates requests before forwarding to upstream. * [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests. * [api-breaker](plugins/api-breaker.md): Circuit Breaker for API that stops requests forwarding to upstream in case of unhealthy state. +* [traffic-split](plugins/traffic-split.md): The traffic division plug-in divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plug-in, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. ### Monitoring diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index af0057014ced..29f967d86776 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -20,6 +20,7 @@ - [中文](../zh-cn/plugins/traffic-split.md) # Summary + - [**Name**](#name) - [**Attributes**](#attributes) - [**How To Enable**](#how-to-enable) @@ -34,7 +35,7 @@ ## Name -The traffic splitting plug-in divides the request traffic according to a specified ratio and diverts it to the corresponding upstream. The plug-in can realize the functions of gray release, blue-green release and custom release. +The traffic division plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. ## Attributes @@ -51,21 +52,21 @@ The traffic splitting plug-in divides the request traffic according to a specifi | rules.upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | | rules.upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | | rules.upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | -| rules.upstreams.weight | integer | optional | weight = 1 | | The traffic is divided according to the weight value, and the roundrobin algorithm is used to divide multiple weights. | +| rules.upstreams.weighted_upstreams | integer | optional | weighted_upstreams = 1 | | The traffic is divided according to the `weighted_upstreams` value, and the roundrobin algorithm is used to divide multiple `weighted_upstreams`. | ## How To Enable -### Grayscale Release - -Traffic is split according to the weight value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. +There is only the value of `weighted_upstreams` in the upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. ```json { - "weight": 2 + "weighted_upstreams": 2 } ``` -There is only a `weight` value in the plugin upstreams, which represents the weight value of the upstream traffic arriving on the route. +### Grayscale Release + +Traffic is split according to the `weighted_upstreams` value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -89,10 +90,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } @@ -155,7 +156,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 Multiple matching rules can be set in `match`, multiple expressions in `vars` are in the relationship of `add`, and multiple `vars` rules are in the relationship of `or`; as long as one of the vars rules is passed, then Indicates that `match` passed. -Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weighted_upstreams`, the flow is divided into 4:2. Among them, only the `weighted_upstreams` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -183,10 +184,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } @@ -204,7 +205,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. -Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weighted_upstreams`, the flow is divided into 4:2. Among them, only the `weighted_upstreams` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -237,10 +238,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } diff --git a/doc/zh-cn/README.md b/doc/zh-cn/README.md index 53250ab9a4b6..441bcb1aaa1b 100644 --- a/doc/zh-cn/README.md +++ b/doc/zh-cn/README.md @@ -81,6 +81,7 @@ * [request-validation](plugins/request-validation.md): 请求验证。 * [proxy-mirror](plugins/proxy-mirror.md):代理镜像插件提供镜像客户端请求的能力。 * [api-breaker](plugins/api-breaker.md): API的断路器,在状态不正常的情况下停止将请求转发到上游。 +* [traffic-split](plugins/traffic-split.md): 请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 ### Monitoring diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index d6904a4e7ef3..3bf260644808 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -35,7 +35,7 @@ ## 名字 -流量分割插件,对请求流量按指定的比例划分,并将其分流到对应的 upstream。通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 +请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 ## 属性 @@ -52,21 +52,21 @@ | rules.upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | | rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | | rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | -| rules.upstreams.weight | integer | 可选 | weight = 1 | | 根据 weight 值做流量划分,多个 weight 之间使用 roundrobin 算法划分。| +| rules.upstreams.weighted_upstreams | integer | 可选 | weighted_upstreams = 1 | | 根据 `weighted_upstreams` 值做流量划分,多个 weighted_upstreams 之间使用 roundrobin 算法划分。| ## 如何启用 -### 灰度发布 - -根据插件中 upstreams 配置的 weight 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 +在插件的 upstreams 中只有 `weighted_upstreams` 值,表示到达默认 `route` 上的 upstream 流量权重值。 ```json { - "weight": 2 + "weighted_upstreams": 2 } ``` -在插件 upstreams 中只有 `weight` 值,表示到达 route 上的 upstream 流量权重值。 +### 灰度发布 + +根据插件中 upstreams 配置的 `weighted_upstreams` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -90,10 +90,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } @@ -156,7 +156,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 `match` 中可以设置多个匹配规则,`vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过。 -示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weighted_upstreams` 值将流量按 4:2 划分。其中只有 `weighted_upstreams` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -184,10 +184,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } @@ -205,7 +205,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 -示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weighted_upstreams` 值将流量按 4:2 划分。其中只有 `weighted_upstreams` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -238,10 +238,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weighted_upstreams": 4 }, { - "weight": 2 + "weighted_upstreams": 2 } ] } diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index a3e51b029f72..8e4e593b89e0 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -55,7 +55,7 @@ __DATA__ nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weight = 2 + weighted_upstreams = 2 }, { upstream = { @@ -64,10 +64,10 @@ __DATA__ nodes = {["127.0.0.1:1982"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weight = 2 + weighted_upstreams = 2 }, { - weight = 1 + weighted_upstreams = 1 } } } @@ -105,10 +105,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weight = 2 + weighted_upstreams = 2 }, { - weight = 1 + weighted_upstreams = 1 } } } @@ -153,10 +153,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weight = 2 + weighted_upstreams = 2 }, { - weight = 1 + weighted_upstreams = 1 } } } @@ -324,10 +324,10 @@ GET /t "upstreams": [ { "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, - "weight": 2 + "weighted_upstreams": 2 }, { - "weight": 1 + "weighted_upstreams": 1 } ] } @@ -412,9 +412,9 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, - {"weight": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2}, + {"weighted_upstreams": 1} ] } ] @@ -498,8 +498,8 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2} ] } ] @@ -570,7 +570,7 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weighted_upstreams": 1} ] } ] @@ -637,10 +637,10 @@ GET /t "foo.com:80": 0 } }, - "weight": 2 + "weighted_upstreams": 2 }, { - "weight": 1 + "weighted_upstreams": 1 } ] } @@ -701,10 +701,10 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ "127.0.0.1:1981":1 } }, - "weight": 2 + "weighted_upstreams": 2 }, { - "weight": 1 + "weighted_upstreams": 1 } ] } @@ -884,9 +884,9 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 2}, - {"weight": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2}, + {"weighted_upstreams": 1} ] } ] From 847fa6b27323c449d7ecf0b84afbe847012518a5 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Sat, 12 Dec 2020 20:13:39 +0800 Subject: [PATCH 09/30] `weighted_upstreams` field changed to singular form `weighted_upstream`. --- apisix/plugins/traffic-split.lua | 8 +++--- doc/plugins/traffic-split.md | 24 ++++++++-------- doc/zh-cn/plugins/traffic-split.md | 24 ++++++++-------- t/plugin/traffic-split.t | 44 +++++++++++++++--------------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 3e43d5d45603..ed2f5e0428ec 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -98,7 +98,7 @@ local upstreams_schema = { properties = { upstream_id = schema_def.id_schema, -- todo: support upstream_id method upstream = schema_def.upstream, - weighted_upstreams = { + weighted_upstream = { description = "used to split traffic between different" .. "upstreams for plugin configuration", type = "integer", @@ -111,7 +111,7 @@ local upstreams_schema = { -- the upstream of `route` is used by default. default = { { - weighted_upstreams = 1 + weighted_upstream = 1 } }, minItems = 1, @@ -231,12 +231,12 @@ local function new_rr_obj(upstreams) local server_list = {} for _, upstream_obj in ipairs(upstreams) do if not upstream_obj.upstream then - -- If the `upstream` object has only the `weighted_upstreams` value, it means that + -- If the `upstream` object has only the `weighted_upstream` value, it means that -- the `upstream` weight value on the default `route` has been reached. -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" end - server_list[upstream_obj.upstream] = upstream_obj.weighted_upstreams + server_list[upstream_obj.upstream] = upstream_obj.weighted_upstream end return roundrobin:new(server_list) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 29f967d86776..e61413bc540a 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -52,21 +52,21 @@ The traffic division plugin divides the request traffic according to the specifi | rules.upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | | rules.upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | | rules.upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | -| rules.upstreams.weighted_upstreams | integer | optional | weighted_upstreams = 1 | | The traffic is divided according to the `weighted_upstreams` value, and the roundrobin algorithm is used to divide multiple `weighted_upstreams`. | +| rules.upstreams.weighted_upstream | integer | optional | weighted_upstream = 1 | | The traffic is divided according to the `weighted_upstream` value, and the roundrobin algorithm is used to divide multiple `weighted_upstream`. | ## How To Enable -There is only the value of `weighted_upstreams` in the upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. +There is only the value of `weighted_upstream` in the upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. ```json { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ``` ### Grayscale Release -Traffic is split according to the `weighted_upstreams` value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. +Traffic is split according to the `weighted_upstream` value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -90,10 +90,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } @@ -156,7 +156,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 Multiple matching rules can be set in `match`, multiple expressions in `vars` are in the relationship of `add`, and multiple `vars` rules are in the relationship of `or`; as long as one of the vars rules is passed, then Indicates that `match` passed. -Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weighted_upstreams`, the flow is divided into 4:2. Among them, only the `weighted_upstreams` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weighted_upstream`, the flow is divided into 4:2. Among them, only the `weighted_upstream` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -184,10 +184,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } @@ -205,7 +205,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. -Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weighted_upstreams`, the flow is divided into 4:2. Among them, only the `weighted_upstreams` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weighted_upstream`, the flow is divided into 4:2. Among them, only the `weighted_upstream` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -238,10 +238,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 3bf260644808..f300358bdfb7 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -52,21 +52,21 @@ | rules.upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | | rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | | rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | -| rules.upstreams.weighted_upstreams | integer | 可选 | weighted_upstreams = 1 | | 根据 `weighted_upstreams` 值做流量划分,多个 weighted_upstreams 之间使用 roundrobin 算法划分。| +| rules.upstreams.weighted_upstream | integer | 可选 | weighted_upstream = 1 | | 根据 `weighted_upstream` 值做流量划分,多个 weighted_upstream 之间使用 roundrobin 算法划分。| ## 如何启用 -在插件的 upstreams 中只有 `weighted_upstreams` 值,表示到达默认 `route` 上的 upstream 流量权重值。 +在插件的 upstreams 中只有 `weighted_upstream` 值,表示到达默认 `route` 上的 upstream 流量权重值。 ```json { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ``` ### 灰度发布 -根据插件中 upstreams 配置的 `weighted_upstreams` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 +根据插件中 upstreams 配置的 `weighted_upstream` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -90,10 +90,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } @@ -156,7 +156,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 `match` 中可以设置多个匹配规则,`vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过。 -示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weighted_upstreams` 值将流量按 4:2 划分。其中只有 `weighted_upstreams` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weighted_upstream` 值将流量按 4:2 划分。其中只有 `weighted_upstream` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -184,10 +184,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } @@ -205,7 +205,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 -示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weighted_upstreams` 值将流量按 4:2 划分。其中只有 `weighted_upstreams` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weighted_upstream` 值将流量按 4:2 划分。其中只有 `weighted_upstream` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -238,10 +238,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstreams": 4 + "weighted_upstream": 4 }, { - "weighted_upstreams": 2 + "weighted_upstream": 2 } ] } diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 8e4e593b89e0..68273ad8dc14 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -55,7 +55,7 @@ __DATA__ nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstreams = 2 + weighted_upstream = 2 }, { upstream = { @@ -64,10 +64,10 @@ __DATA__ nodes = {["127.0.0.1:1982"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstreams = 2 + weighted_upstream = 2 }, { - weighted_upstreams = 1 + weighted_upstream = 1 } } } @@ -105,10 +105,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstreams = 2 + weighted_upstream = 2 }, { - weighted_upstreams = 1 + weighted_upstream = 1 } } } @@ -153,10 +153,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstreams = 2 + weighted_upstream = 2 }, { - weighted_upstreams = 1 + weighted_upstream = 1 } } } @@ -324,10 +324,10 @@ GET /t "upstreams": [ { "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, - "weighted_upstreams": 2 + "weighted_upstream": 2 }, { - "weighted_upstreams": 1 + "weighted_upstream": 1 } ] } @@ -412,9 +412,9 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2}, - {"weighted_upstreams": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2}, + {"weighted_upstream": 1} ] } ] @@ -498,8 +498,8 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2} ] } ] @@ -570,7 +570,7 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weighted_upstreams": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weighted_upstream": 1} ] } ] @@ -637,10 +637,10 @@ GET /t "foo.com:80": 0 } }, - "weighted_upstreams": 2 + "weighted_upstream": 2 }, { - "weighted_upstreams": 1 + "weighted_upstream": 1 } ] } @@ -701,10 +701,10 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ "127.0.0.1:1981":1 } }, - "weighted_upstreams": 2 + "weighted_upstream": 2 }, { - "weighted_upstreams": 1 + "weighted_upstream": 1 } ] } @@ -884,9 +884,9 @@ GET /t } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstreams": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstreams": 2}, - {"weighted_upstreams": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2}, + {"weighted_upstream": 1} ] } ] From 3126c9ea0d9a3ae3257d594bef1bcd73db6722f0 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 21 Dec 2020 20:38:07 +0800 Subject: [PATCH 10/30] fix ci run error. --- t/plugin/traffic-split.t | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 68273ad8dc14..c8fdbfafa32b 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -307,7 +307,7 @@ GET /t local t = require("lib.test_admin").test local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, - [[{ + [=[{ "uri": "/server_port", "plugins": { "traffic-split": { @@ -315,10 +315,7 @@ GET /t { "match": [ { - "vars": [ - ["arg_name", "==", "jack"], - ["arg_age", "!","<", "16"] - ] + "vars": [["arg_name", "==", "jack"],["arg_age", "!","<", "16"]] } ], "upstreams": [ @@ -340,7 +337,7 @@ GET /t "127.0.0.1:1980": 1 } } - }]] + }]=] ) if code >= 300 then ngx.status = code @@ -404,12 +401,8 @@ GET /t "rules": [ { "match": [ - { - "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] - }, - { - "vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]] - } + {"vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]]}, + {"vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]]} ], "upstreams": [ {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, @@ -490,12 +483,8 @@ GET /t "rules": [ { "match": [ - { - "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] - }, - { - "vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]] - } + {"vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]]}, + {"vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]]} ], "upstreams": [ {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, @@ -764,7 +753,7 @@ GET /t local t = require("lib.test_admin").test local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, - [[{ + [=[{ "uri": "/server_port", "plugins": { "traffic-split": { @@ -772,9 +761,7 @@ GET /t { "match": [ { - "vars": [ - [ "http_release","==","blue" ] - ] + "vars": [["http_release","==","blue"]] } ], "upstreams": [ @@ -798,7 +785,7 @@ GET /t "127.0.0.1:1980": 1 } } - }]] + }]=] ) if code >= 300 then ngx.status = code @@ -840,7 +827,7 @@ GET /t -=== TEST 24: release is equal to `green` +=== TEST 24: release is equal to `green` --- config location /t { content_by_lua_block { From 371f80665a9a1d5d8616aa1ae7204c67feca540b Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 21 Dec 2020 21:58:29 +0800 Subject: [PATCH 11/30] update README files content. --- README.md | 2 +- README_CN.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c2d516d6597..abd7a4081229 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against - [Health Checks](doc/health-check.md): Enable health check on the upstream node, and will automatically filter unhealthy nodes during load balancing to ensure system stability. - Circuit-Breaker: Intelligent tracking of unhealthy upstream services. - [Proxy Mirror](doc/plugins/proxy-mirror.md): Provides the ability to mirror client requests. - - [Traffic Split](doc/plugins/traffic-split.md): Supports dividing the request traffic according to the specified proportional relationship. + - [Traffic Split](doc/plugins/traffic-split.md): Support the functions of gray release, blue-green release and custom release. - **Fine-grained routing** diff --git a/README_CN.md b/README_CN.md index 275dd546ad5b..63a506773844 100644 --- a/README_CN.md +++ b/README_CN.md @@ -97,6 +97,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵 - [健康检查](doc/zh-cn/health-check.md):启用上游节点的健康检查,将在负载均衡期间自动过滤不健康的节点,以确保系统稳定性。 - 熔断器: 智能跟踪不健康上游服务。 - [代理镜像](doc/zh-cn/plugins/proxy-mirror.md): 提供镜像客户端请求的能力。 + - [流量拆分](doc/zh-cn/plugins/traffic-split.md): 支持灰度发布、蓝绿发布和自定义发布的功能。 - **精细化路由** From b8f18d6a99ebaeb5b67d5f226b9ce2b663312f5c Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 21 Dec 2020 23:12:43 +0800 Subject: [PATCH 12/30] rerun ci. --- conf/config-default.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 7079b58aae3c..1029da4a74b4 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -152,6 +152,7 @@ nginx_config: # config for render the template to generate n stream_configuration_snippet: | # Add custom Nginx stream configuration to nginx.conf. # The configuration should be well indented! + http: enable_access_log: true # enable access log or not, default true access_log: "logs/access.log" @@ -244,7 +245,7 @@ plugins: # plugin list (sorted in alphabetical order) - uri-blocker - wolf-rbac - zipkin - #- server-info + # - server-info - traffic-split stream_plugins: From e5e04144919d997bc5ac2eb7f87e8eb8b8aa6efa Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Wed, 23 Dec 2020 15:37:05 +0800 Subject: [PATCH 13/30] upstream nodes support array types. --- apisix/plugins/traffic-split.lua | 68 +++++++---- t/plugin/traffic-split.t | 203 ++++++++++++++++++++++++++++++- 2 files changed, 244 insertions(+), 27 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index ed2f5e0428ec..4cd0c726cfd4 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -171,35 +171,51 @@ local function parse_domain_for_node(node) end +local function set_pass_host(ctx, upstream_info, host) + -- Currently only supports a single upstream of the domain name. + -- When the upstream is `IP`, do not do any `pass_host` operation. + if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) then + local pass_host = upstream_info.pass_host or "pass" + if pass_host == "pass" then + ctx.var.upstream_host = ctx.var.host + return + end + + if pass_host == "rewrite" then + ctx.var.upstream_host = upstream_info.upstream_host + return + end + + ctx.var.upstream_host = host + return + end + + return +end + + local function set_upstream(upstream_info, ctx) local nodes = upstream_info.nodes local new_nodes = {} - for addr, weight in pairs(nodes) do - local node = {} - local ip, port, host - host, port = core.utils.parse_addr(addr) - ip = parse_domain_for_node(host) - node.host = ip - node.port = port - node.weight = weight - table_insert(new_nodes, node) - - -- Currently only supports a single upstream of the domain name. - -- When the upstream is `IP`, do not do any `pass_host` operation. - if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) then - local pass_host = upstream_info.pass_host or "pass" - if pass_host == "pass" then - ctx.var.upstream_host = ctx.var.host - break - end - - if pass_host == "rewrite" then - ctx.var.upstream_host = upstream_info.upstream_host - break - end - - ctx.var.upstream_host = host - break + if core.table.isarray(nodes) then + for _, node in ipairs(nodes) do + set_pass_host(ctx, upstream_info, node.host) + node.host = parse_domain_for_node(node.host) + node.port = node.port + node.weight = node.weight + table_insert(new_nodes, node) + end + else + for addr, weight in pairs(nodes) do + local node = {} + local ip, port, host + host, port = core.utils.parse_addr(addr) + set_pass_host(ctx, upstream_info, host) + ip = parse_domain_for_node(host) + node.host = ip + node.port = port + node.weight = weight + table_insert(new_nodes, node) end end core.log.info("upstream_host: ", ctx.var.upstream_host) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index c8fdbfafa32b..9f8a94cf8fbb 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -927,7 +927,7 @@ GET /t -=== TEST 27: `match` rule failed, `age` condition did not match +=== TEST 27: `match` rule failed, `age` condition did not match --- config location /t { content_by_lua_block { @@ -949,3 +949,204 @@ GET /t 1980, 1980, 1980, 1980, 1980, 1980 --- no_error_log [error] + + + +=== TEST 28: upstreams nodes are array type and node is the domain name +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"foo.com", "port": 80, "weight": 0}]}, "weighted_upstream": 2} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 29: domain name resolved successfully +--- request +GET /server_port +--- error_code: 502 +--- error_log eval +qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ + + + +=== TEST 30: the nodes of upstreams are array type, with multiple nodes +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}, {"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2}, + {"weighted_upstream": 1} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 31: `match` rule passed +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["appkey"] = "hello" + for i = 1, 5 do + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981, 1981, 1982, 1982 +--- no_error_log +[error] + + + +=== TEST 32: the upstream node is an array type and has multiple upstreams +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] + } + ], + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2}, + {"weighted_upstream": 1} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 33: `match` rule passed +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + local headers = {} + headers["appkey"] = "hello" + for i = 1, 5 do + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981, 1981, 1982, 1982 +--- no_error_log +[error] From b2b6701d795c0784f86ea06ca3e056f379caa591 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Wed, 23 Dec 2020 18:00:25 +0800 Subject: [PATCH 14/30] code style. --- apisix/plugins/traffic-split.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 4cd0c726cfd4..8a76e87e9f98 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -156,7 +156,9 @@ end local function parse_domain_for_node(node) - if not ipmatcher.parse_ipv4(node) and not ipmatcher.parse_ipv6(node) then + if not ipmatcher.parse_ipv4(node) + and not ipmatcher.parse_ipv6(node) + then local ip, err = init.parse_domain(node) if ip then return ip @@ -174,7 +176,9 @@ end local function set_pass_host(ctx, upstream_info, host) -- Currently only supports a single upstream of the domain name. -- When the upstream is `IP`, do not do any `pass_host` operation. - if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) then + if not core.utils.parse_ipv4(host) + and not core.utils.parse_ipv6(host) + then local pass_host = upstream_info.pass_host or "pass" if pass_host == "pass" then ctx.var.upstream_host = ctx.var.host @@ -247,8 +251,8 @@ local function new_rr_obj(upstreams) local server_list = {} for _, upstream_obj in ipairs(upstreams) do if not upstream_obj.upstream then - -- If the `upstream` object has only the `weighted_upstream` value, it means that - -- the `upstream` weight value on the default `route` has been reached. + -- If the `upstream` object has only the `weighted_upstream` value, it means + -- that the `upstream` weight value on the default `route` has been reached. -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" end From 727ef58d40ce2a02dd8558dd4ac634733141f96a Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Wed, 23 Dec 2020 18:38:44 +0800 Subject: [PATCH 15/30] fix unstable test cases. --- t/plugin/traffic-split.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 9f8a94cf8fbb..30bdca87702d 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -1023,7 +1023,7 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ } ], "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}, {"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}, {"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 4}, {"weighted_upstream": 1} ] } From 51ecca7b93a28fd43b7f5c03cdd41995ad36d136 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 15:52:10 +0800 Subject: [PATCH 16/30] fix doc desc and code style. --- README.md | 2 +- README_CN.md | 2 +- apisix/plugins/traffic-split.lua | 4 ++-- doc/README.md | 2 +- doc/plugins/traffic-split.md | 2 ++ doc/zh-cn/README.md | 2 +- doc/zh-cn/plugins/traffic-split.md | 4 +++- t/plugin/traffic-split.t | 2 +- 8 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index abd7a4081229..062d38b29a10 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against - [Health Checks](doc/health-check.md): Enable health check on the upstream node, and will automatically filter unhealthy nodes during load balancing to ensure system stability. - Circuit-Breaker: Intelligent tracking of unhealthy upstream services. - [Proxy Mirror](doc/plugins/proxy-mirror.md): Provides the ability to mirror client requests. - - [Traffic Split](doc/plugins/traffic-split.md): Support the functions of gray release, blue-green release and custom release. + - [Traffic Split](doc/plugins/traffic-split.md): Allows users to incrementally direct percentages of traffic between various upstreams. - **Fine-grained routing** diff --git a/README_CN.md b/README_CN.md index 63a506773844..6ca2fec9dcc2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -97,7 +97,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵 - [健康检查](doc/zh-cn/health-check.md):启用上游节点的健康检查,将在负载均衡期间自动过滤不健康的节点,以确保系统稳定性。 - 熔断器: 智能跟踪不健康上游服务。 - [代理镜像](doc/zh-cn/plugins/proxy-mirror.md): 提供镜像客户端请求的能力。 - - [流量拆分](doc/zh-cn/plugins/traffic-split.md): 支持灰度发布、蓝绿发布和自定义发布的功能。 + - [流量拆分](doc/zh-cn/plugins/traffic-split.md): 允许用户逐步控制各个上游之间的流量百分比。 - **精细化路由** diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 8a76e87e9f98..8411b5071a06 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -269,11 +269,12 @@ function _M.access(conf, ctx) end local upstreams, match_flag - for _, rule in pairs(conf.rules) do + for _, rule in ipairs(conf.rules) do match_flag = true for _, single_match in ipairs(rule.match) do local expr, err = expr.new(single_match.vars) if err then + core.log.error("vars expression does not match: ", err) return 500, err end @@ -288,7 +289,6 @@ function _M.access(conf, ctx) break end end - core.log.info("match_flag: ", match_flag) if not match_flag then diff --git a/doc/README.md b/doc/README.md index 75db69129663..5dcb52c75a35 100644 --- a/doc/README.md +++ b/doc/README.md @@ -81,7 +81,7 @@ * [request-validation](plugins/request-validation.md): Validates requests before forwarding to upstream. * [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests. * [api-breaker](plugins/api-breaker.md): Circuit Breaker for API that stops requests forwarding to upstream in case of unhealthy state. -* [traffic-split](plugins/traffic-split.md): The traffic division plug-in divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plug-in, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. +* [traffic-split](plugins/traffic-split.md): The traffic split plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plug-in, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. ### Monitoring diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index e61413bc540a..439ea54568cc 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -37,6 +37,8 @@ The traffic division plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. +Note: Since the selection of different upstreams in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. + ## Attributes | Name | Type | Requirement | Default | Valid | Description | diff --git a/doc/zh-cn/README.md b/doc/zh-cn/README.md index 441bcb1aaa1b..465ac1bdc43e 100644 --- a/doc/zh-cn/README.md +++ b/doc/zh-cn/README.md @@ -81,7 +81,7 @@ * [request-validation](plugins/request-validation.md): 请求验证。 * [proxy-mirror](plugins/proxy-mirror.md):代理镜像插件提供镜像客户端请求的能力。 * [api-breaker](plugins/api-breaker.md): API的断路器,在状态不正常的情况下停止将请求转发到上游。 -* [traffic-split](plugins/traffic-split.md): 请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 +* [traffic-split](plugins/traffic-split.md): 请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现灰度发布、蓝绿发布和自定义发布功能。 ### Monitoring diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index f300358bdfb7..59d76d869d2a 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -35,7 +35,9 @@ ## 名字 -请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现 灰度发布、蓝绿发布和自定义发布功能。 +请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现灰度发布、蓝绿发布和自定义发布功能。 + +注:由于插件中选择不同上游是根据 roundrobin 算法选择,因此存在算法状态重置的情况下,会对流量分配的比率并不完全精准。 ## 属性 diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 30bdca87702d..3aac80069873 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -924,7 +924,7 @@ GET /t 1980, 1981, 1981, 1982, 1982 --- no_error_log [error] - +--- LAST === TEST 27: `match` rule failed, `age` condition did not match From a37d7549208da1d7e6f76b1359c73cdf9bc02e8f Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 15:55:31 +0800 Subject: [PATCH 17/30] delete `---LAST` of test cases. --- t/plugin/traffic-split.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 3aac80069873..30bdca87702d 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -924,7 +924,7 @@ GET /t 1980, 1981, 1981, 1982, 1982 --- no_error_log [error] ---- LAST + === TEST 27: `match` rule failed, `age` condition did not match From 0b84c2e625eedf72c5e82f67a20ead9877b4b725 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 17:21:55 +0800 Subject: [PATCH 18/30] fix: the upstream key is not unique --- apisix/plugins/traffic-split.lua | 14 ++++-- t/plugin/traffic-split.t | 84 ++++++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 8411b5071a06..49aaeae7351f 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -241,21 +241,29 @@ local function set_upstream(upstream_info, ctx) end local matched_route = ctx.matched_route - upstream.set(ctx, up_conf.type .. "#route_" .. matched_route.value.id, - ctx.conf_version, up_conf, matched_route) + local upstream_key = up_conf.type .. "#route_" .. + matched_route.value.id .. "_" ..upstream_info.vid + core.log.info("upstream_key: ", upstream_key) + upstream.set(ctx, upstream_key, ctx.conf_version, up_conf, matched_route) + return end local function new_rr_obj(upstreams) local server_list = {} - for _, upstream_obj in ipairs(upstreams) do + for i, upstream_obj in ipairs(upstreams) do if not upstream_obj.upstream then -- If the `upstream` object has only the `weighted_upstream` value, it means -- that the `upstream` weight value on the default `route` has been reached. -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" end + + if type(upstream_obj.upstream) == "table" then + -- Add a virtual id field to uniquely identify the upstream `key`. + upstream_obj.upstream.vid = i + end server_list[upstream_obj.upstream] = upstream_obj.weighted_upstream end diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 30bdca87702d..8a0aabef24a1 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -908,10 +908,8 @@ location /t { content_by_lua_block { local t = require("lib.test_admin").test local bodys = {} - local headers = {} - headers["appkey"] = "hello" for i = 1, 5 do - local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET) bodys[i] = body end table.sort(bodys) @@ -1059,10 +1057,8 @@ location /t { content_by_lua_block { local t = require("lib.test_admin").test local bodys = {} - local headers = {} - headers["appkey"] = "hello" for i = 1, 5 do - local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET) bodys[i] = body end table.sort(bodys) @@ -1134,10 +1130,8 @@ location /t { content_by_lua_block { local t = require("lib.test_admin").test local bodys = {} - local headers = {} - headers["appkey"] = "hello" for i = 1, 5 do - local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET, "", nil, headers) + local _, _, body = t('/server_port?name=jack&age=36', ngx.HTTP_GET) bodys[i] = body end table.sort(bodys) @@ -1150,3 +1144,75 @@ GET /t 1980, 1981, 1981, 1982, 1982 --- no_error_log [error] + + + +=== TEST 34: multiple upstream +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weighted_upstream": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 35: the upstream `key` is unique +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 2 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1981, 1982 +--- grep_error_log eval +qr/upstream_key: roundrobin#route_1_\d/ +--- grep_error_log_out +upstream_key: roundrobin#route_1_1 +upstream_key: roundrobin#route_1_2 +--- no_error_log +[error] From 13057ae537c5860da91c1cd689997dffde2896bc Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 17:31:13 +0800 Subject: [PATCH 19/30] fix global "type". --- apisix/plugins/traffic-split.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 49aaeae7351f..2accfda76bb0 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -23,6 +23,7 @@ local ipmatcher = require("resty.ipmatcher") local expr = require("resty.expr.v1") local pairs = pairs local ipairs = ipairs +local type = type local table_insert = table.insert local lrucache = core.lrucache.new({ From 84399e879064d79d49218c5946516e633912a507 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 19:54:18 +0800 Subject: [PATCH 20/30] add more test case and update schema field. --- apisix/plugins/traffic-split.lua | 20 +++--- doc/plugins/traffic-split.md | 52 +++++++------- doc/zh-cn/plugins/traffic-split.md | 50 +++++++------- t/plugin/traffic-split.t | 107 +++++++++++++++-------------- 4 files changed, 115 insertions(+), 114 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 2accfda76bb0..9973405e809b 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -99,7 +99,7 @@ local upstreams_schema = { properties = { upstream_id = schema_def.id_schema, -- todo: support upstream_id method upstream = schema_def.upstream, - weighted_upstream = { + weight = { description = "used to split traffic between different" .. "upstreams for plugin configuration", type = "integer", @@ -112,7 +112,7 @@ local upstreams_schema = { -- the upstream of `route` is used by default. default = { { - weighted_upstream = 1 + weight = 1 } }, minItems = 1, @@ -129,7 +129,7 @@ local schema = { type = "object", properties = { match = match_schema, - upstreams = upstreams_schema + weighted_upstreams = upstreams_schema } } } @@ -251,11 +251,11 @@ local function set_upstream(upstream_info, ctx) end -local function new_rr_obj(upstreams) +local function new_rr_obj(weighted_upstreams) local server_list = {} - for i, upstream_obj in ipairs(upstreams) do + for i, upstream_obj in ipairs(weighted_upstreams) do if not upstream_obj.upstream then - -- If the `upstream` object has only the `weighted_upstream` value, it means + -- If the `upstream` object has only the `weight` value, it means -- that the `upstream` weight value on the default `route` has been reached. -- Need to set an identifier to mark the empty upstream. upstream_obj.upstream = "empty_upstream" @@ -265,7 +265,7 @@ local function new_rr_obj(upstreams) -- Add a virtual id field to uniquely identify the upstream `key`. upstream_obj.upstream.vid = i end - server_list[upstream_obj.upstream] = upstream_obj.weighted_upstream + server_list[upstream_obj.upstream] = upstream_obj.weight end return roundrobin:new(server_list) @@ -277,7 +277,7 @@ function _M.access(conf, ctx) return end - local upstreams, match_flag + local weighted_upstreams, match_flag for _, rule in ipairs(conf.rules) do match_flag = true for _, single_match in ipairs(rule.match) do @@ -294,7 +294,7 @@ function _M.access(conf, ctx) end if match_flag then - upstreams = rule.upstreams + weighted_upstreams = rule.weighted_upstreams break end end @@ -304,7 +304,7 @@ function _M.access(conf, ctx) return end - local rr_up, err = lrucache(upstreams, nil, new_rr_obj, upstreams) + local rr_up, err = lrucache(weighted_upstreams, nil, new_rr_obj, weighted_upstreams) if not rr_up then core.log.error("lrucache roundrobin failed: ", err) return 500 diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 439ea54568cc..da980ee6b4f7 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -37,7 +37,7 @@ The traffic division plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. -Note: Since the selection of different upstreams in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. +Note: Since the selection of different upstream in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. ## Attributes @@ -45,30 +45,30 @@ Note: Since the selection of different upstreams in the plugin is based on the r | ---------------- | ------- | ----------- | ------- | ------- | ---------------------------------------------------------------------------------------- | | rules.match | array[object] | optional | | | List of matching rules. | | rules.match.vars | array[array] | optional | | | A list consisting of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | -| rules.upstreams | array[object] | optional | | | List of upstream configuration rules. | -| rules.upstreams.upstream_id | string or integer | optional | | | The upstream id is bound to the corresponding upstream(not currently supported). | -| rules.upstreams.upstream | object | optional | | | Upstream configuration information. | -| rules.upstreams.upstream.type | enum | optional | roundrobin | [roundrobin, chash] | roundrobin supports weighted load, chash consistent hashing, the two are alternatives. | -| rules.upstreams.upstream.nodes | object | optional | | | In the hash table, the key of the internal element is the list of upstream machine addresses, in the format of address + Port, where the address part can be an IP or a domain name, such as 192.168.1.100:80, foo.com:80, etc. value is the weight of the node. In particular, when the weight value is 0, it has special meaning, which usually means that the upstream node is invalid and never wants to be selected. | -| rules.upstreams.upstream.timeout | object | optional | 15 | | Set the timeout period for connecting, sending and receiving messages (time unit: second, all default to 15 seconds). | -| rules.upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | -| rules.upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | -| rules.upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | -| rules.upstreams.weighted_upstream | integer | optional | weighted_upstream = 1 | | The traffic is divided according to the `weighted_upstream` value, and the roundrobin algorithm is used to divide multiple `weighted_upstream`. | +| rules.weighted_upstreams | array[object] | optional | | | List of upstream configuration rules. | +| rules.weighted_upstreams.upstream_id | string or integer | optional | | | The upstream id is bound to the corresponding upstream(not currently supported). | +| rules.weighted_upstreams.upstream | object | optional | | | Upstream configuration information. | +| rules.weighted_upstreams.upstream.type | enum | optional | roundrobin | [roundrobin, chash] | roundrobin supports weighted load, chash consistent hashing, the two are alternatives. | +| rules.weighted_upstreams.upstream.nodes | object | optional | | | In the hash table, the key of the internal element is the list of upstream machine addresses, in the format of address + Port, where the address part can be an IP or a domain name, such as 192.168.1.100:80, foo.com:80, etc. value is the weight of the node. In particular, when the weight value is 0, it has special meaning, which usually means that the upstream node is invalid and never wants to be selected. | +| rules.weighted_upstreams.upstream.timeout | object | optional | 15 | | Set the timeout period for connecting, sending and receiving messages (time unit: second, all default to 15 seconds). | +| rules.weighted_upstreams.upstream.pass_host | enum | optional | "pass" | ["pass", "node", "rewrite"] | pass: pass the host requested by the client, node: pass the host requested by the client; use the host configured with the upstream node, rewrite: rewrite the host with the value configured by the upstream_host. | +| rules.weighted_upstreams.upstream.name | string | optional | | | Identify the upstream service name, usage scenario, etc. | +| rules.weighted_upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | +| rules.weighted_upstreams.weight | integer | optional | weight = 1 | | The traffic is divided according to the `weight` value, and the roundrobin algorithm is used to divide multiple `weight`. | ## How To Enable -There is only the value of `weighted_upstream` in the upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. +There is only the value of `weight` in the weighted_upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. ```json { - "weighted_upstream": 2 + "weight": 2 } ``` ### Grayscale Release -Traffic is split according to the `weighted_upstream` value configured by upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. +Traffic is split according to the `weight` value configured by weighted_upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -78,7 +78,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "traffic-split": { "rules": [ { - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -92,10 +92,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } @@ -130,7 +130,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -158,7 +158,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 Multiple matching rules can be set in `match`, multiple expressions in `vars` are in the relationship of `add`, and multiple `vars` rules are in the relationship of `or`; as long as one of the vars rules is passed, then Indicates that `match` passed. -Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weighted_upstream`, the flow is divided into 4:2. Among them, only the `weighted_upstream` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -177,7 +177,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -186,10 +186,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } @@ -207,7 +207,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. -Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weighted_upstream`, the flow is divided into 4:2. Among them, only the `weighted_upstream` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -231,7 +231,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -240,10 +240,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 59d76d869d2a..47c0f1903230 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -45,30 +45,30 @@ | ------------ | ------------- | ------ | ------ | ------ | -------------------- | | rules.match | array[object] | 可选 | | | 匹配规则列表 | | rules.match.vars | array[array] | 可选 | | | 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | -| rules.upstreams | array[object] | 可选 | | | 上游配置规则列表。 | -| rules.upstreams.upstream_id | string or integer | 可选 | | | 通过上游 id 绑定对应上游(暂不支持)。 | -| rules.upstreams.upstream | object | 可选 | | | 上游配置信息。 | -| rules.upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的(目前只支持 `roundrobin`)。 | -| rules.upstreams.upstream.nodes | object | 可选 | | | 哈希表,内部元素的 key 是上游机器地址 列表,格式为地址 + Port,其中地址部 分可以是 IP 也可以是域名,⽐如 192.168.1.100:80、foo.com:80等。 value 则是节点的权重,特别的,当权重 值为 0 有特殊含义,通常代表该上游节点 失效,永远不希望被选中。 | -| rules.upstreams.upstream.timeout | object | 可选 | 15 | | 设置连接、发送消息、接收消息的超时时间(时间单位:秒,都默认为 15 秒)。 | -| rules.upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | -| rules.upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | -| rules.upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | -| rules.upstreams.weighted_upstream | integer | 可选 | weighted_upstream = 1 | | 根据 `weighted_upstream` 值做流量划分,多个 weighted_upstream 之间使用 roundrobin 算法划分。| +| rules.weighted_upstreams | array[object] | 可选 | | | 上游配置规则列表。 | +| rules.weighted_upstreams.upstream_id | string or integer | 可选 | | | 通过上游 id 绑定对应上游(暂不支持)。 | +| rules.weighted_upstreams.upstream | object | 可选 | | | 上游配置信息。 | +| rules.weighted_upstreams.upstream.type | enum | 可选 | roundrobin | [roundrobin, chash] | roundrobin 支持权重的负载,chash 一致性哈希,两者是二选一的(目前只支持 `roundrobin`)。 | +| rules.weighted_upstreams.upstream.nodes | object | 可选 | | | 哈希表,内部元素的 key 是上游机器地址 列表,格式为地址 + Port,其中地址部 分可以是 IP 也可以是域名,⽐如 192.168.1.100:80、foo.com:80等。 value 则是节点的权重,特别的,当权重 值为 0 有特殊含义,通常代表该上游节点 失效,永远不希望被选中。 | +| rules.weighted_upstreams.upstream.timeout | object | 可选 | 15 | | 设置连接、发送消息、接收消息的超时时间(时间单位:秒,都默认为 15 秒)。 | +| rules.weighted_upstreams.upstream.pass_host | enum | 可选 | "pass" | ["pass", "node", "rewrite"] | pass: 透传客户端请求的 host, node: 不透传客户端请求的 host; 使用 upstream node 配置的 host, rewrite: 使用 upstream_host 配置的值重写 host 。 | +| rules.weighted_upstreams.upstream.name | string | 可选 | | | 标识上游服务名称、使⽤场景等。 | +| rules.weighted_upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | +| rules.weighted_upstreams.weight | integer | 可选 | weight = 1 | | 根据 `weight` 值做流量划分,多个 weight 之间使用 roundrobin 算法划分。| ## 如何启用 -在插件的 upstreams 中只有 `weighted_upstream` 值,表示到达默认 `route` 上的 upstream 流量权重值。 +在插件的 weighted_upstreams 中只有 `weight` 值,表示到达默认 `route` 上的 upstream 流量权重值。 ```json { - "weighted_upstream": 2 + "weight": 2 } ``` ### 灰度发布 -根据插件中 upstreams 配置的 `weighted_upstream` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 +根据插件中 weighted_upstreams 配置的 `weight` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -78,7 +78,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "traffic-split": { "rules": [ { - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -92,10 +92,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } @@ -130,7 +130,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -158,7 +158,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 `match` 中可以设置多个匹配规则,`vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过。 -示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weighted_upstream` 值将流量按 4:2 划分。其中只有 `weighted_upstream` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -177,7 +177,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -186,10 +186,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } @@ -207,7 +207,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 -示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weighted_upstream` 值将流量按 4:2 划分。其中只有 `weighted_upstream` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -231,7 +231,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 ] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -240,10 +240,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weighted_upstream": 4 + "weight": 4 }, { - "weighted_upstream": 2 + "weight": 2 } ] } diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 8a0aabef24a1..dd1f4166cb15 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -47,7 +47,7 @@ __DATA__ } } }, - upstreams = { + weighted_upstreams = { { upstream = { name = "upstream_A", @@ -55,7 +55,7 @@ __DATA__ nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstream = 2 + weight = 2 }, { upstream = { @@ -64,10 +64,10 @@ __DATA__ nodes = {["127.0.0.1:1982"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstream = 2 + weight = 2 }, { - weighted_upstream = 1 + weight = 1 } } } @@ -97,7 +97,7 @@ done local ok, err = plugin.check_schema({ rules = { { - upstreams = { + weighted_upstreams = { { upstream = { name = "upstream_A", @@ -105,10 +105,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstream = 2 + weight = 2 }, { - weighted_upstream = 1 + weight = 1 } } } @@ -145,7 +145,7 @@ done } } }, - upstreams = { + weighted_upstreams = { { upstream = { name = "upstream_A", @@ -153,10 +153,10 @@ done nodes = {["127.0.0.1:1981"]=2}, timeout = {connect = 15, send = 15, read = 15} }, - weighted_upstream = 2 + weight = 2 }, { - weighted_upstream = 1 + weight = 1 } } } @@ -236,7 +236,7 @@ GET /t -=== TEST 6: when `upstreams` is empty, the upstream of `route` is used by default +=== TEST 6: when `weighted_upstreams` is empty, the upstream of `route` is used by default --- config location /t { content_by_lua_block { @@ -249,7 +249,7 @@ GET /t "traffic-split": { "rules": [ { - "upstreams": [{}] + "weighted_upstreams": [{}] } ] } @@ -318,13 +318,13 @@ GET /t "vars": [["arg_name", "==", "jack"],["arg_age", "!","<", "16"]] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, - "weighted_upstream": 2 + "weight": 2 }, { - "weighted_upstream": 1 + "weight": 1 } ] } @@ -404,10 +404,10 @@ GET /t {"vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]]}, {"vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]]} ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2}, - {"weighted_upstream": 1} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weight": 2}, + {"weight": 1} ] } ] @@ -486,9 +486,9 @@ GET /t {"vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]]}, {"vars": [["arg_name2", "in", ["jack", "rose"]], ["arg_age2", "!", "<", 18]]} ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weight": 2} ] } ] @@ -558,8 +558,8 @@ GET /t "vars": [["arg_name", "==", "jack"], ["arg_age", "~~", "^[1-9]{1,2}"]] } ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weighted_upstream": 1} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1980":1, "127.0.0.1:1981":2, "127.0.0.1:1982":2}, "timeout": {"connect": 15, "send": 15, "read": 15}}, "weight": 1} ] } ] @@ -617,7 +617,7 @@ GET /t "traffic-split": { "rules": [ { - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -626,10 +626,10 @@ GET /t "foo.com:80": 0 } }, - "weighted_upstream": 2 + "weight": 2 }, { - "weighted_upstream": 1 + "weight": 1 } ] } @@ -681,7 +681,7 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ "traffic-split": { "rules": [ { - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -690,10 +690,10 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ "127.0.0.1:1981":1 } }, - "weighted_upstream": 2 + "weight": 2 }, { - "weighted_upstream": 1 + "weight": 1 } ] } @@ -764,7 +764,7 @@ GET /t "vars": [["http_release","==","blue"]] } ], - "upstreams": [ + "weighted_upstreams": [ { "upstream": { "name": "upstream_A", @@ -870,10 +870,10 @@ GET /t "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] } ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weighted_upstream": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weighted_upstream": 2}, - {"weighted_upstream": 1} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": {"127.0.0.1:1981":20}}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": {"127.0.0.1:1982":10}}, "weight": 2}, + {"weight": 1} ] } ] @@ -950,7 +950,7 @@ GET /t -=== TEST 28: upstreams nodes are array type and node is the domain name +=== TEST 28: upstream nodes are array type and node is the domain name --- config location /t { content_by_lua_block { @@ -963,8 +963,8 @@ GET /t "traffic-split": { "rules": [ { - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"foo.com", "port": 80, "weight": 0}]}, "weighted_upstream": 2} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"foo.com", "port": 80, "weight": 0}]}, "weight": 2} ] } ] @@ -1002,7 +1002,7 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ -=== TEST 30: the nodes of upstreams are array type, with multiple nodes +=== TEST 30: the nodes of upstream are array type, with multiple nodes --- config location /t { content_by_lua_block { @@ -1020,9 +1020,9 @@ qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] } ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}, {"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 4}, - {"weighted_upstream": 1} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}, {"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weight": 4}, + {"weight": 1} ] } ] @@ -1074,7 +1074,7 @@ GET /t -=== TEST 32: the upstream node is an array type and has multiple upstreams +=== TEST 32: the upstream node is an array type and has multiple upstream --- config location /t { content_by_lua_block { @@ -1092,10 +1092,10 @@ GET /t "vars": [["arg_name", "==", "jack"], ["arg_age", ">", "23"],["http_appkey", "~~", "[a-z]{1,5}"]] } ], - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weighted_upstream": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2}, - {"weighted_upstream": 1} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weight": 2}, + {"weight": 1} ] } ] @@ -1147,7 +1147,7 @@ GET /t -=== TEST 34: multiple upstream +=== TEST 34: multiple upstream and empty_upstream --- config location /t { content_by_lua_block { @@ -1160,9 +1160,10 @@ GET /t "traffic-split": { "rules": [ { - "upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weighted_upstream": 2}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weighted_upstream": 2} + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weight": 1}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weight": 1}, + {"weight": 1} ] } ] @@ -1197,7 +1198,7 @@ location /t { content_by_lua_block { local t = require("lib.test_admin").test local bodys = {} - for i = 1, 2 do + for i = 1, 3 do local _, _, body = t('/server_port', ngx.HTTP_GET) bodys[i] = body end @@ -1208,7 +1209,7 @@ location /t { --- request GET /t --- response_body -1981, 1982 +1980, 1981, 1982 --- grep_error_log eval qr/upstream_key: roundrobin#route_1_\d/ --- grep_error_log_out From 80683f5088166d6449c0b9821aaef2ae6e59c863 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 20:06:04 +0800 Subject: [PATCH 21/30] update test case. --- t/plugin/traffic-split.t | 82 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index dd1f4166cb15..e68bb6a84df1 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -1147,7 +1147,7 @@ GET /t -=== TEST 34: multiple upstream and empty_upstream +=== TEST 34: multi-upstream, test with unique upstream key --- config location /t { content_by_lua_block { @@ -1161,9 +1161,8 @@ GET /t "rules": [ { "weighted_upstreams": [ - {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weight": 1}, - {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weight": 1}, - {"weight": 1} + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weight": 2}, + {"upstream": {"name": "upstream_B", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1982, "weight": 2}]}, "weight": 2} ] } ] @@ -1198,7 +1197,7 @@ location /t { content_by_lua_block { local t = require("lib.test_admin").test local bodys = {} - for i = 1, 3 do + for i = 1, 2 do local _, _, body = t('/server_port', ngx.HTTP_GET) bodys[i] = body end @@ -1209,7 +1208,7 @@ location /t { --- request GET /t --- response_body -1980, 1981, 1982 +1981, 1982 --- grep_error_log eval qr/upstream_key: roundrobin#route_1_\d/ --- grep_error_log_out @@ -1217,3 +1216,74 @@ upstream_key: roundrobin#route_1_1 upstream_key: roundrobin#route_1_2 --- no_error_log [error] +--- LAST + + +=== TEST 36: has empty upstream, test the upstream key is unique +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "weighted_upstreams": [ + {"upstream": {"name": "upstream_A", "type": "roundrobin", "nodes": [{"host":"127.0.0.1", "port":1981, "weight": 2}]}, "weight": 1}, + {"weight": 1} + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 37: the upstream `key` is unique +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 2 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + bodys[i] = body + end + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- request +GET /t +--- response_body +1980, 1981 +--- grep_error_log eval +qr/upstream_key: roundrobin#route_1_\d/ +--- grep_error_log_out +upstream_key: roundrobin#route_1_1 +--- no_error_log +[error] From f752da80f039a3a69854158fac04b8355f2bfdda Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 21:00:56 +0800 Subject: [PATCH 22/30] update code style and doc --- apisix/plugins/traffic-split.lua | 4 ++-- doc/plugins/traffic-split.md | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 9973405e809b..d74b08c406b7 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -57,8 +57,8 @@ local vars_schema = { items = { anyOf = { { - type = "string", - minLength = 1, maxLength = 100 + type = "string", + minLength = 1, maxLength = 100 }, { type = "number" diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index da980ee6b4f7..8dc9214af68e 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -21,21 +21,21 @@ # Summary -- [**Name**](#name) -- [**Attributes**](#attributes) -- [**How To Enable**](#how-to-enable) - - [**Grayscale Release**](#grayscale-release) - - [**Blue-green Release**](#blue-green-release) - - [**Custom Release**](#custom-release) -- [**Test Plugin**](#test-plugin) - - [**Grayscale Test**](#grayscale-test) - - [**Blue-green Test**](#blue-green-test) - - [**Custom Test**](#custom-test) -- [**Disable Plugin**](#disable-plugin) + - [**Name**](#name) + - [**Attributes**](#attributes) + - [**How To Enable**](#how-to-enable) + - [**Grayscale Release**](#grayscale-release) + - [**Blue-green Release**](#blue-green-release) + - [**Custom Release**](#custom-release) + - [**Test Plugin**](#test-plugin) + - [**Grayscale Test**](#grayscale-test) + - [**Blue-green Test**](#blue-green-test) + - [**Custom Test**](#custom-test) + - [**Disable Plugin**](#disable-plugin) ## Name -The traffic division plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. +The traffic split plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. Note: Since the selection of different upstream in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. From 9fa9deda33b47094985d33cf828b725f6aeedaf8 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Thu, 24 Dec 2020 21:10:15 +0800 Subject: [PATCH 23/30] update test. --- t/plugin/traffic-split.t | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index e68bb6a84df1..88575289f494 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -14,12 +14,26 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::APISIX 'no_plan'; +master_on(); repeat_each(1); no_long_string(); no_root_location(); -log_level("info"); +no_shuffle(); run_tests; @@ -1216,7 +1230,7 @@ upstream_key: roundrobin#route_1_1 upstream_key: roundrobin#route_1_2 --- no_error_log [error] ---- LAST + === TEST 36: has empty upstream, test the upstream key is unique From f819400d67c716e0d9b8ce2ae25131dd7f356d21 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 01:46:19 +0800 Subject: [PATCH 24/30] refactored plugin docs. --- doc/README.md | 2 +- doc/plugins/traffic-split.md | 208 ++++++++++++++--------------- doc/zh-cn/README.md | 2 +- doc/zh-cn/plugins/traffic-split.md | 207 ++++++++++++++-------------- 4 files changed, 211 insertions(+), 208 deletions(-) diff --git a/doc/README.md b/doc/README.md index 5dcb52c75a35..050c0dd096bf 100644 --- a/doc/README.md +++ b/doc/README.md @@ -81,7 +81,7 @@ * [request-validation](plugins/request-validation.md): Validates requests before forwarding to upstream. * [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests. * [api-breaker](plugins/api-breaker.md): Circuit Breaker for API that stops requests forwarding to upstream in case of unhealthy state. -* [traffic-split](plugins/traffic-split.md): The traffic split plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plug-in, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. +* [traffic-split](plugins/traffic-split.md): Allows users to incrementally direct percentages of traffic between various upstreams. ### Monitoring diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 8dc9214af68e..441fe60981d4 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -21,21 +21,17 @@ # Summary - - [**Name**](#name) - - [**Attributes**](#attributes) - - [**How To Enable**](#how-to-enable) - - [**Grayscale Release**](#grayscale-release) - - [**Blue-green Release**](#blue-green-release) - - [**Custom Release**](#custom-release) - - [**Test Plugin**](#test-plugin) - - [**Grayscale Test**](#grayscale-test) - - [**Blue-green Test**](#blue-green-test) - - [**Custom Test**](#custom-test) - - [**Disable Plugin**](#disable-plugin) +- [**Name**](#name) +- [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) + - [**Grayscale Release**](#grayscale-release) + - [**Blue-green Release**](#blue-green-release) + - [**Custom Release**](#custom-release) +- [**Disable Plugin**](#disable-plugin) ## Name -The traffic split plugin divides the request traffic according to the specified ratio and diverts it to the corresponding upstream; through this plugin, gray-scale publishing, blue-green publishing and custom publishing functions can be realized. +The traffic split plugin allows users to incrementally direct percentages of traffic between various upstreams. Note: Since the selection of different upstream in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. @@ -58,7 +54,9 @@ Note: Since the selection of different upstream in the plugin is based on the ro ## How To Enable -There is only the value of `weight` in the weighted_upstreams of the plugin, which indicates the weight value of upstream traffic reaching the default `route`. +The traffic-split plugin is mainly composed of two parts: `match` and `weighted_upstreams`. `match` is a custom conditional rule, and `weighted_upstreams` is upstream information. When using the plugin, at least the `weighted_upstreams` part needs to be configured, so that the default `match` rule is passed, and the traffic ratio between each upstream will be gradually guided according to the `weight` value in `weighted_upstreams`. You can also configure `match` and `weighted_upstreams` at the same time, so that the traffic in `weighted_upstreams` will be divided only after the `match` rule is matched. + +>Note: 1. In `match`, the expression in vars is the relationship of `and`, and the relationship between multiple `vars` is the relationship of `or`. 2. There is only a `weight` value in the weighted_upstreams of the plugin, which means the upstream traffic weight value on the default `route`. Such as: ```json { @@ -66,9 +64,11 @@ There is only the value of `weight` in the weighted_upstreams of the plugin, whi } ``` +The following provides examples of plugin usage, which will help you understand the use of plugin. + ### Grayscale Release -Traffic is split according to the `weight` value configured by weighted_upstreams in the plugin (the rule of `match` is not configured, and `match` is passed by default). The request traffic is divided into 4:2, 2/3 of the traffic reaches the upstream of the `1981` port in the plugin, and 1/3 of the traffic reaches the upstream of the default `1980` port on the route. +Do not configure the `match` rule part (the `match` is passed by default), and the traffic is split according to the `weight` value of the weighted_upstreams configuration in the plugin. Divide `plugin upstream` and `route's upstream` request traffic according to 3:2, of which 60% of the traffic reaches the upstream of the `1981` port in the plugin, and 40% of the traffic reaches the upstream of the default `1980` port on the route . ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -92,7 +92,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -111,9 +111,29 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` +**Test plugin:** + +There are 5 requests, 3 requests hit the upstream of port 1981 of the plug-in, and 2 requests hit the upstream of port 1980 of `route`. + +```shell +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 + +hello 1980 + +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 + +world 1981 + +...... +``` + ### Blue-green Release -Get the blue and green conditions through the request header (you can also get through the request parameters or NGINX variables). After the `match` rule is matched, it means that all requests hit the upstream configured by the plugin, otherwise the request only hits the configuration on the route upstream. +Get the `match` rule parameter through the request header (you can also get it through the request parameter or NGINX variable). After the `match` rule is matched, it means that all requests hit the upstream configured by the plugin, otherwise the request only hits the `route` configured upstream. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -154,11 +174,35 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` +**Test plugin:** + +The rule of `match` is matched, and all requests hit the upstream port 1981 configured by the plugin: + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'release: new_release' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +The `match` rule fails to match, and all requests hit the 1980 port upstream configured on the `route`: + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'release: old_release' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + ### Custom Release -Multiple matching rules can be set in `match`, multiple expressions in `vars` are in the relationship of `add`, and multiple `vars` rules are in the relationship of `or`; as long as one of the vars rules is passed, then Indicates that `match` passed. +Multiple `vars` rules can be set in `match`. Multiple expressions in `vars` have an `add` relationship, and multiple `vars` rules have an `or` relationship; as long as one of the vars is required If the rule passes, the entire `match` passes. -Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +**Example 1: Only one `vars` rule is configured, and multiple expressions in `vars` are in the relationship of `add`. In `weighted_upstreams`, the traffic is divided into 3:2 according to the value of `weight`, of which only the part of the `weight` value represents the proportion of upstream on the `route`. When `match` fails to pass, all traffic will only hit the upstream on the route.** ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -186,7 +230,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -205,9 +249,37 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` -The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. +The plugin sets the requested `match` rule and upstream with port `1981`, and the route has upstream with port `1980`. + +**Test plugin:** -Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the value of `weight`, the flow is divided into 4:2. Among them, only the `weight` part represents the proportion of upstream on the route. When `match` fails to match, all traffic will only hit upstream on the route. +>1. After the verification of the `match` rule is passed, 60% of the requests hit the upstream of the plug-in port 1981, and 40% of the requests hit the upstream of the 1980 port of the `route`. + +The match rule is successfully verified, and the upstream port of `1981` is hit. + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +The match rule fails to verify, and it hits the upstream of the default port of `1980`. + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +After 5 requests, the service of port `1981` was hit 3 times, and the service of port `1980` was hit 2 times. + +**Example 2: Configure multiple `vars` rules. Multiple expressions in `vars` are `add` relationships, and multiple `vars` are `and` relationships. According to the `weight` value in `weighted_upstreams`, the traffic is divided into 3:2, where only the part of the `weight` value represents the proportion of upstream on the route. When `match` fails to pass, all traffic will only hit the upstream on the route.** ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -240,7 +312,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -259,87 +331,11 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` -The plugin sets the request matching rules and sets the port to upstream with `1981`, and the route has upstream with port `1980`. - -## Test Plugin - -### Grayscale Test - -**2/3 of the requests hit the upstream on port 1981, and 1/3 of the traffic hit the upstream on port 1980.** - -```shell -$ curl http://127.0.0.1:9080/index.html -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 - -$ curl http://127.0.0.1:9080/index.html -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -### Blue-green Test - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'new-release: blue' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -When the match is passed, all requests will hit the upstream configured by the plugin, otherwise hit the upstream configured on the route. - -### Custom Test - -**Example 1:** - -**After the verification of the `match` rule passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** - -The match check succeeds and it hits the upstream port of `1981`. - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -The match check succeeds, but it hits the upstream of the default port of `1980`. - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 -``` - -After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. - -**The `match` rule verification failed (missing request header `apisix-key`), the response is the default upstream data `hello 1980`** - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 -``` +The plugin sets the requested `match` rule and the upstream port of `1981`, and the route has upstream port of `1980`. -**Example 2:** +**Test plugin:** -**The expressions of the two `vars` match successfully. After the `match` rule is passed, 2/3 of the requests hit the upstream of port 1981, and 1/3 hit the upstream of port 1980.** +>1. The expressions of the two `vars` are matched successfully. After the `match` rule is verified, 60% of the requests hit the 1981 port upstream of the plugin, and 40% of the requests hit the 1980 port upstream of the `route`. ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i @@ -359,9 +355,9 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` -After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. +After 5 requests, the service of port `1981` was hit 3 times, and the service of port `1980` was hit 2 times. -**The second `vars` expression failed (missing the `name2` request parameter). After the `match` rule was verified, 2/3 of the requests hit the upstream of port 1981, and 1/3 of the request hits the port 1980 upstream.** +>2. The second expression of `vars` failed to match (missing the `name2` request parameter). After the `match` rule was verified, 60% of the requests hit the plug-in's 1981 port upstream, and 40% of the request traffic hits Go upstream to the 1980 port of `route`. ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i @@ -381,9 +377,9 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` -After 3 requests, it hits the `world 1981` service twice and the `hello 1980` service once. +After 5 requests, the service of port `1981` was hit 3 times, and the service of port `1980` was hit 2 times. -**Two `vars` expressions failed (missing `name` and `name2` request parameters), `match` rule verification failed, and the response is the default upstream data `hello 1980`** +>3. The expression verification of two `vars` failed (missing the request parameters of `name` and `name2`), the `match` rule verification failed, and the response is the upstream data `hello 1980` of the default `route`. ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i @@ -396,7 +392,7 @@ hello 1980 ## Disable Plugin -When you want to remove the traffic-split plug-in, it's very simple, just delete the corresponding json configuration in the plug-in configuration, no need to restart the service, it will take effect immediately: +When you want to remove the traffic-split plugin, it's very simple, just delete the corresponding json configuration in the plugin configuration, no need to restart the service, it will take effect immediately: ```shell $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' diff --git a/doc/zh-cn/README.md b/doc/zh-cn/README.md index 465ac1bdc43e..ab699a2db602 100644 --- a/doc/zh-cn/README.md +++ b/doc/zh-cn/README.md @@ -81,7 +81,7 @@ * [request-validation](plugins/request-validation.md): 请求验证。 * [proxy-mirror](plugins/proxy-mirror.md):代理镜像插件提供镜像客户端请求的能力。 * [api-breaker](plugins/api-breaker.md): API的断路器,在状态不正常的情况下停止将请求转发到上游。 -* [traffic-split](plugins/traffic-split.md): 请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现灰度发布、蓝绿发布和自定义发布功能。 +* [traffic-split](plugins/traffic-split.md):允许用户逐步控制各个上游之间的流量百分比。 ### Monitoring diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 47c0f1903230..dd10a8f03f40 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -26,16 +26,12 @@ - [如何启用](#如何启用) - [灰度发布](#灰度发布) - [蓝绿发布](#蓝绿发布) - - [自定义发布](#自定义发布) -- [测试插件](#测试插件) - - [灰度测试](#灰度测试) - - [蓝绿测试](#蓝绿测试) - - [自定义测试](#自定义测试) + - [自定义发布](#自定义发布) - [禁用插件](#禁用插件) ## 名字 -请求流量分割插件,对流量按指定的比例划分,并将其分流到对应的 upstream ;通过该插件可以实现灰度发布、蓝绿发布和自定义发布功能。 +traffic-split 插件使用户可以逐步引导各个上游之间的流量百分比。 注:由于插件中选择不同上游是根据 roundrobin 算法选择,因此存在算法状态重置的情况下,会对流量分配的比率并不完全精准。 @@ -58,7 +54,9 @@ ## 如何启用 -在插件的 weighted_upstreams 中只有 `weight` 值,表示到达默认 `route` 上的 upstream 流量权重值。 +traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成,`match` 是自定义的条件规则,`weighted_upstreams` 是 upstream 的信息。在使用插件时,至少需要配置 `weighted_upstreams` 部分,这样将默认 `match` 规则通过,会根据 `weighted_upstreams` 中的 `weight` 值,逐步引导各个 upstream 之间的流量比例。你也可以同时配置 `match` 和 `weighted_upstreams`,这样只有 `match` 规则匹配通过后,才会对 `weighted_upstreams` 中的流量进行划分。 + +>注:1、在 `match` 里,vars 中的表达式是 `and` 的关系,多个 `vars` 之间是 `or` 的关系。2、在插件的 weighted_upstreams 中只有 `weight` 值,表示到达默认 `route` 上的 upstream 流量权重值。如: ```json { @@ -66,9 +64,11 @@ } ``` +下面提供插件的使用示例,这将有助于你对插件使用上的理解。 + ### 灰度发布 -根据插件中 weighted_upstreams 配置的 `weight` 值做流量分流(不配置 `match` 的规则,已经默认 `match` 通过)。将请求流量按 4:2 划分,2/3 的流量到达插件中的 `1981` 端口上游, 1/3 的流量到达 route 上默认的 `1980` 端口上游。 +不配置 `match` 规则部分(已经默认 `match` 通过),根据插件中 weighted_upstreams 配置的 `weight` 值做流量分流。将 `插件 upstream` 与 `route 的 upstream` 请求流量按 3:2 进行划分,其中 60% 的流量到达插件中的 `1981` 端口的 upstream, 40% 的流量到达 route 上默认 `1980` 端口的 upstream。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -92,7 +92,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "read": 15 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -111,9 +111,29 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` +**插件测试:** + +请求5次,3次请求命中插件1981端口的 upstream, 2次请求命中 `route` 的1980端口 upstream。 + +```shell +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 + +hello 1980 + +$ curl http://127.0.0.1:9080/index.html -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 + +world 1981 + +...... +``` + ### 蓝绿发布 -通过请求头获取蓝绿条件(也可以通过请求参数获取或NGINX变量),在 `match` 规则匹配通过后,表示所有请求都命中到插件配置的 upstream ,否则所以请求只命中 `route` 上配置的 upstream 。 +通过请求头获取 `match` 规则参数(也可以通过请求参数获取或NGINX变量),在 `match` 规则匹配通过后,表示所有请求都命中到插件配置的 upstream ,否则所以请求只命中 `route` 上配置的 upstream 。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -126,7 +146,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "match": [ { "vars": [ - ["http_new-release","==","blue"] + ["http_release","==","new_release"] ] } ], @@ -154,11 +174,35 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` +**插件测试:** + +`match` 规则匹配通过,所有请求都命中插件配置的1981端口 upstream : + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'release: new_release' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +`match` 规则匹配失败,所有请求都命中 `route` 上配置的 1980端口 upstream : + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'release: old_release' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + ### 自定义发布 -`match` 中可以设置多个匹配规则,`vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则表示 `match` 通过。 +`match` 中可以设置多个 `vars` 规则,`vars` 中的多个表达式之间是 `add` 的关系, 多个 `vars` 规则之间是 `or` 的关系;只要其中一个 vars 规则通过,则整个 `match` 通过。 -示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +**示例1:只配置了一个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系。在 `weighted_upstreams` 中根据 `weight` 值将流量按 3:2 划分,其中只有 `weight` 值的部分表示 `route` 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。** ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -186,7 +230,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -205,9 +249,48 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` -插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 +插件设置了请求的 `match` 规则及端口为`1981`的 upstream,route 上具有端口为`1980`的 upstream。 + +**插件测试:** + +>1、在 `match` 规则校验通过后, 60% 的请求命中到插件的1981端口的 upstream, 40% 的请求命中到 `route` 的1980端口的 upstream。 + +match 规则校验成功, 命中端口为`1981`的 upstream。 + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +world 1981 +``` + +match 规则校验失败,,命中默认端口为`1980`的 upstream。 -示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weight` 值将流量按 4:2 划分。其中只有 `weight` 部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。 +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +在请求5次后,3次命中 `1981` 端口的服务,2次命中 `1980` 端口的服务。 + +>2、`match` 规则校验失败(缺少请求头 `apisix-key` ), 响应都为默认 upstream 的数据 `hello 1980`。 + +```shell +$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i +HTTP/1.1 200 OK +Content-Type: text/html; charset=utf-8 +...... + +hello 1980 +``` + +**示例2:配置多个 `vars` 规则, `vars` 中的多个表达式是 `add` 的关系, 多个 `vars` 之间是 `and` 的关系。根据 `weighted_upstreams` 中的 `weight` 值将流量按 3:2 划分,其中只有 `weight` 值的部分表示 route 上的 upstream 所占的比例。 当 `match` 匹配不通过时,所有的流量只会命中 route 上的 upstream 。** ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' @@ -240,7 +323,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "127.0.0.1:1981":10 } }, - "weight": 4 + "weight": 3 }, { "weight": 2 @@ -259,87 +342,11 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 }' ``` -插件设置了请求的匹配规则并设置端口为`1981`的 upstream,route 上具有端口为`1980`的upstream。 - -## 测试插件 - -### 灰度测试 - -**2/3 的请求命中到1981端口的upstream, 1/3 的流量命中到1980端口的upstream。** - -```shell -$ curl http://127.0.0.1:9080/index.html -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 - -$ curl http://127.0.0.1:9080/index.html -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -### 蓝绿测试 - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'new-release: blue' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -当 `match` 匹配通过后,所有请求都命中到插件配置的 `upstream`,否则命中 `route` 上配置的 upstream 。 - -### 自定义测试 - -**示例1:** - -**在`match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** - -match 校验成功, 命中端口为`1981`的 upstream。 - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -world 1981 -``` - -match 校验成功,命中默认端口为`1980`的 upstream。 - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'apisix-key: hello' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 -``` - -在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 - -**`match` 规则校验失败(缺少请求头 `apisix-key` ), 响应都为默认 upstream 的数据 `hello 1980`** - -```shell -$ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i -HTTP/1.1 200 OK -Content-Type: text/html; charset=utf-8 -...... - -hello 1980 -``` +插件设置了请求的 `match` 规则及端口为`1981`的 upstream,route 上具有端口为`1980`的 upstream 。 -**示例2:** +**测试插件:** -**两个 `vars` 的表达式匹配成功, `match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** +>1、两个 `vars` 的表达式匹配成功, `match` 规则校验通过后, 60% 的请求命中到插件的1981端口 upstream, 40% 的请求命中到 `route` 的1980端口upstream。 ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack&name2=rose' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i @@ -359,9 +366,9 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` -在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 +在请求5次后,3次命中 `1981` 端口的服务,2次命中 `1980` 端口的服务。 -**第二个 `vars` 的表达式匹配失败(缺少 `name2` 请求参数), `match` 规则校验通过后, 2/3 的请求命中到1981端口的upstream, 1/3 命中到1980端口的upstream。** +>2、第二个 `vars` 的表达式匹配失败(缺少 `name2` 请求参数),`match` 规则校验通过后, 60% 的请求命中到插件的1981端口 upstream, 40% 的请求流量命中到 `route` 的1980端口 upstream。 ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -H 'user-id2:22' -H 'apisix-key: hello' -H 'apisix-key2: world' -i @@ -381,9 +388,9 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` -在请求3次后,分别两次命中 `world 1981` 服务,一次命中 `hello 1980` 服务。 +在请求5次后,3次命中 `1981` 端口的服务,2次命中 `1980` 端口的服务。 -**两个 `vars` 的表达式匹配失败(缺少 `name` 和 `name2` 请求参数),`match` 规则校验失败, 响应都为默认 upstream 的数据 `hello 1980`** +>3、两个 `vars` 的表达式校验失败(缺少 `name` 和 `name2` 请求参数),`match` 规则校验失败, 响应都为默认 `route` 的 upstream 数据 `hello 1980`。 ```shell $ curl 'http://127.0.0.1:9080/index.html?name=jack' -H 'user-id:30' -i From 2e9dde5834308b34753fe48c8908823738d60ec6 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 01:50:53 +0800 Subject: [PATCH 25/30] update plugin docs. --- doc/plugins/traffic-split.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 441fe60981d4..d2eff9cac946 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -146,7 +146,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 "match": [ { "vars": [ - ["http_new-release","==","blue"] + ["http_release","==","new_release"] ] } ], From ce0abab51e80b5644792c7d8ee58fa9fb03258a4 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 02:01:36 +0800 Subject: [PATCH 26/30] fix doc typo. --- doc/zh-cn/plugins/traffic-split.md | 2 +- t/plugin/traffic-split.t | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index dd10a8f03f40..63aeb6378a4d 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -26,7 +26,7 @@ - [如何启用](#如何启用) - [灰度发布](#灰度发布) - [蓝绿发布](#蓝绿发布) - - [自定义发布](#自定义发布) + - [自定义发布](#自定义发布) - [禁用插件](#禁用插件) ## 名字 diff --git a/t/plugin/traffic-split.t b/t/plugin/traffic-split.t index 88575289f494..72935193dfcd 100644 --- a/t/plugin/traffic-split.t +++ b/t/plugin/traffic-split.t @@ -14,22 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -our $SkipReason; - -BEGIN { - if ($ENV{TEST_NGINX_CHECK_LEAK}) { - $SkipReason = "unavailable for the hup tests"; - - } else { - $ENV{TEST_NGINX_USE_HUP} = 1; - undef $ENV{TEST_NGINX_USE_STAP}; - } -} -use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::APISIX 'no_plan'; -master_on(); repeat_each(1); no_long_string(); no_root_location(); From cc550e06218ddac82edd255dbc561c155376e179 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 11:27:15 +0800 Subject: [PATCH 27/30] update docs. --- doc/plugins/traffic-split.md | 10 +++++----- doc/zh-cn/plugins/traffic-split.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index d2eff9cac946..11b418a7f9f1 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -52,11 +52,9 @@ Note: Since the selection of different upstream in the plugin is based on the ro | rules.weighted_upstreams.upstream.upstream_host | string | optional | | | Only valid when pass_host is configured as rewrite. | | rules.weighted_upstreams.weight | integer | optional | weight = 1 | | The traffic is divided according to the `weight` value, and the roundrobin algorithm is used to divide multiple `weight`. | -## How To Enable - -The traffic-split plugin is mainly composed of two parts: `match` and `weighted_upstreams`. `match` is a custom conditional rule, and `weighted_upstreams` is upstream information. When using the plugin, at least the `weighted_upstreams` part needs to be configured, so that the default `match` rule is passed, and the traffic ratio between each upstream will be gradually guided according to the `weight` value in `weighted_upstreams`. You can also configure `match` and `weighted_upstreams` at the same time, so that the traffic in `weighted_upstreams` will be divided only after the `match` rule is matched. +The traffic-split plugin is mainly composed of two parts: `match` and `weighted_upstreams`. `match` is a custom conditional rule, and `weighted_upstreams` is upstream configuration information. If you configure `match` and `weighted_upstreams` information, then after the `match` rule is verified, it will be based on the `weight` value in `weighted_upstreams`; the ratio of traffic between each upstream in the plug-in will be guided, otherwise, all traffic will be directly Reach the `upstream` configured on `route` or `service`. Of course, you can also configure only the `weighted_upstreams` part, which will directly guide the traffic ratio between each upstream in the plugin based on the `weight` value in `weighted_upstreams`. ->Note: 1. In `match`, the expression in vars is the relationship of `and`, and the relationship between multiple `vars` is the relationship of `or`. 2. There is only a `weight` value in the weighted_upstreams of the plugin, which means the upstream traffic weight value on the default `route`. Such as: +>Note: 1. In `match`, the expression in vars is the relationship of `and`, and the relationship between multiple `vars` is the relationship of `or`. 2. There is only a `weight` value in the weighted_upstreams of the plug-in, which means reaching the upstream traffic weight value configured on `route` or `service`. Such as: ```json { @@ -64,11 +62,13 @@ The traffic-split plugin is mainly composed of two parts: `match` and `weighted_ } ``` +## How To Enable + The following provides examples of plugin usage, which will help you understand the use of plugin. ### Grayscale Release -Do not configure the `match` rule part (the `match` is passed by default), and the traffic is split according to the `weight` value of the weighted_upstreams configuration in the plugin. Divide `plugin upstream` and `route's upstream` request traffic according to 3:2, of which 60% of the traffic reaches the upstream of the `1981` port in the plugin, and 40% of the traffic reaches the upstream of the default `1980` port on the route . +The `match` rule part is missing, and the traffic is split according to the `weight` value configured by the `weighted_upstreams` in the plugin. Divide `plug-in upstream` and `route's upstream` according to the traffic ratio of 3:2, of which 60% of the traffic reaches the upstream of the `1981` port in the plugin, and 40% of the traffic reaches the default `1980` port on the route Upstream. ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 63aeb6378a4d..537c0b5296ec 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -52,11 +52,9 @@ traffic-split 插件使用户可以逐步引导各个上游之间的流量百分 | rules.weighted_upstreams.upstream.upstream_host | string | 可选 | | | 只在 pass_host 配置为 rewrite 时有效。 | | rules.weighted_upstreams.weight | integer | 可选 | weight = 1 | | 根据 `weight` 值做流量划分,多个 weight 之间使用 roundrobin 算法划分。| -## 如何启用 - -traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成,`match` 是自定义的条件规则,`weighted_upstreams` 是 upstream 的信息。在使用插件时,至少需要配置 `weighted_upstreams` 部分,这样将默认 `match` 规则通过,会根据 `weighted_upstreams` 中的 `weight` 值,逐步引导各个 upstream 之间的流量比例。你也可以同时配置 `match` 和 `weighted_upstreams`,这样只有 `match` 规则匹配通过后,才会对 `weighted_upstreams` 中的流量进行划分。 +traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成,`match` 是自定义的条件规则,`weighted_upstreams` 是 upstream 的配置信息。如果配置 `match` 和 `weighted_upstreams` 信息,那么在 `match` 规则校验通过后,会根据 `weighted_upstreams` 中的 `weight` 值;引导插件中各个 upstream 之间的流量比例,否则,所有流量直接到达 `route` 或 `service` 上配置的 `upstream`。当然你也可以只配置 `weighted_upstreams` 部分,这样会直接根据 `weighted_upstreams` 中的 `weight` 值,引导插件中各个 upstream 之间的流量比例。 ->注:1、在 `match` 里,vars 中的表达式是 `and` 的关系,多个 `vars` 之间是 `or` 的关系。2、在插件的 weighted_upstreams 中只有 `weight` 值,表示到达默认 `route` 上的 upstream 流量权重值。如: +>注:1、在 `match` 里,vars 中的表达式是 `and` 的关系,多个 `vars` 之间是 `or` 的关系。2、在插件的 weighted_upstreams 中只有 `weight` 值,表示到达 `route` 或 `service` 上配置的 upstream 流量权重值。如: ```json { @@ -64,11 +62,13 @@ traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成 } ``` +## 如何启用 + 下面提供插件的使用示例,这将有助于你对插件使用上的理解。 ### 灰度发布 -不配置 `match` 规则部分(已经默认 `match` 通过),根据插件中 weighted_upstreams 配置的 `weight` 值做流量分流。将 `插件 upstream` 与 `route 的 upstream` 请求流量按 3:2 进行划分,其中 60% 的流量到达插件中的 `1981` 端口的 upstream, 40% 的流量到达 route 上默认 `1980` 端口的 upstream。 +缺少 `match` 规则部分,根据插件中 `weighted_upstreams` 配置的 `weight` 值做流量分流。将 `插件的 upstream` 与 `route 的 upstream` 按 3:2 的流量比例进行划分,其中 60% 的流量到达插件中的 `1981` 端口的 upstream, 40% 的流量到达 route 上默认 `1980` 端口的 upstream。 ```shell curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' From 2dca2142ccbb98c350062f4770efb45fb8698c70 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 11:48:46 +0800 Subject: [PATCH 28/30] update doc note content. --- doc/plugins/traffic-split.md | 2 +- doc/zh-cn/plugins/traffic-split.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 11b418a7f9f1..93ea6fa3f495 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -33,7 +33,7 @@ The traffic split plugin allows users to incrementally direct percentages of traffic between various upstreams. -Note: Since the selection of different upstream in the plugin is based on the roundrobin algorithm, the ratio of traffic distribution is not completely accurate when the algorithm state is reset. +Note: The ratio between each upstream may not so accurate since the drawback of weighted round robin algorithm (especially when the wrr state is reset). ## Attributes diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 537c0b5296ec..eac12deae9e9 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -33,7 +33,7 @@ traffic-split 插件使用户可以逐步引导各个上游之间的流量百分比。 -注:由于插件中选择不同上游是根据 roundrobin 算法选择,因此存在算法状态重置的情况下,会对流量分配的比率并不完全精准。 +注:由于加权循环算法(特别是在重置wrr状态时)的缺点,因此每个上游之间的比率可能不太准确。 ## 属性 From 6f6263b4431c5403e8a176de81e408e9818e9214 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 13:58:43 +0800 Subject: [PATCH 29/30] update docs title. --- doc/plugins/traffic-split.md | 6 ++---- doc/zh-cn/plugins/traffic-split.md | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 93ea6fa3f495..3eec521f32a5 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -23,7 +23,7 @@ - [**Name**](#name) - [**Attributes**](#attributes) -- [**How To Enable**](#how-to-enable) +- [**Example**](#example) - [**Grayscale Release**](#grayscale-release) - [**Blue-green Release**](#blue-green-release) - [**Custom Release**](#custom-release) @@ -62,9 +62,7 @@ The traffic-split plugin is mainly composed of two parts: `match` and `weighted_ } ``` -## How To Enable - -The following provides examples of plugin usage, which will help you understand the use of plugin. +## Example ### Grayscale Release diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index eac12deae9e9..130e9c0a0ac6 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -23,7 +23,7 @@ - [名字](#名字) - [属性](#属性) -- [如何启用](#如何启用) +- [示例](#示例) - [灰度发布](#灰度发布) - [蓝绿发布](#蓝绿发布) - [自定义发布](#自定义发布) @@ -62,9 +62,7 @@ traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成 } ``` -## 如何启用 - -下面提供插件的使用示例,这将有助于你对插件使用上的理解。 +## 示例 ### 灰度发布 From 237a4e1c651343b7ff37fd1ca74409facfd25a6a Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 25 Dec 2020 14:36:27 +0800 Subject: [PATCH 30/30] in docs add how to enable content. --- doc/plugins/traffic-split.md | 46 ++++++++++++++++++++++++++++++ doc/zh-cn/plugins/traffic-split.md | 46 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/doc/plugins/traffic-split.md b/doc/plugins/traffic-split.md index 3eec521f32a5..9f92c6a28d66 100644 --- a/doc/plugins/traffic-split.md +++ b/doc/plugins/traffic-split.md @@ -23,6 +23,7 @@ - [**Name**](#name) - [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) - [**Example**](#example) - [**Grayscale Release**](#grayscale-release) - [**Blue-green Release**](#blue-green-release) @@ -62,6 +63,51 @@ The traffic-split plugin is mainly composed of two parts: `match` and `weighted_ } ``` +## How To Enable + +Create a route and enable the `traffic-split` plugin: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 1 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + ## Example ### Grayscale Release diff --git a/doc/zh-cn/plugins/traffic-split.md b/doc/zh-cn/plugins/traffic-split.md index 130e9c0a0ac6..834c46836679 100644 --- a/doc/zh-cn/plugins/traffic-split.md +++ b/doc/zh-cn/plugins/traffic-split.md @@ -23,6 +23,7 @@ - [名字](#名字) - [属性](#属性) +- [如何启用](#如何启用) - [示例](#示例) - [灰度发布](#灰度发布) - [蓝绿发布](#蓝绿发布) @@ -62,6 +63,51 @@ traffic-split 插件主要由 `match` 和 `weighted_upstreams` 两部分组成 } ``` +## 如何启用 + +创建一个路由并启用 `traffic-split` 插件: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/index.html", + "plugins": { + "traffic-split": { + "rules": [ + { + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":10 + }, + "timeout": { + "connect": 15, + "send": 15, + "read": 15 + } + }, + "weight": 1 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + ## 示例 ### 灰度发布