乐趣区

关于istio:基于-Istio-的全链路灰度方案探索和实践

作者|曾宇星(宇曾)

审核 & 校对:曾宇星(宇曾)

编辑 & 排版:雯燕

背景

微服务软件架构下,业务新性能上线前搭建残缺的一套测试零碎进行验证是相当费人费时的事,随着所拆分出微服务数量的一直增大其难度也愈大。这一整套测试零碎所需付出的机器老本往往也不低,为了保障利用新版本上线前的性能正确性验证效率,这套零碎还必须始终独自保护好。当业务变得宏大且简单时,往往还得筹备多套,这是整个行业独特面临且难解的老本和效率挑战。如果能在同一套生产零碎中实现新版本上线前的性能验证的话,所节约的人力和财力是相当可观的。

除了开发阶段的性能验证,生产环境中引入灰度公布能力更好地管制新版本软件上线的危险和爆炸半径。灰度公布是将具备肯定特色或者比例的生产流量调配到须要被验证的服务版本中,以察看新版本上线后的运行状态是否合乎预期。

阿里云 ASM Pro(相干链接请见文末)基于 Service Mesh 所构建的全链路灰度计划,能很好帮忙解决以上两个场景的问题。

ASM Pro 产品性能架构图:

外围能力应用的就是上图扩大的流量打标和按标路由以及流量 Fallback 的能力,上面具体介绍阐明。

场景阐明

全链路灰度公布的常见场景如下:

以 Bookinfo 为例,入口流量会带上冀望的 tag 分组,sidecar 通过获取申请上下文(Header 或 Context) 中的冀望 tag,将流量路由散发到对应 tag 分组,若对应 tag 分组不存在,默认会 fallback 路由到 base 分组,具体 fallback 策略可配置。接下来详细描述具体的实现细节。

入口流量的 tag 标签,个别是在网关层面基于相似 tag 插件的形式,将申请流量进行打标。比方将 userid 处于肯定范畴的打上代表灰度的 tag,思考到理论环境网关的抉择和实现的多样性,网关这块实现不在本文探讨的范畴内。

上面咱们着重探讨基于 ASM Pro 如何做到全链路流量打标和实现全链路灰度。

实现原理

Inbound 是指申请发到 App 的入口流量,Outbond 是指 App 向外发动申请的进口流量。

上图是一个业务利用在开启 mesh 后典型流量门路:业务 App 接管到一个内部申请 p1,接着调用背地所依赖的另一个服务的接口。此时,申请的流量门路是 p1->p2->p3->p4,其中 p2 是 Sidecar 对 p1 的转发,p4 是 Sidecar 对 p3 的转发。为了实现全链路灰度,p3 和 p4 都须要获取到 p1 进来的流量标签,能力将申请路由到标签所对应的后端服务实例,且 p3 和 p4 也要带上同样的标签。关键在于,如何让标签的传递对于利用齐全无感,从而实现全链路的标签透传,这是全链路灰度的关键技术。ASM Pro 的实现是基于分布式链路追踪技术(比方,OpenTracing、OpenTelemetry 等)中的 traceId 来实现这一性能。

在分布式链路追踪技术中,traceId 被用于惟一地标识一个残缺的调用链,链路上的每一个利用所收回的扇出(fanout)调用,都会通过分布式链路追踪的 SDK 将源头的 traceId 给带上。ASM Pro 全链路灰度解决方案的实现正是建设在这一分布式应用架构所宽泛驳回的实际之上的。

上图中,Sidecar 原本所看到的 inbound 和 outbound 流量是齐全独立的,无奈感知两者的对应关系,也不分明一个 inbound 申请是否导致了多个 outbound 申请的产生。换句话说,图中 p1 和 p3 两个申请之间是否有对应关系 Sidecar 并不知情。

在 ASM Pro 全链路灰度解决方案中,通过 traceId 将 p1 和 p3 两个申请做关联,具体说来依赖了 Sidecar 中的 x-request-id 这个 trace header。Sidecar 外部保护了一张映射表,其中记录了 traceId 和标签的对应关系。当 Sidecar 收到 p1 申请时,将申请中的 traceId 和标签存储到这张表中。当收到 p3 申请时,从映射表中查问取得 traceId 所对应的标签并将这一标签退出到 p4 申请中,从而实现全链路的打标和按标路由。下图大抵示例了这一实现原理。

换句话说,ASM Pro 的全链路灰度性能须要利用应用分布式链路追踪技术。如果想使用这一技术的利用没有应用分布式链路追踪技术的话不可避免地波及到肯定的革新工作。对于 Java 利用来说,仍能够思考采纳 Java Agent 以 AOP 的形式让业务无需革新地实现 traceId 在 inbound 和 outbound 之间透传。

实现流量打标

ASM Pro 中引入了全新的 TrafficLabel CRD 用于定义 Sidecar 所需透传的流量标签从哪里获取。上面所例举的 YAML 文件中,定义了流量标签起源和须要将标签存储 OpenTracing 中(具体是 x-trace 头)。其中流量标的名为 trafficLabel,取值顺次从 $getContext(x-request-id) 到最初从本地环境的 $(localLabel) 中获取。

apiVersion: istio.alibabacloud.com/v1beta1
kind: TrafficLabel
metadata:
  name: default
spec:
  rules:
  - labels:
      - name: trafficLabel
        valueFrom:
        - $getContext(x-request-id)  // 若应用 aliyun arms, 对应为 x -b3-traceid
        - $(localLabel)
    attachTo:
    - opentracing
    # 示意失效的协定,空为都不失效,* 为都失效
    protocols: "*"

CR 定义蕴含两块,即标签的获取和存储。

  • 获取逻辑:先依据协定上下文或者头(Header 局部)中的定义的字段获取流量标签,如果没有,会依据 traceId 通过 Sidecar 本地记录的 map 获取, 该 map 表中保留了 traceId 对应流量标识的映射。若 map 表中找到对应映射,会将该流量打上对应的流量标,若获取不到,会将流量标取值为本地部署对应环境的 localLabel。localLabel 对应本地部署的关联 label,label 名为 ASM_TRAFFIC_TAG。

本地部署对应环境的标签名为 ”ASM_TRAFFIC_TAG”,理论部署能够联合 CI/CD 零碎来关联。

  • 存储逻辑:attachTo 指定存储在协定上下文的对应字段,比方 HTTP 对应 Header 字段,Dubbo 对应 rpc context 局部,具体存储到哪一个字段中可配置。

有了 TrafficLabel 的定义,咱们晓得如何将流量打标和传递标签,但光有这个还不足以做到全链路灰度,咱们还须要一个能够基于 trafficLabel 流量标识来做路由的性能,也就是“按标路由”,以及路由 fallback 等逻辑,以便当路由的目的地不存在时,能够实现降级的性能。

按流量标签路由

这一性能的实现扩大了 Istio 的 VirtualService 和 DestinationRule。

在 DestinationRule 中定义 Subset

自定义分组 subset 对应的是 trafficLabel 的 value

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: myapp
spec:
  host: myapp/*
  subsets:
  - name: myproject            # 我的项目环境
    labels:
      env: abc
  - name: isolation            # 隔离环境
    labels:
      env: xxx                 # 机器分组
  - name: testing-trunk        # 骨干环境
    labels:
      env: yyy
  - name: testing              # 日常环境
    labels:
      env: zzz
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: myapp
spec:
  hosts: 
        - myapp/*
  ports:
  - number: 12200
    name: http
    protocol: HTTP
    endpoints:
      - address: 0.0.0.0
        labels:
            env: abc
      - address: 1.1.1.1
        labels:
            env: xxx
      - address: 2.2.2.2
        labels:
            env: zzz
      - address: 3.3.3.3
        labels:
            env: yyy

Subset 反对两种指定模式:

  • labels 用于匹配利用中带特定标记的节点(endpoint);
  • 通过 ServiceEntry 用于指定属于特定 subset 的 IP 地址,留神这种形式与 labels 指定逻辑不同,它们能够不是从注册核心(K8s 或者其余)拿到的地址,间接通过配置的形式指定。实用于 Mock 环境,这个环境下的节点并没有向服务注册核心注册。

在 VirtualService 中基于 subset

1)全局默认配置

  • route 局部能够按程序指定多个 destination,多个 destination 之间依照 weight 值的比例来调配流量。
  • 每个 destination 下能够指定 fallback 策略,case 标识在什么状况下执行 fallback,取值:noinstances(无服务资源)、noavailabled(有服务资源然而服务不可用),target 指定 fallback 的指标环境。如果不指定 fallback,则强制在该 destination 的环境下执行。
  • 按标路由逻辑,咱们通过革新 VirtualService,让 subset 反对占位符 $trafficLabel, 该占位符 $trafficLabel 示意从申请流量标中获取指标环境,对应 TrafficLabel CR 中的定义。

全局默认模式对应泳道,也就是单个环境内关闭,同时指定了环境级别的 fallback 策略。自定义分组 subset 对应的是 trafficLabel 的 value

配置样例如下:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: default-route
spec:
  hosts:                     # 对所有利用失效
  - */*
  http:
  - name: default-route
    route:
    - destination:
        subset: $trafficLabel
      weight: 100
      fallback:
         case: noinstances
         target: testing-trunk
    - destination:
            host: */*
        subset: testing-trunk    # 骨干环境
      weight: 0
      fallback:
        case: noavailabled
        target: testing
    - destination:
        subset: testing          # 日常环境
      weight: 0
      fallback:
        case: noavailabled
        target: mock
    - destination:
            host: */*
        subset: mock             # Mock 核心
       weight: 0

2)集体开发环境定制

  • 先打到日常环境,当日常环境没有服务资源时,再打到骨干环境。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: projectx-route
spec:
  hosts:                   # 只对 myapp 失效
  - myapp/*
  http:
  - name: dev-x-route
    match:
      trafficLabel:
      - exact: dev-x       # dev 环境: x
    route:
    - destination:
            host: myapp/*
        subset: testing          # 日常环境
      weight: 100
      fallback:
        case: noinstances
        target: testing-trunk
    - destination:
            host: myapp/*
        subset: testing-trunk    # 骨干环境
      weight: 0

3)反对权重配置

将打了骨干环境标并且本机环境是 dev-x 的流量,80% 打到骨干环境,20% 打到日常环境。当骨干环境没有可用的服务资源时,流量打到日常。

sourceLabels 为本地 workload 对应的 label

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: dev-x-route
spec:
  hosts:                   # 对哪些利用失效(不反对多利用配置)- myapp/*
  http:
  - name: dev-x-route
    match:
      trafficLabel:
      - exact: testing-trunk # 骨干环境标
      sourceLabels:
      - exact: dev-x  # 流量来自某个我的项目环境
    route:
    - destination:
            host: myapp/*
        subset: testing-trunk # 80% 流量打向骨干环境
      weight: 80
      fallback:
        case: noavailabled
        target: testing
    - destination:host: myapp/*
        subset: testing       # 20% 流量打向日常环境
      weight: 20

按(环境)标路由

该计划依赖业务部署利用时带上相干标识(例子中对应 label 为 ASM_TRAFFIC_TAG: xxx),常见为环境标识,标识能够了解是服务部署的相干元信息,这个依赖上游部署零碎 CI/CD 零碎的串联,大略示意图如下:

  • K8s 场景,通过业务部署时主动带上对应环境 / 分组 label 标识即可,也就是采纳 K8s 自身作为元数据管理核心。
  • 非 K8s 场景,能够通过微服务已集成的服务注册核心或者元数据配置管理服务(metadata server)来集成实现。

注:ASM Pro 自研开发了 ServiceDiretory 组件(能够参看 ASM Pro 产品性能架构图),实现了多注册核心对接以及部署元信息的动静获取;

利用场景延长

上面是典型的一个基于流量打标和按标路由实现的多套开发环境治理性能;每个开发者对应的 Dev X 环境只需部署有版本更新的服务即可;如果须要和其余开发者联调,能够通过配置 fallback 将服务申请 fallback 流转到对应开发环境即可。如下图的 Dev Y 环境的 B -> Dev X 环境的 C。

同理,将 Dev X 环境等同于线上灰度版本环境也是能够的,对应能够解决线上环境的全链路灰度公布问题。

总结

本文介绍的基于“流量打标”和“按标路由”能力是一个通用计划,基于此能够较好地解决测试环境治理、线上全链路灰度公布等相干问题,基于服务网格技术做到与开发语言无关。同时,该计划适应于不同的 7 层协定,以后已反对 HTTP/gRpc 和 Dubbo 协定。

对应全链路灰度,其余厂商也有一些计划,比照其余计划 ASM Pro 的解决方案的长处是:

  • 反对多语言、多协定。
  • 对立配置模板 TrafficLabel,配置简略且灵便,反对多级别的配置(全局、namespace、pod 级别)。
  • 反对路由 fallback 实现降级。

基于“流量打标”和“按标路由”能力还能够用于其余相干场景:

  • 大促前的性能压测。在线上压测的场景中,为了让压测数据和正式的线上数据实现隔离,罕用的办法是对于音讯队列,缓存,数据库应用影子的形式。这就须要流量打标的技术,通过 tag 辨别申请是测试流量还是生产流量。当然,这须要 Sidecar 对中间件比方 Redis、RocketMQ 等进行反对。
  • 单元化路由。常见的单元化路由场景,可能是须要依据申请流量中的某些元信息比方 uid,而后通过配置得出对应所属的单元。在这个场景中,咱们能够通过扩大 TrafficLabel 定义获取“单元标”的函数来给流量打上“单元标”,而后基于“单元标”将流量路由到对应的服务单元。

相干链接:

1)阿里云 ASM Pro:
https://servicemesh.console.a…

退出移动版