1.MSE是啥
阿里云MSE(Microservices Engine)微服务引擎含以下三个主要模块:微服务注册中心、微服务治理和云原生网关。其中微服务治理功能中的标签路由,可以实现灰度功能。
2.灰度需求
除了流量入口(如桩服务、gateway服务等),所有后端服务都要能支持按版本分组;
将应用的相关版本隔离成一个独立的运行环境,通过设置流控路由规则,将满足规则的请求流量路由到目标版本应用。
示意图:
- 线上服务版本v1111;
- 新版版本v2222,即灰度版本;
- 设置流量规则,满足规则的流量进入灰度版本,不满足则进入线上版本;
关键要求:
- 要能按流量规则准确路由;
- 流量必须是全链路的;
3.demo程序
device -> charge -> order -> base 四个服务,其中charge、order有2个版本。
架构:k8s + springcloud + eureka + feign
注册中心:
所有服务接受两个参数:
- 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集群创建
创建后,在K8S中创建命名空间mse-pilot,并在命名空间下创建mse-pilot服务
4.2.开启服务所在的命名空间的"微服务治理"
可选:开启命名空间下所有服务的“微服务治理”;
生产实施时不需要执行,通过下一步对单个应用的配置逐个开启。
4.3.应用deployment配置
同一个服务的线上版本和灰度版本在K8S中是独立的deployment,对应不同的deployment配置
deployment新增三个配置
生产配置:
灰度配置:
应用发布后,k8s中运行6个独立的deployment
4.4.MSE配置
4.4.1.MSE应用列表
MSE应用列表中自动生成4个应用,同一个应用的生产版本、灰度版本汇聚到一起。
4.4.2.创建标签路由规则
被流量入口服务直接调用的服务需要配置路由规则;反之不需要,因为路由规则可以“链路传递”;
如a、b、c、d、e五个服务,有以下调用链:
a -> b -> c -> d -> e
a -> c -> d -> e
则b、c服务都需要配置流量规则,d、e服务不需要配置。
4.4.3.路由规则支持的参数类型
路由参数类型:
- cookie
- header
- parameter
- body content
路由参数表达式:
- =
- !=
- >
- >=
- <
- <=
- in
- 百分比
- 模100
模100支持的表达式:
- =
- !=
- >
- >=
- <
- <=
测试案例混合使用了parameter和header类型:
条件模式:满足任一条件(另一个选项是满足所有条件)
具体条件:
- parameter参数:userId in (111, 222, 333)
- parameter参数:userId 模100 = 1
- header参数:mse-username in (msd01, msd02, msd03)
4.4.4.实时流量对比查看
4.4.5.灰度路由规则与百分比逻辑
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,实现对用户代码的无侵入
本质上是植入了阿里的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等。