无灰度不发布
名词解释
- 灰度策略:用于将请求转发到不同节点、服务上的路由规则。
- 灰度追踪:随服务调用在请求上下文环境所传递的灰度路由信息。
一、说明
灰度部署可以抽象为函数target = gray_rules(request_context),其中request_context是服务调用的上下文环境,gray_rules实现了灰度路由规则,而target则是经过处理后的目标端地址。
常见的灰度发布策略:
- 将流量按比例转发到不同目标端。
- 将流量按策略转发到不同目标端。
目前希望通过灰度部署实现正式版部署前的线上验证问题,所以暂不实现按流量比例转发等AB Test场景。通常灰度部署涉及四个基本问题:
- 灰度流量识别:如何识别出请求流量是灰度流量还是正常流量。
- 灰度策略下发:如何下发灰度策略给服务,更进一步是发送给指定服务还是全部服务。
- 灰度策略执行:如何执行灰度策略。
- 灰度追踪:灰度流量标识如何在服务调用链中传递。
二、业界方案
2.1、Istio
2.2、Spring cloud gray
通过gray-admin下方灰度策略到全部服务,sdk实现灰度判断逻辑。
2.3、总结
- 灰度策略下发:灰度策略通过全局控制中心下发到全部节点或服务。
- 灰度追踪:灰度追踪需要自定义代码实现并且随着服务调用逐层传递。
三、我们的方案
3.1、灰度发布过程
- 联系运维预先建立好灰度部署所需要的域名、k8s service或mq topic。
- 联系运维在流量入口处配置灰度策略,例如需要对哪个服务、域名、mq topic进行灰度部署。
- 在风火轮中部署灰度服务。
- 灰度版本验证。
- 灰度版本下线,如果对mq topic进行灰度需要将消息全部消费完后在下线。
- 联系运维在流量入口处清除灰度策略。
- 风火轮中部署正式版本。
3.2、实现方案
这里我们解决上文提到的灰度部署四个基本问题中的三个:灰度流量识别、灰度策略下发、灰度追踪,而灰度策略执行则由下文“流量层灰度控制”、“数据层灰度控制”介绍。
3.2.1、灰度流量识别
在流量入口处判断request context中是否带有灰度标识grayid,grayid用于标识唯一用户身份,考虑到非登录场景,建议grayid选择设备id等唯一标识符,通过grayid的值来识别流量是否是灰度流量。
3.2.2、灰度策略下发
有别于istio、spring cloud gray的方案,在我们的方案中,灰度策略不进行全局下发而是携带在灰度追踪中。假定本次请求的灰度追踪中含有{type=”dubbo”, name = “serviced”, version = “gray” },当调用ServiceD时则调用ServiceD的gray版本。
灰度策略协议:结合公司目前技术栈,灰度部署支持服务、域名、kafka topic三个层面的灰度部署。现阶段version字段取值只有gray,考虑扩展性增加了version字段,方便后续能够部署多个灰度版本。
[
{
"type": "dubbo",
"name": "${name}", // dubbo application name
"version": "${version}"
},
{
"type": "http",
"name": "${url}", // 域名
"version": "${version}"
},
{
"type": "kafka",
"name": "${topic1}", // Topic名称
"version": "${version}"
},
{
"type": "kafka",
"name": "${topic2}", // Topic名称
"version": "${version}"
},
// ...
]
3.2.3、灰度追踪
在流量入口处通过grayid识别出灰度流量后,添加灰度策略到request context中,随服务调用逐层传递。
以公司目前访问链路为例解释一下上述过程。假设已经部署服务D的灰度版本, 前端请求的http header中带有灰度标识grayid,kong作为流量第一入口点判断流量是否是灰度流量,如果是灰度流量则设置灰度策略{type=”dubbo”, name = “serviced”, version = “gray” },并在转发请求时添加到http header的grayrules中。服务B接收到请求后,如果在本次请求上下文中调用服务D,则根据灰度策略调用服务D的gray版本。
3.2.4、流量层灰度控制
如下图,目前公司在用的技术栈及访问链路是:
- 大部分服务部署在k8s集群内,少量服务部署在vm中。
- http服务与dubbo服务并存。
- dubbo目前是2.x版本,dubbo调用不经过k8s service。
- k8s service由运维手工配置。
- mq使用kafka。
结合现有系统访问路径,可以抽象出以下几种场景:
- kong转发流量
- dubbo调用
- k8s service转发流量
- 其他七层负载转发请求
- kafka传递消息
3.2.4.1、Kong转发流量
获取http header中grayid,如果grayid的值等于预定值则注入灰度策略。如果对上游服务进行灰度则转发到灰度上游,否则仍然转发到默认上游。
灰度域名命名规则${domain}-gray.abite.com
3.2.4.2、Dubbo调用
利用dubbo tag机制实现灰度发布。如上图所示,服务B通过dubbo调用服务D,假定服务D部署一个正式版本和一个灰度版本,灰度部署详细过程如下:
- 启动服务D灰度版本时设置dubbo tag=gray。
- 当服务B调用服务D时,如果request context中带有灰度追踪{ type=”dubbo”, name = “serviced”, version = “gray” }则调用服务D的gray版本。
3.2.4.3、K8s service转发流量
如上图所示,服务B通过k8s service调用服C,假定服务C部署一个正式版本和一个灰度版本。当服务B调用服务C时,如果context request中带有灰度追踪{ type=”http”, name = “servicec”, version = “gray” }则调用服务C的gray版本。
3.2.4.4、其他七层负载转发请求
实现原理、方式同“k8s service转发流量”。
3.2.5、数据层灰度控制
3.2.5.1、Kafka
目前采用同集群多Topic方式进行灰度,Kong&风火轮需要配置相应的灰度Topic和灰度版本号
风火轮需要下发的配置如下:
GRAY_KAFKA_TOPICS=
生产者
灰度追踪通过Http、Dubbo、Kafka等载体传递并保存在请求上下文的ThreadLocal中,如果当前发送消息对应的Topic在灰度追踪中则在原Topic名称后面加上灰度版本号,即消息会发送到对应的灰度Topic中。同时,生产者在发送灰度消息前需要将灰度追踪注入消息Header中,透传给下游应用。
消费者
消费者应用启动时,根据风火轮配置的灰度Topic和灰度版本号,在消费者订阅原Topic时在原Topic后面加上灰度版本号,即订阅的是灰度Topic。同时,灰度消费者接收到灰度消息时将灰度追踪注入约定好的ThreadLocal变量中。
3.2.5.2、MySQL
暂不统一实现,业务系统根据自身特点实现。
3.2.5.3、ElasticSearch
暂不统一实现,业务系统根据自身特点实现。
3.2.5.4、Redis
暂不统一实现,业务系统根据自身特点实现。
3.3、流量切换
通常灰度版本验证无误后可以在运行时进行流量切换变为正式版本,考虑到尽快实现灰度部署,暂时不实现这个功能。灰度版本切换到正式版本,关键技术点:
- 移除dubbo tag变为正式版本。
- 修改kafka消费者消费topic到正式topic。
- 修改k8s service或nginx指向。
四、挑战
- 理解Reqeust Context传递对研发有挑战。
- Reqeust Context的全链路改造需要一段时间。
- 数据层兼容需研发实现。