其他
基于 Istio 的全链路灰度方案探索和实践
作者|曾宇星(宇曾)
审核&校对:曾宇星(宇曾)
编辑&排版:雯燕
1
背景
ASM Pro 产品功能架构图:
2
场景说明
入口流量的 tag 标签,一般是在网关层面基于类似 tag 插件的方式,将请求流量进行打标。 比如将 userid 处于一定范围的打上代表灰度的 tag,考虑到实际环境网关的选择和实现的多样性,网关这块实现不在本文讨论的范围内。
3
实现原理
Inbound 是指请求发到 App 的入口流量,Outbond 是指 App 向外发起请求的出口流量。
4
实现流量打标
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: "*"
获取逻辑:先根据协议上下文或者头(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 部分,具体存储到哪一个字段中可配置。
5
按流量标签路由
在 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
labels 用于匹配应用中带特定标记的节点(endpoint);
通过 ServiceEntry 用于指定属于特定 subset 的 IP 地址,注意这种方式与labels指定逻辑不同,它们可以不是从注册中心(K8s 或者其他)拿到的地址,直接通过配置的方式指定。适用于 Mock 环境,这个环境下的节点并没有向服务注册中心注册。
在 VirtualService 中基于 subset
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
先打到日常环境,当日常环境没有服务资源时,再打到主干环境。
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
将打了主干环境标并且本机环境是 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
6
按(环境)标路由
K8s 场景,通过业务部署时自动带上对应环境/分组 label 标识即可,也就是采用K8s 本身作为元数据管理中心。
非 K8s 场景,可以通过微服务已集成的服务注册中心或者元数据配置管理服务(metadata server)来集成实现。
7
应用场景延伸
8
总结
支持多语言、多协议。
统一配置模板 TrafficLabel, 配置简单且灵活,支持多级别的配置(全局、namespace 、pod 级别)。
支持路由 fallback 实现降级。
大促前的性能压测。在线上压测的场景中,为了让压测数据和正式的线上数据实现隔离,常用的方法是对于消息队列,缓存,数据库使用影子的方式。这就需要流量打标的技术,通过 tag 区分请求是测试流量还是生产流量。当然,这需要 Sidecar 对中间件比如 Redis、RocketMQ 等进行支持。
单元化路由。常见的单元化路由场景,可能是需要根据请求流量中的某些元信息比如 uid,然后通过配置得出对应所属的单元。在这个场景中,我们可以通过扩展 TrafficLabel 定义获取“单元标”的函数来给流量打上“单元标”,然后基于“单元标”将流量路由到对应的服务单元。