上次我们聊到为什么要实现低风险发布和常见的低风险发布策略。今天我们来聊一聊如何具体实现低风险发布,本篇是如何实现低风险发布的第二篇,也是低风险发布的实践落地篇,主要探讨灰度发布和蓝绿部署如何通过技术手段实现,以及落地实践中的一些要点。

写在前面

任何一个技术方案都有它适配的场景,灰度发布和蓝绿部署的技术方案也不例外,甚至灰度发布和蓝绿部署还是有比较多的依赖。本文给出的推荐方案或实践案例,主要适合于后端微服务的部署和发布场景。可从以下两个条件进行甄别是否适用:

1、后端服务的部署和发布,前端、移动端的发布不在此次考虑范围;

2、后端采用的是微服务架构,如果是单体应用,没必要考虑这么复杂的方案,可在发布过程中直接调整负载均衡器或流量网关的路由策略即可。

微服务也是当下最火热的后端架构之一,使用场景广泛,在部署、可扩展性、易于团队协作等诸多方面有着明显的优势,但是也对基础技术设施,包括服务注册中心、RPC调用、配置中心、可观测性、监控、安全等方面有着很高的要求。微服务不是本篇讨论的重点,不了解的小伙伴可以自行查阅相关书籍和资料,极客时间等平台上也有相关专栏课程。我们接下来的方案依赖于微服务的一些基础技术组件,主要包括以下几种:

1、API网关:API 网关为客户与服务系统之间的交互提供了统一的接口,也是管理请求和响应的中心点。API 网关在微服务架构中是系统设计的一个解决方案,用来整合各个不同模块的微服务,统一协调服务[1]。

2、服务注册中心:服务注册中心本质上是为了解耦服务提供者和服务消费者,主流的服务注册中心包括Consul、Nacos、Eureka、ETCD、Zookeeper等。

3、Service Mesh:一种新型的用于处理服务与服务之间通信的技术,尤其适用以云原生应用形式部署的服务,能够保证服务与服务之间调用的可靠性。在实际部署时,Service Mesh 通常以轻量级的网络代理的方式跟应用的代码部署在一起,从而以应用无感知的方式实现服务治理[2]。代表产品有Istio、Linkerd等。

如果你了解过微服务或者所在的公司正在使用微服务,相信你对以上提到的技术不会陌生,API网关和服务注册中心可以说是实施微服务的基础,Service Mesh 除了Istio这个代表性的开源产品外,在国内多个互联网公司也都有落地实践,阿里云和华为云等各大云厂商也都各自推出了自家的Service Mesh产品。我们的服务灰度发布或蓝绿部署方案,也会依赖于微服务的这些技术组件,国内大厂的技术实践页大致如此[3],不过其中的Service Mesh不是必须依赖的,只有API网关和服务注册中心,就可以实现功能完善的灰度发布和蓝绿部署。

还需要指出的一点是,灰度发布和蓝绿部署依赖微服务框架和发布系统的紧密配合才能完成,如果服务已经部署到Kubernetes(K8s)上,相比于运行在虚拟机,整个部署和发布过程可能会更加轻松。

整体方案介绍

核心在于构建两个模块的能力,一个是路由规则管理,另一个是实现流量调度,即服务按照配置好的路由规则实现如何请求下游服务。

实现灰度发布和蓝绿部署,核心是要在服务发布的过程中,控制上游服务的流量按照路由规则请求到本服务的各个部署实例中。如,要发布服务B的灰度实例G,此时期望灰度实例不接收流量或者接收1%的流量,此时要调整服务B的路由规则,所有上游服务也要感知到这个路由规则,才能做出响应,而期望上游服务做出的响应是要把所有请求服务B的流量不分配或者分配1%给到实例G。

这里就涉及两个核心问题:一是路由规则怎么管理,包括如何设置这个规则,服务的路由规则变更后,其他服务如何感知;二是,如何响应这个路由规则的变更,如何按照路由规则实现流量调度。下面我们来探讨下这两个问题。

路由规则管理

从上面的分析可以知道,路由规则管理的主要目标,通俗来说就是,制定一套路由规则,规则变更时能够被所有服务感知。解决方案通常是:每个服务有自己的路由规则,这个规则可用于声明其他服务如何访问自己,当前也可以声明自己如何访问别的服务,但是对于单个微服务来说,获取别的服务信息难度相对较高,可操作性就比较低了。为了让其他服务及时感知自己的路由规则变化,我们也需要在路由规则变化时推送这些变化内容给相关服务。因此我们需要一套路由规则管理系统,包括统一路由规则定义、路由变化订阅通知机制等等。

实现流量调度

那么,其他服务收到路由规则的变化通知后应该如何响应呢?这取决于服务间是如何进行访问的。如果是直接连接下游服务IP地址的方式来访问,这就需要在服务请求Client或相关SDK代码上做文章,如果是通过类似Istio Sidecar的代理访问模式,则需要在Sidecar上进行处理了。不管通过微服务的SDK还是Sidecar,所要做的核心内容都是根据收到的服务路由规则变化,调整访问该服务的流量策略。如,收到服务B新增了一个实例G,要求分配1%给到实例G,那么就调整现有流量的1%给到实例G。

核心架构

基于以上的分析,核心架构如下:

image

其中路由管理控制面负责路由规则的存储,接收路由规则上报,并推送路由规则给数据面。而数据面就由Sidecar或者服务的SDK承担,负责响应路由规则变化,按照路由规则实现访问下游服务的流量调度。如灰度发布服务B的时候,B服务会根据用户指定的发布策略生成路由规则,并把规则上报给路由管理控制面,控制器检测到服务A需要访问B服务,就把服务B的路由变化推送给服务A,服务A的Sidecar或SDK及时调整流量策略,控制一定比例的流量访问服务B的灰度实例即可完成这一阶段的流量访问控制。

接下来我们看下具体的灰度发布和蓝绿部署流程是怎样的和实现的一些细节。假定我们的服务采用K8s部署,下面分别论述基于API网关和服务注册中心、基于Service Mesh实现的灰度发布和蓝绿部署方案,其中:

1、API网关以APISIX[4]为例,服务注册中心以Consul为例;

2、Service Mesh以Istio为例。

基于API网关和Consul实现

路由规则定义

无论是实现灰度发布,还是蓝绿部署,一套简单易用的路由规则定义都是基础,幸运的是Consul等注册中心已经提供了一套相对完善的服务注册配置[5],如果使用Consul作为路由管理的控制面,我们基于此扩展即可。我们可以扩展 meta 字段内容,定义灰度发布等需要的字段信息。服务实例注册到Cousul的路由规则,示例如下(采用yaml格式):

name: bservice
# ...
meta:
  canary: true # 当前部署实例是否为灰度实例
  bluegreen: green # 当前部署实例是蓝环境还是绿环境
  weight: 10 # 权重为10
  match: # 匹配规则,支持按照header、cookie、uri prefix等等
    - header:
        - key: "userid" # header key
          value_match: "123" # value匹配条件
      cookie:
          regex: "^(.*?;)?(email=[^;]*@some-company-name.com)(;.*)?$"
      uri:
        - prefix:
            - "/api/user"

以上是一个典型的例子,大家页可以根据自己习惯约定不同的路由规则定义,重要的是团队内外达成共识和规则易于大家理解。

灰度发布

清晰的路由规则约定是实施灰度发布或蓝绿部署的前提,基于上述路由规则约定,我们在灰度发布的过程中只需要在服务部署时修改服务的 meta 数据即可。但是,如果我们只使用了Consul的服务注册与发现能力,尚未构建Service Mesh,对于从Nginx/Ingress的流量则无法实施有效控制,所以我们把核心架构拆分为路由管理控制面与各个微服务、路由管理控制面与Nginx/Ingress两个模块来探讨下如何实现路由规则注册与推送、流量调度的。

路由管理控制面与各个微服务

路由规则管理

路由规则管理主要包括服务路由注册、变化通知与推送等,借助于服务注册中心的能力,我们可以轻松地实现服务注册,Consul借助Client Agent等组件,轻松实现服务注册信息的推送,同时保障高可用。路由管理控制面与服务之间的上报路由变化、推送路由规则等需求,借助Consul都得到了很好的实现。当前Java、Go语言相关的微服务框架也大多支持Consul作为注册中心。

流量调度

余下的就是我们的服务SDK如何去响应路由规则的变化了,大致的实现逻辑是:各个微服务的SDK订阅服务注册数据变化,解析 meta 中的服务灰度发布相关信息,控制流量按照路由规则进行分配,可实现灰度发布时的流量调度预期策略。如服务B要进行灰度发布,上报路由规则期望调整5%的流量给灰度实例,此时相关微服务都会收到这条路由规则的变化通知,服务A要请求B服务,此时就会调整访问B服务的流量策略,控制5%的流量访问B服务的灰度实例。不同的微服务框架有不同的实现方式,进一步了解可参考相关微服务框架的文档,如Go微服务框架Kratos的路由与负载均衡[6]。

路由管理控制面与API网关

不直接连接Nginx或Ingress等API网关流量的服务,只需要借助上述方式即可实现灰度发布,因为服务的入流量均受上游服务SDK控制。但是如果是业务网关服务或者与API网关直接连接的服务,还是不能进行灰度发布,因为路由规则未同步给API网关,API网关也没有实现识别上述路由规则的流量调度。

那么怎么解决呢?很简单,就是路由规则同步给API网关,API网关实现识别上述路由规则的流量调度。这不废话么,究竟怎么实现呢?其实当前很多优秀的API网关都支持上述扩展,以APISIX为例,可以同步Consul的服务注册信息,也可以扩展自定义插件支持识别上述路由规则,按照预期方式实现流量调度,这里就不详细介绍了,具体可参考APISIX的官方文档[7]。顺便提一句,你也可以选择使用Nginx Ingress来实现灰度发布[8]。

灰度发布流程

聊到这里,想必你已经了解怎么实现灰度发布了,但是可能对一些流程细节还是比较模糊,比如灰度实例刚发布时要不要接收生产环境流量呢?灰度发布实例要怎么设置灰度策略呢?灰度实例验证完需要销毁么?我们的服务部署在K8s,发布系统需要怎么做才能实现灰度发布呢?下面我们就来分析下这些问题如何解决。

灰度策略用户自定义

我想和你说的第一点就是:灰度策略需要支持用户自定义,同时发布系统也要有默认的灰度策略。各个业务需求千差万别,所以灰度策略一定要开放。针对某类或者具体服务,可以设置默认的灰度策略,这样保障发布的顺畅性,也可以在发布过程中灵活调整,保障流量策略满足发布验证多变的需求场景。核心的服务我们可以设置默认灰度发布后,不承载生产流量,此时我们仅指定环境变量 canary=true,灰度实例读取环境变量,注册以下路由规则到注册中心,SDK收到路由变化时做出流量策略调整,只允许 header 中包含 canary=true 的流量转向该灰度实例,这就做到了第一阶段的流量策略,即仅接收 header 中包含 canary=true 的流量。用户可以手工调整流量比例或其他流量策略,包括只接收用户名为 test 的流量等等。

发布系统如何控制灰度发布流程

第二点是:采用单独的发布步骤、单独的工作负载(如单独一个Deployment)控制灰度实例。在一个发布流水线中,灰度发布和蓝绿部署通常都可以作为单独的一个步骤,这样也会有更高的灵活性。灰度发布的过程是由发布系统控制的,如果使用K8s发布,我们可以新建一个单独的 Deployment 来控制灰度实例,这样做的好处是与生产环境的稳定集群实例尽量保持距离。调整流量策略时,尽量避免对现有生产实例产生影响,不能因为灰度策略的调整频繁重启生产环境的实例,灰度实例和生产环境的稳定集群实例采用两个Deployment有助于帮助做到这一点。

灰度发布的基本流程

灰度发布的基本流程如下图所示:

graph TD 灰度发布开始 –> 发布灰度实例:Canary=true –> 测试流量验证 –>|用户调整灰度策略| B(灰度实例接收生产环境正式流量:如Weight=5) B –> 测试验收灰度通过 –> 灰度阶段结束并下线灰度实例 –> 稳定集群全量发布 B –> 测试验收灰度不通过 –> 灰度失败

灰度实例发布后默认不接收生产环境的流量,测试可以通过在 header 中注入 canary=true 注入测试流量进行测试,验证通过后,再调整生产的小部分流量到灰度实例中,这样做更是降低了生产发布的风险。

需要指出的是,如果灰度发布阶段没有验证出问题,稳定集群全量发布后,如果实例过多,可能需要较长时间的回滚操作。

蓝绿部署

上面我们详细介绍了灰度发布的实现方式和流程,蓝绿部署的实现原理可以说是和灰度发布是极其相似的。有一个值得探讨的问题是,有了灰度发布为什么还要实现蓝绿部署呢?要回答这个问题我们可以先梳理下蓝绿部署的基本流程,如下图所示:

graph TD 蓝绿部署开始 –> 部署绿环境实例:Bluegreen=green –> 测试流量验证 –>|用户调整流量策略| B(绿环境实例接收生产环境正式流量:如Weight=5) B –> 绿环境验收通过 –> 切换绿环境为稳定集群环境:快速切换LB指向 –> 下线之前生产实例 B –> 绿环境测试验收不通过 –> 切流回滚

相比灰度发布,蓝绿环境的切换速度是很快的,一旦发现绿环境有问题,可以快速切换回蓝环境,这个优势是灰度发布不具备的。

除了快速切换的优势外,蓝绿部署相比灰度发布也能避免灰度发布过程中可能出现的以下问题:

1、灰度实例接收生产流量时,因为瞬时流量过大造成灰度实例不可用,因为灰度实例的数量往往较少。不过也可以通过限制最大可接收流量比例等策略解决;

2、灰度阶段不能发现所有问题,因为只接收了小部分流量,所以难以发现数据库慢查询、性能等问题。

但是如上篇所说,蓝绿部署最大的缺点就是蓝绿环境同时存在期间造成较大的服务器资源浪费。所以建议根据具体场景和团队偏好,灵活选取低风险发布策略。

基于Istio实现

下面我们简单聊下基于Service Mesh实现灰度发布的方案,如果你所在的团队已经在使用Service Mesh,这可以极大的降低实现灰度发布或蓝绿部署的门槛,说到这里,不可否认的是,现在Service Mesh本身就是极高的门槛,Istio由于资源消耗、性能等因素,当前在生产环境应用的案例还是比较少。无论是基于Consul Mesh[7]还是基于istio实现[7]灰度发布[8],官方博客中都有详细的介绍,在这里就不详细说了。

与基于服务注册中心Consul和API网关的方案不同,由于Istio等Service Mesh产品提供了网关流量入口、服务流量控制的整套解决方案,我们基于 Istio 的 Virtual Service 和 Destination Rule 现有能力,针对网关和后端微服务使用同一套机制进行流量控制调度即可实现灰度发布和蓝绿部署,整个发布基本流程与基于API网关和Consul的实现方案是一致的。

总结

在低风险发布的两篇文章中,我们重点讨论了为什么要进行低风险发布、低风险发布的常见策略和如果实现低风险发布中的灰度发布和蓝绿部署。

上一篇中,低风险发布是我们避免线上严重事故的重要措施,也是适应产品迭代快节奏安全发布的行之有效的技术手段。常见的低风险发布策略包括滚动发布、灰度发布、蓝绿部署等,要根据技术背景、成本、团队偏好等多种因素选择适合自己团队的发布策略。

本篇中,我们重点介绍了如何实现低风险部署,我以灰度发布和蓝绿部署为例,详细论述了技术方案实施的前提条件、整体方案、详细方案和技术要点,介绍了基于Consul等注册中心和APISIX等API网关的具体实施方案,最后我们也探讨了如何基于Istio等Service Mesh产品实现。

技术发展日新月异,好的产品层出不穷,我们要循着问题出发,理清我们的核心诉求,这样才能甄别并选择最合适的解决方案。也许本篇给出的技术方案不久后就会过时,但是从问题出发,理清核心诉求,不断探寻更好技术方案的理念不会过时。

持续追求卓越,你我共勉!

参考:

[1] https://apisix.apache.org/zh/blog/2023/03/08/why-do-microservices-need-an-api-gateway/

[2] https://buoyant.io/service-mesh-manifesto

[3] https://tech.youzan.com/gray-deloyments-and-blue-green-deployments-practices-in-youzan/

[4] https://api7.ai/apisix

[5] https://developer.hashicorp.com/consul/docs/services/configuration/services-configuration-reference

[6] https://go-kratos.dev/docs/component/selector

[7] https://docs.api7.ai/apisix/documentation

[8] https://cloud.tencent.com/document/practice/457/48907

[9] https://developer.hashicorp.com/consul/tutorials/get-started-hcp/hcp-gs-canary-deployments

[7] https://istio.io/latest/zh/docs/concepts/traffic-management/

[8] https://istio.io/latest/zh/blog/2017/0.1-canary/