利用阿里云的MSE,上一个真正的灰度发布方案

1.MSE是啥

阿里云MSE(Microservices Engine)微服务引擎含以下三个主要模块:微服务注册中心、微服务治理和云原生网关。其中微服务治理功能中的标签路由,可以实现灰度功能。

利用阿里云的MSE,上一个真正的灰度发布方案

 2.灰度需求

除了流量入口(如桩服务、gateway服务等),所有后端服务都要能支持按版本分组;
将应用的相关版本隔离成一个独立的运行环境,通过设置流控路由规则,将满足规则的请求流量路由到目标版本应用。
示意图:

  • 线上服务版本v1111;
  • 新版版本v2222,即灰度版本;
  • 设置流量规则,满足规则的流量进入灰度版本,不满足则进入线上版本;

利用阿里云的MSE,上一个真正的灰度发布方案

 关键要求:

  1. 要能按流量规则准确路由;
  2. 流量必须是全链路的;

3.demo程序

device -> charge -> order -> base 四个服务,其中charge、order有2个版本。
架构:k8s + springcloud + eureka + feign
注册中心:

利用阿里云的MSE,上一个真正的灰度发布方案

所有服务接受两个参数:

  • str: 记录经过的链路,并在最后一个服务输出;
  • userId:根据该值做路由选择;

3.1.device \ charge \ order

接受并加工str,传递str \ userId

EchoController.java

package com.ykc.edas;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
 
@RestController
public class EchoController {
    @Autowired
    private EchoFeign echoFeign;
 
    @RequestMapping(value = "/device", method = RequestMethod.GET)
    public String echo(@RequestParam(value="str", required = false) String str,
                       @RequestParam(value="userId", required = false) String userId){
        return echoFeign.echo(str + "device.v1111 -> ",userId);
    }
 
}

EchoFeign.java

package com.ykc.edas;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
 
@RestController
@FeignClient(name = "edas-test2.charge")
public interface EchoFeign {
 
    @RequestMapping(value = "/charge", method = RequestMethod.GET)
    String echo(@RequestParam(value="str", required = false) String str,
                @RequestParam(value="userId", required = false) String userId);
}

3.2.base

返回str和userId

EchoController.java

package com.ykc.edas;
import org.springframework.web.bind.annotation.*;
 
@RestController
public class EchoController {
    @RequestMapping(value = "/base", method = RequestMethod.GET)
    public String echo(@RequestParam(value = "str", required = false) String str,
                       @RequestParam(value = "userId", required = false) String userId)
    {
        return str + "base.v1111  (userId: " + userId + ")";
    }
}

3.3.请求与输出

请求device服务:http://172.16.1.192:30000/device?userId="xxx"
如果路由线上版本,返回:device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: xxx)
如果路由灰度版本,返回:device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: xxx)

4.部署与配置

4.1.安装组件ack-mse-pilot

在指定K8S集群创建

利用阿里云的MSE,上一个真正的灰度发布方案

 创建后,在K8S中创建命名空间mse-pilot,并在命名空间下创建mse-pilot服务

利用阿里云的MSE,上一个真正的灰度发布方案

 4.2.开启服务所在的命名空间的"微服务治理"

可选:开启命名空间下所有服务的“微服务治理”;
生产实施时不需要执行,通过下一步对单个应用的配置逐个开启。

利用阿里云的MSE,上一个真正的灰度发布方案

 4.3.应用deployment配置

同一个服务的线上版本和灰度版本在K8S中是独立的deployment,对应不同的deployment配置

利用阿里云的MSE,上一个真正的灰度发布方案

 deployment新增三个配置
生产配置:

利用阿里云的MSE,上一个真正的灰度发布方案

 灰度配置:

利用阿里云的MSE,上一个真正的灰度发布方案

应用发布后,k8s中运行6个独立的deployment

利用阿里云的MSE,上一个真正的灰度发布方案

4.4.MSE配置

4.4.1.MSE应用列表

MSE应用列表中自动生成4个应用,同一个应用的生产版本、灰度版本汇聚到一起。

利用阿里云的MSE,上一个真正的灰度发布方案

4.4.2.创建标签路由规则

被流量入口服务直接调用的服务需要配置路由规则;反之不需要,因为路由规则可以“链路传递”;
如a、b、c、d、e五个服务,有以下调用链:
a -> b -> c -> d -> e
a -> c -> d -> e
则b、c服务都需要配置流量规则,d、e服务不需要配置。

利用阿里云的MSE,上一个真正的灰度发布方案

4.4.3.路由规则支持的参数类型

路由参数类型:

  1. cookie
  2. header
  3. parameter
  4. body content

路由参数表达式:

  • =
  • !=
  • >
  • >=
  • <
  • <=
  • in
  • 百分比
  • 模100

模100支持的表达式:

  • =
  • !=
  • >
  • >=
  • <
  • <=

测试案例混合使用了parameter和header类型:
条件模式:满足任一条件(另一个选项是满足所有条件)
具体条件:

  • parameter参数:userId in (111, 222, 333) 
  • parameter参数:userId 模100 = 1
  • header参数:mse-username in (msd01, msd02, msd03)

利用阿里云的MSE,上一个真正的灰度发布方案

4.4.4.实时流量对比查看

利用阿里云的MSE,上一个真正的灰度发布方案

4.4.5.灰度路由规则与百分比逻辑

利用阿里云的MSE,上一个真正的灰度发布方案

5.测试

userId是111、222、333的请求,会路由到灰度版本

[root@iZbp1ckjqrnvsfle1x9kzdZ deployment]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId='111';echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 111)
[root@iZbp1ckjqrnvsfle1x9kzdZ deployment]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId='222';echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 222)
[root@iZbp1ckjqrnvsfle1x9kzdZ deployment]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId='333';echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 333)

userId模100=1的请求,会路由到灰度版本

[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId=101;echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 101)
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId=201;echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 201)
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId=1001;echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111  (userId: 1001)

userId不在上述范围的请求,路由到生产版本

[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId=444;echo; done
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 444)
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device?userId=1002;echo; done
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: 1002)
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device;echo; done
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111  (userId: null)

header:mse-username在规则范围,路由到灰度版本

[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl -H "mse-username:msd01" http://172.16.1.192:30000/device;echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl -H "mse-username:msd02" http://172.16.1.192:30000/device;echo; done
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111
device.v1111 -> charge.v2222 --> order.v2222 --> base.v1111

header:mse-username不在上述范围,路由到生产版本

[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl -H "mse-username:msd04" http://172.16.1.192:30000/device;echo; done
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
[root@iZbp1ckjqrnvsfle1x9kzdZ ~]# for i in {1..10}; do curl http://172.16.1.192:30000/device;echo; done
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111
device.v1111 -> charge.v1111 --> order.v1111 --> base.v1111

6.MSE标签路由的实现与风险

利用javaAgent技术,加载插件arms-bootstrap-1.7.0-SNAPSHOT.jar,实现对用户代码的无侵入

利用阿里云的MSE,上一个真正的灰度发布方案

本质上是植入了阿里的SDK,从而实现根据自定义的路由规则分发请求。
关于风险,阿里云的回复:

  • 目前已经有很多大客户使用MSE灰度功能;
  • 只要测试阶段没有出现兼容性问题,上生产就不会有风险;
  • 代码实现了紧急动态降级能力,删除灰度规则即可触发功能降级。

我的补充:

  • 性能影响要经过压测;
  • 出现风险的最坏的兜底方案是将deployment中mse配置去掉,触发应用重启(msePilotAutoEnable: 'on'改为'off')。

7.灰度发布功能落地

mse方案对现有环境(包括研发和运维)的改动代价很小;且实施灰度方案后,如果不想用灰度或离开阿里云,代码不需要改动。

7.1.运维侧的改动

1)在K8S中创建组件,起一个名叫mse-pilot的容器;
2)需要开通的业务容器,deployment配置添加注解:

  • msePilotAutoEnable: 'on'
  • msePilotCreateAppName: device / charge / order / base
  • alicloud.service.tag: prod/gray

3) 流量入口服务(如桩服务、网关服务),deployment配置添加环境变量:
alicloud.service.header =  "mse-userId,mse-userName,mse-ipAddress,mse-deviceCode"

7.2.研发侧的改动

现提供的4种参数,header是最方便的:阿里云支持自动透传流量入口服务指定的header(其他3种参数需要服务自己实现透传)。
研发需要在流量入口服务中设置header,其他服务都不需要改动:
例如桩服务中设置mse-deviceCode,网关服务设置mse-userId, mse-userName, mse-ipAddress等。

上一篇:bus


下一篇:汽车电子专业知识篇(九)-charge pump的原理介绍