暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基于ServiceMesh和全链路的灰度发布实现

码不禁言 2021-04-02
1491

背景

互联网产品追求快速迭代验证,对于关键的业务在完成开发测试之后,很多时候需要线上灰度发布,通过切部分流量或者特定请求路由的方式进行验证,之后才进行全量发布;另外产品希望通过A/B Test来分析功能变更对用户产生的影响,可以对流量进行匹配路由到特定的版本。

目前公司没有一套统一的灰度发布验证系统,有灰度需求的业务一般会在代码里头嵌入灰度逻辑,一来导致重复工作,二来对业务接入不友好。因此这里基于公司现有的ServiceMesh和全链路的能力来做下灰度发布的实现方案。


常见的发布方案

这里我们先熟悉一下业界常用的五颜六色的发布方案

1 蓝绿发布

如图所示,蓝绿发布在升级的时候不会停止正在服役的版本v1,而是启动一套新的版本v2,等v2正常跑起来并验证OK之后将负载均衡切换到v2版本,完成升级过程。可以看到蓝绿发布需要两套系统支撑,对资源要求较高。

这里对应到k8s的pod升级实现,正在服役的pod拥有v1标签,service作为负载均衡selector指定了v1的pod;之后启动新版本的pod并打标签v2,之后将service的selector指向v2,这种方式即是蓝绿发布。

2 滚动发布

如图所示,滚动发布并不是非蓝即绿的步骤,而是在相同资源的情况下让两个版本逐步过渡,比如首先升级一个节点到v2版本,这样将会导致部分流量切到新的版本上(当然这里的流量可以做控制,比如新版本的先切10%的流量),通过观察新版本稳定性之后再逐步把其他的v1版本升级到v2版本,完成整个升级过程。

对应到k8s,对于pod进行手工滚动升级是一个复杂的操作,因此k8s定义了一个deployment资源方便实现滚动升级,而具体的滚动升级过程要考虑的内容也挺多,比如多久认为一个新版本是可用的,如何判断版本的可用,在不可用的情况下需要自动回滚等。

目前我们公司在非k8s环境下的升级一般是先将要升级的节点权重调为0,之前新版本发布到该节点,在调大权重观察,如果没问题则将新版本应用到其他节点。可以理解为是一种人工滚动升级操作。

3 灰度发布

灰度发布也可以称为金丝雀发布,简单讲就是按照一定的规则将小部分流量切到新版本上,观察新版本的情况再决定是否扩大升级范围。这里自然地提出一个问题:灰度发布和滚动发布的区别?

笔者在查看了相关文章之后并未得到满意的回答。这里讲下我个人的理解:这两者并不是对立的概念,只是侧重点的不同。灰度发布更强发布过程的流量控制,而滚动升级更强调升级过程的版本变化。如前所述,在滚动升级过程中先升级部分节点并分配一定比例的流量本质上也是在做灰度发布。反过来,在灰度发布验证OK的情况下,逐步将流量切到新版本的过程也是一个滚动升级的过程。

灰度发布强调流量的分配,而流量的分配规则是关键,简单的我们可以对全流量做百分比配置,也可以根据http header做路由加权重规则,甚至可以根据业务的协议字段去做白名单规则。只有对流量进行管控的平台如istio可以做到精确的灰度发布,k8s配合ingress也可以实现一定程度的灰度发布。

4 A/B Test

一般我们的灰度发布是为了版本升级,整个过程不会太久。而A/B Test可以理解为灰度发布的另一种应用场景,更多地是为了对比两个版本,获取用户对产品功能的反馈,进一步对产品后续功能做决策。


基于ServiceMesh的灰度发布实现

如上图所示,这是目前公司mesh具备的灰度能力,业务通过注册的时候指定tag来标志路由信息(比如版本号),请求方通过指定route_tag消息头告诉mesh需要请求的后端节点,这里保证了mesh的单纯性,不涉及具体的业务协议。但对于业务有两个复杂度:service需要在启动的时候注册tag,client在调用的时候需要自己去填写route_tag信息。

1 灰度规则服务

这里很容易想到的是把灰度规则做成服务,将业务的灰度逻辑抽象到服务中,灰度规则的逻辑可能会很复杂,比如会根据业务的协议去做判断,比如根据业务中的某个uin字段判断具体的路由tag

灰度规则对外的接口类似如下:

    {"appid":"aa.bb.cc","method":"/test","header":{"X-Uin":10000},"data":{"test body"}}
    {"ret":0,"msg":"ok","route_tag":"v1"}

    2 ServiceMesh的灰度插件

    在抽象了灰度规则服务之后,关键的问题是谁来调用灰度服务,如果在业务侧做调用判断的话带来一定的侵入性。如果在mesh这里做的话要考虑性能问题和耦合性问题,性能方面解决思路是灰度只针对某些appid的某些method实现,由于灰度的周期并不长因此可以接受一定的性能损耗。从耦合性方面考虑,将灰度服务的请求做成插件化,不影响正常的业务逻辑。如下图所示

    这里引入了gray rule service的一跳,对性能会有影响,如果灰度规则是通用行为比如针对http header做规则实际可以在mesh里头实现,但业务的场景多样化和不确定性考虑,则需要将这块独立成外部服务。


    基于全链路的灰度发布实现

    前面我们描述了通过ServiceMesh实现单个业务灰度的逻辑,但业务的灰度逻辑远比这个复杂,比如业务在一个需求中会同时更新多个服务,多个服务之间存在调用关系,在灰度过程中希望整个调用链都能够按灰度的版本走。一种方式是针对所有链条中的服务做灰度规则配置,但这种方式需要知道整个调用链关系,复杂且容易遗漏。

    由于目前我们的服务有一套全链路追踪的实现,只要在链路追踪的上下文SpanContext里头携带具体的灰度tag,则整条链路的服务都可以感知该灰度规则,这样只要针对入口的服务做灰度规则,而其他服务只要启动的时候指定tag即可。

    这里存在一个问题:A->B->C->D,如果某功能只涉及到A和D,它们指定了灰度tag为v2,而B和C并没有做版本变更,此时的灰度是否影响到B和C?并不会,虽然路由tag会随链路的SpanContext传递,但是mesh在判断没有B和C的该灰度版本之后,会按默认的路由规则进行B和C的路由。


    灰度逻辑的约定

    这里我们理解灰度发布是一个流量控制和发布观察的过程,因此业务逻辑不依赖灰度的可靠性,即在极端情况比如灰度逻辑异常,或者灰度服务不可用的情况,并不影响实际业务的正常运行。细节如下:

    • 灰度服务不可用,则路由走默认的逻辑,即灰度的流量会被路由到老版本

    • 新版本服务不可用,则灰度流量会被路由到老版本,不影响其他灰度流量。

    • 灰度逻辑在一定程度上对配置规则的appid和method有性能影响,因此对性能要求较高的服务不建议长期开启灰度逻辑。

    其他

    1 这里只讲到灰度的验证过程,并没有给出灰度验证和自动发布的流程,这块涉及的内容较为复杂,需要结合发布平台一起来考虑

    2 文章针对的是云服务器考虑的,后续可以考虑结合k8s来做方案设计

    文章转载自码不禁言,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论