最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

【网关开发】9.Openresty 自定义流量分流策略支持灰度(金丝雀)等发布业务场景

来源:博客园
目录
  • 背景
  • 发布模式
    • 蓝绿发布
    • 金丝雀发布
    • 滚动更新
  • 实现原理
    • 架构图
    • 流程说明
  • 核心代码
    • 数据结构
    • 流量标记
    • 代理策略
  • 测试
  • 总结与思考
    • 优先级
    • 扩展

背景

随着云技术和基础架构的成熟,发布过程中可以通过引入相应的发布策略,能让我们在早期实验阶段就可以发现、调整问题,来保证整体系统的稳定性网关作为流量入口,要求有能力进行流量分流配置支持各种灰度发布、金丝雀发布、滚动更新等模式


(资料图)

发布模式

蓝绿发布

通过部署两套环境来解决新老版本的发布问题,流量逐渐的从老系统切换到新系统中,同时保持两个系统中在线,同时切换

金丝雀发布

是灰度发布的一种实现,部署的时候让一小部分用户先试用功能 ,通过日志监控或者服务器监控,或新用户的反馈。如果没有严重问题,尽快部署这个新版本,否则快速回滚,小代价去试错

滚动更新

一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用

实现原理

分成标记和代理策略两部分,标记是根据用户的header中key或者IP等特征,对流量进行标记,代理策略则根据用户的标记值选择对应的机器列表,实现特定的用户访问特定的机器

架构图

流程说明

金丝雀与灰度的流量原理一致1.云平台开始进行金丝雀发布,一般新启动一个POD,将机器元数据增加标签(runtime:v1.0.0)2.通知网关管理后台增加流量规则3.网关管理后台将数据写入etcd,etcd推送给所有网关节点的openresty更新配置4.特殊用户访问请求会将这部分流量进行标记,标记结果是将header中会带有特殊的值5.在负载均衡模块会将标签特殊值与带有元数据标签的机器进行选择。

核心代码

本次代码库:https://github.com/zhaoshoucheng/openresty/blob/main/pkg/lua_script/upstream/traffic_policy.lua在设计上实现两种标记方式一种是

  • 流量分组:一个流量只能有一个分组,多个分组之间的流量是相互独立
  • 流量标签:一个流量可以有很多个标签。所以流量可以在多个标签间共用。

数据结构

流量标记set_group 是分组标记 所有ip是10.99.4.179会被标记成gray分组set_tag 是标签标记 所有header中带有x-canary-test:test 会打上标签ruversion:ruversion_server_test

{"available_domain": ["server_test.com", "server_test1.com"],"name": "server_test","rules": [{"actions": {"action": "set_group","value": "gray"},"key": "","op": "equal","type": "ip","value": "10.99.4.179"}, {"actions": {"action": "set_tag","key": "ruversion","value": "ruversion_server_test"},"key": "x-canary-test","op": "equal","type": "headers","value": "test"}]}

代理策略当分组是gray,则匹配元数据traffic_strategy:gray的机器

{"apply_on": ["server_test.com", "server_test1.com"],"enabled_when": {"match_group": "gray","match_tags": {}},"endpoint_metadata_match": {"traffic_strategy": "gray"},"name": "server_test_match_group"}

当标记的标签是ruversion:ruversion_server_test,则匹配元数据app_version:server_test-v1.0.0的机器

{"apply_on": ["server_test.com", "server_test1.com"],"enabled_when": {"match_group": "","match_tags": {"ruversion": "ruversion_server_test"}},"endpoint_metadata_match": {"app_version": "server_test-v1.0.0"},"name": "server_test_match_tags"}

机器元数据信息metadata中保存的标签

{"endpoints": [{"address": "10.218.22.239:8090","metadata": {"app_version": "server_test-v2.0.0","traffic_strategy": "gray",},"modify_date": 0,"state": "up","weight": 1}, {"address": "10.218.22.246:8090","metadata": {"app_version": "server_test-v1.0.0","traffic_strategy": "default",},"modify_date": 0,"state": "up","weight": 1}]}

剩下的就是通过程序,把这些关联起来

流量标记

apis.conf 配置access_by_lua_block阶段需要调用的lua块

access_by_lua_block {require "upstream.traffic_policy".do_coloring()}

元表数据结构 _coloring_policy 标记配置,proxy_policy代理策略配置

function _M.new(opt)    return setmetatable({        _coloring_policy = {},      -- map[domain]coloring policy        _proxy_policy = {},         -- map[domain]proxy policy        _etcd_revision = "0",    }, _MT)endlocal function do_coloring(self)    if not coloring_policy then        return     end    local policy = coloring_policy:get_coloring_policy()  -- 根据域名获取配置    if not policy then        return    end    local parts = { }    local rules = policy.rules    for i = 1, #rules do        local rule = rules[i]        local cond = rule.op        if not cond then            return "invalid rule op `"..tostring(rule.op).."`"        end        local _match = false        if rule.type and rule.type == "headers" then            local headers = ngx.req.get_headers()            if headers[rule.key] and headers[rule.key] == rule.value then                _match = true            end        end        if rule.type and rule.type == "ip" then            if ngx.var.remote_addr == rule.value then                _match = true            end        end        -- TODO 可以添加其他条件        local actions = rule.actions        if _match then            if actions.action == "set_group" then                ngx.req.set_header("X-Traffic-Group", actions.value)            end            if actions.action == "set_tag" then                table.insert(parts, actions.key.."="..actions.value)            end        end    end    if #parts ~= 0 then        ngx.req.set_header("X-Traffic-Metadata", table.concat(parts, "; "))    endend

测试经过流量标记后,header中的值,命中两种不同的规则grouptag

代理策略

代理策略主要是根据流量标记,选择合适的机器列表,传给负载均衡模块upstream_context.lua 调用方

-- 将所有机器列表输入,选择合适的机器输出local ups_nodes = traffoc_policy:do_proxy(self._ups.nodes)

traffic_policy.lua

local function do_proxy(self, nodes)    if not coloring_policy then        return     end    local policies = coloring_policy:get_proxy_policy()  --根据域名选择对应的策略    if not policies then        return nodes    end    return _match_metadata(policies, nodes) endlocal function _match_metadata(policies, nodes)    local headers = ngx.req.get_headers()    local header_traffic_group = headers["x-traffic-group"]           -- 查看分组标记    local header_traffic_tags = get_header_metadata(headers["x-traffic-metadata"])  -- 标签标记    local endpoint_match = {}    for i = 1, #policies do        local policy = policies[i]        local enabled_when = policy["enabled_when"]        if not enabled_when then            break        end        if enabled_when["match_group"] ~= "" and header_traffic_group and enabled_when["match_group"] == header_traffic_group then            -- group 检测命中            endpoint_match = policy["endpoint_metadata_match"]            break        end        if not header_traffic_tags then            goto continue        end        if enabled_when["match_tags"] and enabled_when["match_tags"] ~= {} then            for key, value in pairs(enabled_when["match_tags"]) do                if header_traffic_tags[key] ~= value then                    goto continue                end            end            -- tag 检测命中            endpoint_match = policy["endpoint_metadata_match"]            break        end        ::continue::    end    local match_nodes = {}    for i = 1, #nodes do          -- 选择机器        local metadata = nodes[i]["metadata"]        for key, value in pairs(endpoint_match) do            if metadata[key] ~= value then                goto nextnode            end        end        table.insert(match_nodes, nodes[i])        ::nextnode::    end    return match_nodesend

测试

策略命中与机器选择标记分流方式分组分流方式

总结与思考

最核心的功能是如何使流量分流,也就是特殊的流量转发到特殊的机器,只有可以控制流量的分流才有后续的灰度发布、金丝雀发布、滚动更新等业务场景。所以所有的流量标记、代理策略实际上都是在实现流量动态分流的功能。

优先级

本文只是为了理解流量分流策略,实际在匹配会有优先级来控制匹配过程

扩展

把思路打开,实际上流量标记就是识别流量,在header中添加数据,代理策略就是根据header中选择机器,两个也不一定成对使用,也可以单独进行配置使用,实现更复杂和独特的业务场景。

关键词: 数据结构 标签标记 选择合适