Article / 文章中心

深入剖析全链路灰度技术内幕(2)

发布时间:2021-12-16 点击数:990
03

全链路灰度的解决方案

Cloud Native


如何在实际业务场景中去快速落地全链路灰度呢?目前,主要有两种解决思路,基于物理环境隔离和基于逻辑环境隔离。
1
 物理环境隔离


物理环境隔离,顾名思义,通过增加机器的方式来搭建真正意义上的流量隔离。

物理环境隔离

这种方案需要为要灰度的服务搭建一套网络隔离、资源独立的环境,在其中部署服务的灰度版本。由于与正式环境隔离,正式环境中的其他服务无法访问到需要灰度的服务,所以需要在灰度环境中冗余部署这些线上服务,以便整个调用链路正常进行流量转发。此外,注册中心等一些其他依赖的中间件组件也需要冗余部署在灰度环境中,保证微服务之间的可见性问题,确保获取的节点 IP 地址只属于当前的网络环境。


这个方案一般用于企业的测试、预发开发环境的搭建,对于线上灰度发布引流的场景来说其灵活性不够。况且,微服务多版本的存在在微服务架构中是家常便饭,需要为这些业务场景采用堆机器的方式来维护多套灰度环境。如果您的应用数目过多的情况下,会造成运维、机器成本过大,成本和代价远超收益;如果应用数目很小,就两三个应用,这个方式还是很方便的,可以接受的。


2
 逻辑环境隔离


另一种方案是构建逻辑上的环境隔离,我们只需部署服务的灰度版本,流量在调用链路上流转时,由流经的网关、各个中间件以及各个微服务来识别灰度流量,并动态转发至对应服务的灰度版本。如下图:

逻辑环境隔离


上图可以很好展示这种方案的效果,我们用不同的颜色来表示不同版本的灰度流量,可以看出无论是微服务网关还是微服务本身都需要识别流量,根据治理规则做出动态决策。当服务版本发生变化时,这个调用链路的转发也会实时改变。相比于利用机器搭建的灰度环境,这种方案不仅可以节省大量的机器成本和运维人力,而且可以帮助开发者实时快速的对线上流量进行精细化的全链路控制。


那么全链路灰度具体是如何实现呢?通过上面的讨论,我们需要解决以下问题:
  1. 链路上各个组件和服务能够根据请求流量特征进行动态路由
  2. 需要对服务下的所有节点进行分组,能够区分版本
  1. 需要对流量进行灰度标识、版本标识
  2. 需要识别出不同版本的灰度流量

接下来,会介绍解决上述问题需要用到的技术。

标签路由


标签路由通过对服务下所有节点按照标签名和标签值不同进行分组,使得订阅该服务节点信息的服务消费端可以按需访问该服务的某个分组,即所有节点的一个子集。服务消费端可以使用服务提供者节点上的任何标签信息,根据所选标签的实际含义,消费端可以将标签路由应用到更多的业务场景中。

标签路由



节点打标


那么如何给服务节点添加不同的标签呢?在如今火热的云原生技术推动下,大多数业务都在积极进行容器化改造之旅。这里,我就以容器化的应用为例,介绍在使用 Kubernetes Service 作为服务发现和使用比较流行的 Nacos 注册中心这两种场景下如何对服务 Workload 进行节点打标。


在使用 Kubernetes Service 作为服务发现的业务系统中,服务提供者通过向 ApiServer 提交 Service 资源完成服务暴露,服务消费端监听与该 Service 资源下关联的 Endpoint 资源,从 Endpoint 资源中获取关联的业务 Pod 资源,读取上面的 Labels 数据并作为该节点的元数据信息。所以,我们只要在业务应用描述资源 Deployment 中的 Pod 模板中为节点添加标签即可。

 Kubernetes Service


在使用 Nacos 作为服务发现的业务系统中,一般是需要业务根据其使用的微服务框架来决定打标方式。如果 Java 应用使用的 Spring Cloud 微服务开发框架,我们可以为业务容器添加对应的环境变量来完成标签的添加操作。比如我们希望为节点添加版本灰度标,那么为业务容器添加`spring.cloud.nacos.discovery.metadata.version=gray`,这样框架向Nacos注册该节点时会为其添加一个标签`verison=gray`。

Nacos

流量染色


请求链路上各个组件如何识别出不同的灰度流量?答案就是流量染色,为请求流量添加不同灰度标识来方便区分。我们可以在请求的源头上对流量进行染色,前端在发起请求时根据用户信息或者平台信息的不同对流量进行打标。如果前端无法做到,我们也可以在微服务网关上对匹配特定路由规则的请求动态添加流量标识。此外,流量在链路中流经灰度节点时,如果请求信息中不含有灰度标识,需要自动为其染色,接下来流量就可以在后续的流转过程中优先访问服务的灰度版本。

分布式链路追踪


还有一个很重要的问题是如何保证灰度标识能够在链路中一直传递下去呢?如果在请求源头染色,那么请求经过网关时,网关作为代理会将请求原封不动的转发给入口服务,除非开发者在网关的路由策略中实施请求内容修改策略。接着,请求流量会从入口服务开始调用下一个微服务,会根据业务代码逻辑形成新的调用请求,那么我们如何将灰度标识添加到这个新的调用请求,从而可以在链路中传递下去呢?
从单体架构演进到分布式微服务架构,服务之间调用从同一个线程中方法调用变为从本地进程的服务调用远端进程中服务,并且远端服务可能以多副本形式部署,以至于一条请求流经的节点是不可预知的、不确定的,而且其中每一跳的调用都有可能因为网络故障或服务故障而出错。分布式链路追踪技术对大型分布式系统中请求调用链路进行详细记录,核心思想就是通过一个全局唯一的 traceid 和每一条的 spanid 来记录请求链路所经过的节点以及请求耗时,其中 traceid 是需要整个链路传递的。


借助于分布式链路追踪思想,我们也可以传递一些自定义信息,比如灰度标识。业界常见的分布式链路追踪产品都支持链路传递用户自定义的数据,其数据处理流程如下图所示:

数据处理流程

逻辑环境隔离——基于 SDK


上面我们详细介绍了实现全链路灰度所需要的几种技术,如果想为现有的业务接入全链路灰度能力,不可避免的需要为业务使用的开发框架 SDK 进行改造。首先,需要支持动态路由功能,对于 Spring Cloud、Dubbo 开发框架,可以对出口流量实现自定义 Filter,在该 Filter 中完成流量识别以及标签路由。同时需要借助分布式链路追踪技术完成流量标识链路传递以及流量自动染色。此外,需要引入一个中心化的流量治理平台,方便各个业务线的开发者定义自己的全链路灰度规则。基于 SDK 实现方式的图例如下:

逻辑环境隔离sdk


逻辑环境隔离——基于 Java Agent


基于 SDK 方式的弊端在于需要业务进行 SDK 版本升级,甚至会涉及到业务代码的变动。企业内部各个微服务虽然使用同一种开发框架,但很难保证框架版本是一致的,所以不得不为每一个版本维护一份全链路灰度的代码。业务代码与 SDK 代码紧耦合,SDK 版本迭代会触发业务不必要的发版变更,对业务的侵入性比较强。


另一种比较流行的方式是基于字节码增强技术在编译时对开发框架进行功能拓展,这种方案业务无感知,以无侵入方式为业务引入全链路灰度能力。基于 Java Agent 的实现方式的图例如下:

Java Agent


但仍然无法避免是开发者需要为业务使用版本不一致的开发框架维护对应的 Java Agent 的版本。如果您比较倾向于这种无侵入的方案但又不想自己来维护,您可以选择阿里云 MSE 服务治理产品,该产品就是一款基于 Java Agent 实现的无侵入式企业生产级服务治理产品,您不需要修改任何一行业务代码,即可拥有不限于全链路灰度的治理能力,并且支持近 5 年内所有的 Spring Boot、Spring Cloud 和 Dubbo。


逻辑环境隔离——基于 Service Mesh


在业务系统的微服务架构中,如果存在大量使用不同的技术栈、语言栈的微服务,Java Agent 的方式就无能为力了。我们可能需要为每一个语言的 SDK 编写和维护全链路灰度代码,不仅需要不同语言栈的开发者,而且涉及到语言无关的 bug 修复时需要全语言版本的 SDK 共同升级,这种代价不见得比基于物理环境隔离方案小。


那有没有一种与语言无关的方案呢?有,下一代微服务架构服务网格,Service Mesh。它将分布式服务的通信层抽象为单独的一层,在这一层中实现负载均衡、服务发现、认证授权、监控追踪、流量控制等分布式系统所需要的功能。显然,我们所需的全链路灰度能力也可以在这个流量治理基础设施层来实现。幸运的是,服务网格明星产品Istio以声明式 API 资源对流量治理进行了统一抽象,借助于 VirtualService 和 DestinationRule 治理规则可以很容易实现全链路灰度的效果,并且Istio集成了各种主流的分布式链路追踪框架。基于 Service Mesh 的实现方式的图例如下:

Service Mesh



在实际生产环境中,服务多版本并行开发是很常见的事情,而且版本迭代速度非常快。版本每次变更都需要修改 VirtualSerivice 资源中路由匹配规则,另外 VirtualSerivice 资源中并没有提供容灾能力。比如存在一条路由规则访问服务提供方的某个灰度版本,如果目标服务不存在该灰度版本或者不可用,按照目前 Istio 的实现是仍然将流量转发至该版本,缺乏容灾机制。还有一种业务场景,如果我们希望对处于一定 UID 范围的用户流量转发指定灰度环境,是无法通过 Istio 现有的流量治理规则实现的。此时,您可以选择阿里云服务网格产品 ASM,是一个统一管理微服务应用流量、兼容 Istio 的托管式平台。ASM 针对上述两个场景都有应对方案,轻松解决您在多语言场景下的全链路灰度诉求。


三种方式对比


下表是三种方式对比,从多个方面进行了对比。

三种微服务治理产品方式对比


  • 如果您倾向于使用无侵入式的 Java Agent 的方式,但又担心自建带来的稳定性问题,您可以选择 MSE 微服务治理产品,该产品是阿里巴巴内部多年在微服务治理领域的沉淀的产出,经历了各种大促考验。

  • 如果您倾向于使用语言无关、无侵入式的 Service Mesh 的方式,但又担心自建带来的稳定性问题,您可以选择阿里云 ASM 产品,相比开源 Istio,在功能性、稳定性和安全性都有很大的提升。

3
 流量入口:网关


在分布式应用中,作为流量入口的网关是不可或缺的。在全链路灰度场景中,就要求微服务网关具备丰富的流量治理能力,支持服务多版本路由,支持对特定路由规则上的请求进行动态打标。对于入口服务可见性问题,网关需要支持多种服务发现方式。安全性问题上,网关作为集群对外的入口可以对所有请求流量进行认证鉴权,保障业务系统不被非法流量入侵。


在虚拟化时期的微服务架构下,业务通常采用流量网关 + 微服务网关的两层架构,流量网关负责南北向流量调度和安全防护,微服务网关负责东西向流量调度和服务治理。在容器和 K8s 主导的云原生时代,Ingress 成为 K8s 生态的网关标准,赋予了网关新的使命,使得流量网关 + 微服务网关合二为一成为可能。阿里云 MSE 发布的云原生网关在能力不打折的情况下,将两层网关变为一层,不仅可以节省 50% 的资源成本,还可以降低运维及使用成本。最重要的是,云原生网关支持与后端微服务治理联动实现端到端的全链路灰度。

流量入口网关