微服务协作开发与灰度发布之流量染色

在微服务体系下服务被拆分,同时在持续集成交付部署的过程中,往往是是多实例、多版本共存,在这样一个集群环境如何有选择的调用服务的某个版本甚至某个实例?答案就是流量染色 ,比如常说的灰度、蓝绿等都可以算作流量染色的实际应用,本文将介绍流量染色的常用场景,以及具体实施方案。

场景

首先从需求出发,先看看在现实中有哪些具体的场景需求。

多服务、多版本协作开发

在开发过程中每个服务都在迭代,这些服务如何集成?较常见的是共用一个集成环境,而开发过程中服务的质量往往是没有保证的,这会使不同的团队之间产生相互的而影响,一个登录服务挂了,大家都服务进入系统。这时我们需要给每个服务一个稳定的基准环境,而不是依赖一些开发态的上下游服务。

流量染色-Develop

多项目、多环境

与协作开发场景类似,如果需要面对不同项目,每个项目又都在同一套系统下有部分服务需要定制,常规的可能是每个团队申请一套资源来部署整套环境,而实际上这些资源间存在较大的浪费,同样也可以像解决协作开发相互g干扰一样,提供一个 Base 环境,每个项目只需要新增自身定制服务的资源即可。

流量染色-Team

本地调试

与多服务版本协作开发类似,进一步的需求是开发人员是不是可以在本地使用集成环境的上下游服务进行调试?同样是可行的,在网络互通的情况下,基本没有差异,只需要在网关有不同的策略配置可以选择路由本地环境的负载即可。

流量染色-Local

金丝雀/灰度发布

金丝雀或灰度发布根据不同策略将部分流量调度到 Canary 版本,在一个持续迭代交付的系统中可能不同服务都存在 Canary 版本,也可能只有部分不服务有 Canary 版本,或者出于灰度策略的不同有的流量可能只需要将部分服务调度到 Canary 版本,而其它服务全部走 Stable 版本,如下图:绿色为全部服务都走 Canary 版本,而红色则仅 SRV-2 调度到 Canary 版本。

流量染色-Canary

流量染色

面对这些场景需求从框架、服务治理角度该如何实现?问题的根本就是在做负载均衡前对负载进行过滤,不过不是简单的条件筛选,而是带有优先级负载筛选。对应的一个概念就是流量染色 ,流量染色的具体实施可以分为两个阶段:

  • 染色策略 :哪些流量需要被染色,以及染成什么“色”,甚至是”单色“还是”彩色“
  • 负载策略 :如何通过染色信息选择负载,可以有各种策略,具体需要根据场景进行设计选择
    • 策略优先还是必选?优先是有匹配的服务时优先选择,没有时选择其它负载(其它负载还存在一个选择问题,前文的 Base 可以理解为”无色“,而其它的都是”有色“,具体也是要结合需求来考虑),必选在没有匹配负载时则直接异常
    • 服务范围?某个 / 某些服务的某个版本,还是全链路的版本筛选

虽然将此过程分成了两个阶段实际上最终要解决的就是负载选择问题,

根据场景需求不同可以有各种组合方案,总体思路有两种:一是在流量入口(或请求发起位置)确定染色信息,工作负载仅需要根据染色信息选择负载,二是将染色和负载选择都做在工作负载。

方案一:

  • 统一在网关染色,网关是接收客户端请求最初的入口,包含原始的终端请求信息,比如 Session、Cookie、Token 等等,根据染色策略生成染色信息,同时将染色信息在服务间传递,染色信息需要根据负载策略设计一套通用规则,如:[{ Service 匹配正则 }:]{ KV 标签条件 }
  • 工作负载根据染色信息选择负载,比如灰度标签、版本等等
  • 优点是染色策略统一在网关,便于管理,且有最原始的终端信息;工作负载仅关注负载选择,SDK 可以做的很轻,降低升级维护成本
  • 不足是对于不走网关的流量能不能灰度?或者不同服务的灰度策略不同该怎么支持?这些还需要方案二的补充

方案二:

  • 工作负载 根据服务上下文中的信息生成染色策略,比如 JWT Token
  • 优点可以将染色策略给到每个服务自身,充分支持服务自治,也可以补充方案一的不足
    • 在大规模的微服务系统中不同服务未必有一致的灰度策略和周期
  • 不足是染色策略配置在工作节点,这样不同服务的灰度策略需要下发给全部节点(默认不知道那些下游服务会依赖该服务),且 SDK 的能力也要相对方案一复杂,相应的也就提高了升级维护成本
    • 染色策略放入注册中心的 metadata 可以一定程度得到简化,调用方订阅所依赖服务的注册信息,在 RPC 的 Client 端根据服务版本分组,并从 metadata 信息中获取染色策略,进而完成负载选择
  • Istio 的灰度算是这种方式,Istio 自身有比较完备的流量管理的规范,且 Sidecar 没有 SDK 的依赖

通过两个方案的分析对比我们有可以看出,没有哪一个是最优的,回到最初的目的对负载进行过滤 ,往往需要根据实际场景进行权衡,将两者结合使用,所谓的染色策略负载策略 并没有明确的界限,最终都是为了将特定流量路由到特定实例。

实践-灰度发布

接下来以灰度发布为例设计实现一套染色及负载策略:

流量染色-Canary

  • 染色策略 :在网关根据 HTTP Header 中的 UserId0~99 定义为绿色 流量,100~199红色 流量,200~∞蓝色 流量,策略结构为 [{ Service 匹配正则 }:]{ KV 标签条件 };... ,其中 KV 条件为 track=stable/canary ,根据正则条件匹配服务(为空标识匹配全部服务),可以多条规则,按顺序优先匹配
    • 0~99 : track=canary
    • 100~199 : SRV-2:track=canary;track=stable
      • track=stable 为兜底
    • 200~∞ : track=stable
  • 负载策略 :在网关或 RPC 框架在调用时根据染色情况对服务实例进行筛选

网关

TODO

  • 染色策略配置规则支持
  • 染色策略加入到请求上下文
  • 上游负载的筛选

RPC 框架

TODO

  • 染色策略加入到请求上下文
  • RPC Client 对上游负载的筛选