作者:刘远
1. 概述
软件开发过程中,运用发布非常频频,通常状况下,开发或运维人员会将体系里一切服务一起上线,使得一切用户都运用上新版别。这样的操作时常会导致发布失利,或因发布前修正代码,线上呈现 Bug。
假定一个在线商城,每天都有很多的用户拜访,假如直接在一切用户中布置新版别运用,一旦呈现问题,一切用户都或许受到影响。相比之下,经过引进灰度发布战略,先将新版别的运用布置到少数的用户中,检查是否存在问题,假如没有,再逐渐扩展到更多的用户中,由此处理全量发布的各种弊端。
灰度发布是一种软件发布战略,它允许你在出产环境中渐进式布置运用,新版别只对部分用户可见,在问题呈现时尽量减少影响。在微服务体系架构中,服务间的依靠关系扑朔迷离,有时单个服务发版依靠多个服务一起运转联动,对这个新版别服务的上下游进行分组验证,这是微服务架构中特有的全链路灰度发布场景。
运用腾讯云微服务引擎 TSE 供给的网关和服务办理才能,能够在不修正任何业务代码的状况下,可视化装备灰度规矩,完结云上轻松易用的全链路灰度发布。
图1-1 全链路灰度场景架构
接下来演示云原生 API 网关+北极星网格构建的全链路灰度发布才能。下图模仿的云书城运用,由4个后端的微服务组成,选用 Spring Boot + Dubbo 完结,调用链包括:保藏功能、购买功能,用户办理功能和订单功能。用户经过前端页面拜访书城,进行内容浏览和书籍下单。
图1-2 云书城前端页面
2. 环境
2.1 云组件版别
本次实践选用如下组件:
●TSE-云原生网关:v2.5.1
●TSE-北极星网格: v1.12.0.1
●TKE:v1.20
●APM-SkyWalking: v8.5.0
咱们将运用布置在腾讯云 TKE 集群中,在实践出产中,全链路灰度对于运用的布置形式没有限制性要求,无论是 CVM 虚拟机,仍是自建容器环境,都能运用此方案。
2.2 灰度服务预备
项目模仿书城保藏服务改版,对 addUserFavoriteBook 接口进行修正:当时基线版别点击【保藏】按钮后,仅显现成功保藏字样,代码示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已保藏(version:1.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("保藏成功");
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服务反常,请稍后重试!");
}
return resp;
}
灰度版别修正后,页面点击【保藏】,会具体显现用户 ID 及书籍 ID,代码示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已保藏(version:2.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("用户 userId = " + userId + " 成功保藏 book isbn = " + isbn);
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服务反常,请稍后重试!");
}
return resp;
}
为了方便查看全链路服务当时版别,各服务将运用版别号回传给前端,在前端页面上显现。
图2-1 基线版别保藏服务
图2-2 灰度版别保藏服务
2.3 北极星网格接入
云书城架构中,服务发现才能目前是经过 Nacos 完结,在全链路灰度发布中,服务间需求运用到办理才能,咱们选用北极星网格对注册发现功能进行替换。项目挑选 Polaris-Dubbo 结构办法接入,经过更新北极星代码依靠,无需修正代码即可完结。比照原项目,有以下几点变化:
●修正 POM.XML 文件,增加北极星-Dubbo 服务注册、服务熔断及服务路由依靠包。
//服务注册插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-registry-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服务熔断插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-circuitbreaker-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服务路由插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-router-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
●在装备文件中,修正 Dubbo 运用注册中心接入地址。
dubbo.registry.address=polaris://x.x.x.x:8091?username=polaris&password=*****
修正后的项目,代码保持 Dubbo 标准办法进行注册及调用,无需变更。
//注册服务(服务端)
@DubboService(version = "${provicer.service.version}")
public class ProviderServiceImpl implements ProviderService {}
//服务调用(消费端)
@DubboReference(version = "1.0.0")
private ProviderService providerService;
2.4 容器服务布置
完结上述修正后,对微服务运用从头编译打包,推送至镜像仓库。在 TKE 集群中,咱们以 Deployment 办法下发运用。其间,保藏服务将基线版别和灰度版别都布置在集群中,其他服务仅布置一个版别,运用服务办理才能进行流量路由。
●基线版别保藏服务 YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service
namespace: qcbm
labels:
app: favorites-service
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service
version: v1
template:
metadata:
labels:
app: favorites-service
version: v1
spec:
containers:
- name: favorites-service
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
●灰度版别保藏服务YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service-new
namespace: qcbm
labels:
app: favorites-service-new
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service-new
version: v1
template:
metadata:
labels:
app: favorites-service-new
version: v1
spec:
containers:
- name: favorites-service-new
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-new-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
●前端页面服务以 Service 办法布置,经过 Ingress 形式对接云原生网关,供给集群外部拜访才能
apiVersion: v1
kind: Service
metadata:
name: qcbm-front
namespace: qcbm
spec:
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
selector:
app: qcbm-front
version: v1
type: NodePort
2.5 云原生网关接入
云原生网关支持将流量直通到 Service 地点的 Pod,无需经过 NodePort 中转。在操控台里绑定 TKE 集群,输入 Service 名,网关经过 Endpoint 里搜集 Pod IP,在网关里主动生成 Kong Services 和 Upstream。一旦 TKE Service 发生变化,Ingress Controller 会动态更新 Upstream 里的 Target 信息。
后续操作依据 Kong 里主动生成的 Services,装备基线及灰度网关路由规矩。
图2-3 云原生网关绑定TKE集群服务
图2-4 云原生网关主动生成 Services
图2-5 云原生网关主动生成 Upstreams
2.6 链路追寻接入
单体体系年代追寻的范围只局限于栈追寻,而在微服务环境中,追寻不只限于调用栈,一个外部恳求需求内部若干服务的联动,完好的一次恳求会跨越多个服务。链路追寻的主要意图是排查毛病,如当时问题点处于调用链的哪一部分,各服务间输入输出是否契合预期,经过链路追寻,能够查看到服务间的网络传输信息,以及各服务内部的调用仓库信息。
选用 APM 的 SkyWalking 协议办法进行上报,首先修正 SkyWalking 文件夹里的 agent.config 文件,装备接入点、Token 、自定义空间和服务名称。
collector.backend_service=x.x.x.x:11800
agent.authentication=xxxxxx
agent.service_name=favorites-service-new
agent.namespace=QCBM
在 Dockerfile 中,修正运用程序的发动命令行,以 JavaAgent 办法指定 SkyWalking Agent 的路径 :
java -javaagent:/app/skywalking/skywalking-agent.jar -jar favorites-service-new.jar
布置后,能够在操控台里验证运用拓扑正确性。
图2-6 运用拓扑图
3. 处理方案
经过四个阶段的操作,完结保藏服务的全链路灰度发布,别离是实例打标、网关路由、微服务路和标签透传。
图3-1 全链路灰度发布方案
3.1 实例打标及标签透传
实例打标,指的是经过实例标签标识不同的运用,将基线版别与灰度版别区别开。一般有两种办法进行实例打标:一是结构主动同步,将运用名,环境变量等做为实例标签;二是用 K8S 布置时的 CRD Label 作为实例标签。本实践中运用 Dubbo 结构里的 applicaiton 字段来区别基线版别和灰度版别运用。
图3-2 网关路由规矩
网关层对灰度流量进行了染色,在微服务调用过程中,需求将染色标签在每一跳进行传递,使得各微服务都能够识别到灰度流量,并进行正确路由处理。
图3-3 标签透传示意图
外部染色标签在入口处,以 HTTP Header 办法存在,在 Dubbo-Gateway 服务处,编码将 HTTP Header 转化为 Dubbo attachment,使得染色标签在微服务内部中持续透传,最终依据 attachment 里的取值做服务间调用依据。
private FavoriteService add(FavoriteService favoriteService, String result) {
logger.info("header:{}", result);
RpcContext.getContext().setAttachment("gray", result == null ? "false" : result);
return favoriteService;
}
3.2 网关路由
网关作为体系流量入口,担任将外部流量依照一定的用户特征,切分流入灰度版别和基线版别。并对灰度流量进行染色打标,供服务办理中心动态路由规矩匹配运用。在实践出产中,一般有三种分流的办法:
●经过匹配用户恳求的 Header 参数,进行流量区别。
●经过匹配用户恳求的 Host 特征,进行流量区别。
●经过流量百分比进行区别,运用云原生网关才能,将其间一部分流量进行染色处理。
本次实践针对前两种切分办法进行介绍。
图3-4 网关路由示意图
3.3 微服务路由
北极星网格在全链路灰度中,充当服务办理中心的角色,处理架构中注册发现、毛病容错、流量操控和安全问题。经过北极星网格操控台中的装备,把基线和灰度恳求,路由到不同的实例分组上,并将灰度恳求固定在灰度版别服务间进行传递和处理。
图3-5 动态路由示意图
咱们创立了2条服务间动态路由规矩,基线和灰度恳求依照不同匹配规矩,路由至对应实例分组。完结中,北极星依据恳求音讯内容来对恳求匹配,并依据优先级进行流量调度。
图3-6 办理中心路由规矩
4. 场景
4.1 经过Header特征全链路灰度
1)场景阐明
假如客户端拜访期望一致域名,比如实践中的 gray.qcbm.yunnative.com,咱们能够经过传入不同的 Header,把恳求别离路由到基线和灰度环境。当出产环境中存在多个客户分组,或多条灰度路由规矩,也能够经过云原生网关进行自定义 Header 染色,运用不同染色标签,进行服务间路由搭配。
图4-1 经过Header特征全链路灰度
2)装备办法
在云原生网关上创立两条路由规矩:
●qcbm-front-router-web,HOST为gray.qcbm.yunnative.com,Header为app:web,路由到Dubbo-Gateway服务
●qcbm-front-router-mobile,HOST为gray.qcbm.yunnative.com,Header为app:mobile,路由到Dubbo-Gateway服务,开启染色(gray:true)
图4-2 云原生网关路由规矩
服务办理中心能够直接运用现成的 app:web 或 app:mobile 标签路由,也能够对路由恳求新增染色,运用染色标签路由,优化复杂环境办理。这里咱们开启云原生网关的 Request-Transformer 插件,对 qcbm-front-router-mobile 路由报文进行修正,增加 gray:true 头,运用该染色标识进行路由办理。
图4-3 路由染色插件
图4-4 增加染色标识
qcbm-front-router-mobile 路由规矩的恳求抵达 Dubbo-Gateway 后,一旦需求拜访保藏服务(FavoriteService),gray:true 染色标签会射中北极星网格灰度服务路由,挑选调用 remote.application为favorites-service-new 的服务实例。此实例分组为咱们布置的 favorites-service-new 灰度版别 deployment。
图4-5 灰度服务路由
qcbm-front-router-web 路由规矩的恳求会射中无染色标签的基线服务路由,调用 remote.application 为 favorites-service 的服务实例。此实例分组为咱们布置的 favorites-service 基线版别 deployment。
图4-6 基线服务路由
3)成果验证
咱们借用 chrome 浏览器插件 ModHeader,对拜访恳求按需增加 Header。
●当增加 app:mobile 后,浏览器拜访 gray.qcbm.yunnative.com,咱们的拜访链路如下:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
页面显现如下:
图4-7 灰度恳求页面
一起,也能够经过链路监控观察到,gateway-service(基线服务)正确的恳求到 favorite-service-new(灰度服务),一起 favorite-service-new 正确恳求到 store-service(基线服务):
图4-8 灰度恳求链路概况
●当增加 app:web 后,浏览器拜访 gray.qcbm.yunnative.com,此刻咱们的拜访链路如下:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service](基线)
页面显现如下:
图4-9 基线恳求页面
经过链路监控,能够观察到,gateway-service(基线服务)正确的恳求到 favorite-service(基线服务),一起 favorite-service 正确恳求到 store-service(基线服务):
图4-10 基线恳求链路概况
在北极星网格中,咱们能够针对链路的每一跳装备路由规矩,每个主调服务都能够定义归于自己的匹配规矩。
4.2 经过域名特征全链路灰度
1)场景阐明
同样的,也能够选用域名对恳求进行区别,预期 web 端用户选用 gray.web.yunnative.com 拜访基线环境;mobile 端用户选用 gray.mobile.yunnative.com 拜访灰度环境。这种分流办法,适用于网关依据用户登录信息,动态分流的场景,不同的用户在登录时,登录模块依据验证信息,返回302报文,给予不同的重定向域名,用户此刻运用不同的域名去拜访,云原生网关经过 HOST 来做流量区别,动态染色 HTTP 恳求。
图4-11 经过域名特征全链路灰度
2)装备办法
在云原生网关上创立两条路由规矩:
●qcbm-front-router-web,HOST 为 gray.web.yunnative.com,路由到 Dubbo-Gateway 服务。
●qcbm-front-router-mobile,HOST 为 gray.mobile.yunnative.com,路由到 Dubbo-Gateway 服务,开启染色(gray:true)。
和场景1相似,qcbm-front-router-mobile 路由规矩的恳求抵达 Dubbo-Gateway 后,一旦拜访保藏服务(FavoriteService),gray:true 染色标签会射中北极星网格灰度路由,调用 remote.application 为 favorites-service-new 的实例分组;而 qcbm-front-router-web 路由规矩的恳求会射中无染色标签的网格基线路由,调用 remote.application 为 favorites-service 的实例分组,拜访基线环境。
3)成果验证
●浏览器拜访gray.mobile.yunnative.com时,染色标签会被打上,此刻拜访链路如下:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
页面显现如下:
图4-12 灰度恳求页面
一起,也能够经过链路监控观察到,gateway-service(基线服务)正确的恳求到 favorite-service-new(灰度服务),一起 favorite-service-new 正确恳求到 store-service(基线服务):
图4-13 灰度恳求链路概况
●当拜访gray.web.yunnative.com时,无染色标签,此刻咱们的链路如下:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service](基线)
页面显现如下:
图4-14 灰度恳求页面
经过链路监控,能够观察到,gateway-service(基线服务)正确的恳求到 favorite-service(基线服务),一起 favorite-service 正确恳求到 store-service(基线服务):
图4-15 基线恳求链路概况
4.3 灰度服务毛病搬运
1)场景阐明
在灰度发布过程中,能够经过监测体系功能和用户反馈来评估新功能的质量。假如新功能在测验期间体现杰出,能够持续将其面向更多用户,替换原版别运用。假如呈现任何问题,能够对灰度服务进行拜访熔断处理,及时修正问题,然后持续灰度测验。
图4-16 灰度服务毛病搬运
2)装备办法
在北极星网格上装备熔断规矩,合作多实例分组路由规矩,完结灰度服务毛病 Failover。在全链路灰度场景基础上,在北极星网格操控台加上一条熔断规矩。
●当 delUserFavoriteBook 接口过错率大于10%,阈值大于10个,熔断 Favorite-Service-New 灰度服务实例分组,同一 Deployment 里的一切 Pod 进入半开状态。
●半开时间60秒,期间一旦恳求成功则以为服务恢复,封闭熔断。
图4-17 灰度服务熔断规矩
接下来,在网格灰度路由中,增加低优先级实例分组,该分组为基线实例。一旦灰度实例分组被熔断,恳求会去拜访基线实例分组,直到灰度服务修正,熔断封闭。
图4-18 灰度服务路由规矩
3)成果验证
布置一个新的“毛病“保藏服务,Dubbo 程序延用 application=favorites-service-new 标签(为区别运用,这里毛病灰度服务命名为 Favorites-Service-New-Bad),确保原路由规矩可用。该“毛病”程序修正了保藏服务的 delUserFavoriteBook 接口代码,当拜访时直接抛出反常,模仿服务毛病。代码如下所示:
public Response<String> delUserFavoriteBook(Long userId, Long isbn) {
String hostAddress;
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
hostAddress = "ip获取失利";
}
throw new RuntimeException("删去保藏-毛病 ip:" + hostAddress);
}
浏览器拜访 gray.mobile.yunnative.com 时,此刻拜访链路如下:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service-New-Bad](毛病灰度)
页面显现如下:
图4-19 灰度恳求页面
进入保藏页面,点击【删去】,程序报错,显现调用反常。
图4-20 灰度服务删去报错
经过链路追寻,也能够查看到服务反常。
图4-21 运用调用拓扑
图4-22 灰度服务删去链路过错
当毛病过错大于10次,favorite-service-new 灰度实例分组被熔断,灰度路由进行低优先级方针挑选,流量回源至基线实例分组favorite-service,此刻测验删去功能正常,因为此刻咱们的拜访链路从头变为:
[云原生网关] --> [Dubbo-Gateway] --> [Favorite-Service](基线正常)
页面显现如下,服务调用已回源:
图4-22 灰度恳求页面(已回源)
5. 总结
在灰度发布实施前,需求依照如下三方面,对全体流程进行计划:
●方针:在开始灰度发布之前,需求了解发布的方针是什么,比如说是测验新版别的功能,仍是功能兼容性等,以便在灰度时进行对应的测验和观测。
●灰度战略:有很多种灰度战略可供挑选,例如按用户特征来灰度、按流量来灰度、按地域来灰度等。经过体系用户的特点,挑选最合适的灰度战略。
●灰度范围:在灰度发布过程中,应当能随时操控哪些用户能够拜访新版别,当呈现问题时,能将恳求快速回滚到旧版别上。
灰度发布过程中,确认流量是否现已按计划切换到灰度实例分组,经过监控和日志,检查各服务是否正常运转,是否契合预期。
确认本次发布成功后,能够依次对老版别分组的实例进行翻滚升级,多次升级完结灰度发布,一旦呈现过错执行回退,有序操控发布节奏。最后,依据实践运用状况,删去或保留网关和办理中心的动态路由规矩。
腾讯云TSE供给了完好的全链路灰度发布处理方案,适用各种发布流程,无需侵入代码,经过可视化装备灰度规矩,有效地处理了微服务全链路灰度发布难完结的问题,让灰度发布更快捷、更顺畅。