关于微服务:基于腾讯云微服务引擎TSE-轻松实现云上全链路灰度发布

102次阅读

共计 11412 个字符,预计需要花费 29 分钟才能阅读完成。

作者:刘远

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 提供了残缺的全链路灰度公布解决方案,实用各种公布流程,无需侵入代码,通过可视化配置灰度规定,无效地解决了微服务全链路灰度公布难实现的问题,让灰度公布更便捷、更顺利。

正文完
 0