微服务治理实践 | 金丝雀发布

拟墨画扇 提交于 2020-03-11 17:54:38

前言

阿里巴巴集团内部有不少故障是因为发布直接或间接引起。因此提升发布的质量,减少错误的发生,是有效减少线上故障的一个关键环节。

为什么大部分的故障和发布相关?因为发布是整个功能更新到线上的最后一个环节,一些研发过程中累计的问题,在最后发布环节才会触发。同时发布本身也是一个复杂的过程,在发布过程中,往往容易出现一些错误操作或者遗漏关键操作。

日常发布中,我们常常会有如下一些错误的想法:

  • 这次改动的内容比较小,而且上线要求比较急,就不需要测试直接发布上线好了
  • 发布不需要走灰度流程,快速发布上线即可
  • 灰度发布没有什么用,就是一个流程而已,发布完就直接发布线上,不用等待观察
  • 虽然灰度发布很重要,但是灰度环境很难搭建,耗时耗力优先级并不高

这些想法都可能让我们进行一次错误的发布。

阿里巴巴内部有安全生产三板斧概念: 可灰度、可观测、可回滚。所有研发同学必须要掌握发布系统的灰度、观测和回滚功能如何使用。

今天我们来聊聊灰度发布。

灰度发布策略

灰度发布是发布整个过程中一个非常重要的环境。目前灰度发布策略有这几种:

  • 蓝绿发布(Blue-Green Deployment)

1

通过部署两套环境来解决新老版本的发布问题。如果新版本(New Version)发生问题要进行回滚的时候,直接通过切流将流量全部切到老版本(Old Version)上。

优点:升级切换和回退比发布回滚迅速
缺点:成本较高,需要部署两套环境。如果新版本中基础服务出现问题,会瞬间影响全网用户;如果新版本有问题也会影响全网用户

  • 金丝雀发布(Canary Release)

2

优点:灵活,策略自定义,可以按照流量或具体的内容进行灰度(比如不同账号,不同参数),出现问题不会影响全网用户
缺点:没有覆盖到所有的用户导致出现问题不好排查

  • 滚动发布(Rolling Release)

3

金丝雀发布的一种变化。通过分批发布的方式进行多批发布(比如一共 9 个实例,分 3 批,每次 3 个实例发布),适合大规模应用发布

优点:出现问题不会影响全网用户,适合大规模应用发布
缺点:发布和回滚周期较长

本文将介绍 Spring Cloud & Dubbo 微服务框架下的金丝雀发布。

微服务金丝雀发布开源实现

微服务金丝雀发布的核心是服务路由,只要能控制服务路由的逻辑,就能确定流量的走向,确定流量走向也就意味着可以控制路由到任意节点。

目前 Spring Cloud 开源国内有Nepxion Discovery这款框架支持灰度发布,或是开发者自己实现 Ribbon 里对应的接口即可。 Apache Dubbo 开发者实现 Dubbo 提供的 Router 或 LoadBalance 即可;另外 Dubbo Admin 界面提供了条件路由、标签路由功能也可以完成动态路由功能。

Spring Cloud

有这么一个 Spring Cloud 服务调用场景:

4

  • Query Parameter 中存在 name=jim 的请求,路由到 192.168.1.1 节点
  • Header 中存在 Test=1 的请求,路由到 192.168.1.2 节点
  • Cookie 中存在 lang=zh-cn 的请求,路由到 192.168.1.3 节点

这些不同类型的请求数据决定着 Spring Cloud 的服务路由逻辑。Spring Cloud 服务路由组件为 Netflix Ribbon(Spring Cloud Hoxton 引入了 Spring Cloud LoadBalancer 用于替换 Netflix Ribbon)。Ribbon 内部的 ILoadBalancer 接口用于获取实例(Server)列表信息,IRule 接口用于获取最终的确定的实例(Server)。因此,如何使用/扩展这两块接口是 Spring Cloud 动态路由的核心。

不论是 Netflix Ribbon 或者 Spring Cloud LoadBalancer,它们在路由的过程中都无法获取 Request 信息,Request 信息存储着用户请求内容中的所有数据。为了这个这个解决,需要引入 ThreadLocal 来透传 Request 数据。

Apache Dubbo

Apache Dubbo 不论是 Router 或者 LoadBalance,它们提供的方法内部都可以获取到 Invocation。有了 Invocation 之后可以获取调用的方法、调用的参数类型、调用的参数值、Attachment。

这些 Invocation 里的内容可以决定过滤掉哪些 Invoker,比如定义了一个 CanaryRouter 对于 getEnv 方法路由到灰度 Invoker,否则路由到其它 Invoker(每个节点注册的时候设置一个自定义的 parameter gray 到 url 中,表示这是一个灰度节点):

public class CanaryRouter extends AbstractRouter {
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        List<Invoker> normal = new ArrayList<>();
        List<Invoker> canary = new ArrayList<>();
        for(Invoker invoker : invokers) {
            if(invoker.getUrl().getParameter("gray", false)) {
                canary.add(invoker);
            } else {
                normal.add(invoker);
            }
        }
        if(invocation.getMethodName().equals("getEnv")) {
            return canary;
        } else {
            return normal;
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWqaccIt-1583908424781)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBIbrePJ-1583908424781)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]

EDAS 金丝雀发布实践

EDAS 金丝雀发布支持 Apache Dubbo 和 Spring Cloud 微服务框架。金丝雀发布的底层使用 Java Agent 技术通过字节码增强 Apache Dubbo 和 Spring Cloud 的路由逻辑。由于是使用 Agent 技术,而且在抽象类上进行了拦截,这使得对 Apache Dubbo 和 Spring Cloud 的 的版本以及注册中心都有了更全面的支持。

  • 版本: Apache Dubbo 支持 2.5.x, 2.6.x 以及 2.7.x;Spring Cloud 支持 Dalston、Edgware、Finchley 以及 Hoxton(暂不支持 Hoxton 里的 Spring Cloud LoadBalancer 负载均衡组件)
  • 注册中心: 支持 Zookeeper,Consul,Eureka,Nacos,Etcd 等注册中心

下面,我们开始体验 EDAS 上的金丝雀发布。

创建应用

创建 Provider 和 Consumer 应用:

5

Provider 请注意填写至少 2 个 Pod 数:

6

全部创建完毕后,调用 consumer 对外暴露的地址确认 consumer 可以顺利调用 provider 服务。

金丝雀发布

Provider 进行应用部署,选择金丝雀发布:
7

设置新版本号:

8

设置对应的规则(本文使用按比例灰度,90% 的流量进入灰度实例):

9

等待金丝雀发布完毕:
10

金丝雀发布结果验证

金丝雀发布完毕后,再次调用 consumer 对外暴露的地址确认金丝雀是否生效。或者可以在发布单页面中查看灰度监控数据:

11

12

这里查看了 3 分钟监控,89.89% 的流量进入了灰度实例(时间越久,比例越准确),基本满足按照比例的灰度部署。

当结果符合预期并进行全量发布时,发布单页面点击 “下一步” 即可:
13

规则解释

Spring Cloud

Spring Cloud 灰度规则目前支持两种类型:

  • 按比例灰度,可设置 0 - 100 之间的整数
  • 按内容灰度,可以根据参数的内容进行灰度

14

其中 path 表示请求路径(不填表示支持所有路径),参数类型支持选择 Cookie,Header 或 Parameter,条件支持 “=”, “>”, “<”, “>=”, “<=”, “!=”, 白名单, 取模。

条件列表中的每一项支持 “与” 或者 “或” 运算。

举个例子,这是 consumer 调用 provider 的一个请求:

URL: http://custom-provider/goods/query
Parameter: goodsId=JL110
Header: ENV=Prod

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cr4lwI88-1583908424789)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBApP7CX-1583908424791)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]

那么规则可以这样设置:

15

Apache Dubbo

Apache Dubbo 灰度规则目前支持两种类型:

  • 按比例灰度,可设置 0 - 100 之间的整数
  • 按内容灰度,可以根据接口参数的内容进行灰度

其中条件支持 “=”, “>”, “<”, “>=”, “<=”, “!=”, 白名单, 取模。

条件列表中的每一项支持 “与” 或者 “或” 运算。

举个例子,定义一个接口如下:

public interface CanaryService {

    String call(String str, int num1,
        Integer num2, String[] strArr, Map<String, String> map,
        List<String> list, User user);

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bk9vjfpH-1583908424793)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wom0TTru-1583908424793)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]

这是 consumer 调用 provider 的参数信息:

str=str
num1=1
num2=2
strArr=[1, 2, 3]
map={"1":"1", "2", "3"}
list=[0, 1, 2]
User=User{name=name, age=20}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Srs08rDv-1583908424794)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOhlANJb-1583908424795)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== “点击并拖拽以移动”)]

那么规则可以这样设置:

16

参数 0、1、2 是 String、int 和 Integer 对象,无需设置表达式,直接进行比较。
参数 3 表示一个 String[] 数组,表达式 [0] 表示取第 1 个元素,数组中的第 1 个元素为 1。
参数 4 表示一个 Map<String, String>,表达式 .get(“1”) 表示取 key 为 “1” 的元素,Map 中 key 为 “1” 的元素是 “1”。
参数 5 表示一个 List 集合,表达式 .get(1)表示取第 2 个元素,数组中的第 2 个元素为 1。
参数 6 表示一个自定义 User 对象(有 name 和 age 两个属性),表达式 .getName() 表示调用 getName() 方法,该方法返回 name 属性的值,这里传递的是 name。

ECS 集群使用

上述内容介绍的是在 K8S 集群下的操作。如若在 ECS 集群下使用,需要确保至少存在 2 个分组(如下截图存在 “默认分组” 和 “gray” 这两个分组):

17

部署应用,选择金丝雀发布:

18

上传新的部署包,设置新版本号,选择灰度分组:

19

后续规则的操作跟 K8S 集群一致。

金丝雀发布后回滚

当金丝雀发布后,发现有 bug 存在,需要进行应用回滚。

发布单页面直接点击 “立即回滚”:

20

金丝雀发布实现细节

Apache Dubbo 金丝雀底层实现的原理是通过 Java Agent 技术动态添加一个 Router,该 Router 内部使用了 Spring 的表达式 SPEL 进行参数,表达式解析结果判断是否需要对 Invoker 列表进行修改。如果是常规请求,删除灰度节点;如果是灰度请求,删除正常节点。

Spring Cloud 金丝雀底层的实现细节是对 ILoadBalancer 接口对应的实现类返回的 Server 列表进行修改。如果是常规请求,删除灰度节点;如果是灰度请求,删除正常节点。

这里如何判断哪些节点是灰度节点呢? 这会跟应用发布流程绑定,当使用 ECS 集群需要选择灰度分组,K8S 集群需要填写灰度 Pod 个数。这些灰度实例或灰度 Pod 启动的时候会带上一个灰度标表示自己是一个灰度实例或 Pod(Spring Cloud 写入到 metadata 持久化到注册中心;Apache Dubbo 写入到 custom parameter 持久化到注册中心)。

另外一个问题: 灰度规则如何保存呢?我们在 Agent 里通过 Nacos Configuration 的监听去监听对应 dataId 的数据变化,这里的 dataId 跟每个应用 id 绑定(应用 id 也通过跟灰度标一样的机制持久化到注册中心)。每次应用发布都会在 id 对应的 dataId 中写入灰度规则,Agent 监听并在内存进行修改。


查看更多:https://yqh.aliyun.com/detail/6713?utm_content=g_1000107307

上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!