在微服务场景中,应用间的调用是随机的。当您部署的Spring Cloud应用或Dubbo应用存在升级版本时,可能会导致无法将具有一定特征的流量路由到应用的目标版本。通过MSE提供的全链路灰度能力,您无需修改业务代码,就可以实现端到端的全链路流量控制。泳道可以将应用的相关版本隔离成一个独立的运行环境。通过设置泳道规则,可以将满足规则的请求流量路由到目标版本的应用。本文介绍如何通过配置MSE云原生网关实现端到端的全链路灰度。
全链路灰度实现流程
使用限制
由于全链路灰度功能整合了标签路由功能(此处指的是MSE微服务治理的标签路由,与MSE云原生网关的标签路由无关),因此不推荐已经加入全链路流量控制的应用配置金丝雀发布和标签路由规则。
全链路灰度能力,对于Java应用的限制详见微服务治理支持的Java框架。
全链路灰度能力,MSE云原生网关版本需要大于等于2.0.2版本,升级网关版本详见升级MSE云原生网关。
场景示例
本文以电商架构中的下单场景为例,介绍从MSE云原生网关到微服务的全链路流控功能。假设应用的架构由MSE云原生网关以及后端的微服务架构(Spring Cloud)组成,后端调用链路有3个:交易中心(A)、商品中心(B)、库存中心(C),可以通过客户端或者HTML来访问后端服务,这些服务之间通过MSE Nacos注册中心实现服务发现。
客户下单后流量从MSE云原生网关进来,调用交易中心(A),交易中心(A)再调用商品中心(B),商品中心(B)调用下游的库存中心(C)。总之,调用链路为:客户>MSE云原生网关>A>B>C。
随着业务不断迭代,新功能需要上线,涉及到应用A和应用C同时发布新版本。在新版本正式上线之前,需要同时对应用A和应用C进行灰度验证,确认无误后方可正式上线。
通过MSE云原生网关和MSE微服务治理提供的全链路灰度能力,您可以端到端构建从网关到多个后端服务的全链路灰度,控制具有一定特征的灰度流量始终路由到应用对应的灰度环境,满足您同时灰度验证多个服务的诉求。此外,在应用无目标灰度版本时,自动容灾到正式环境中。
名词解释
MSE云原生网关:MSE云原生网关是兼容K8s Ingress标准的下一代网关产品,支持ACK容器和Nacos等多种服务发现方式,支持多种认证登录方式快速构建安全防线。更多信息,请参见云原生网关概述。
泳道:为相同版本应用定义的一套隔离环境。只有满足了流控路由规则的请求流量才会路由到对应泳道里的打标应用。一个应用可以属于多个泳道,一个泳道可以包含多个应用,应用和泳道是多对多的关系。
泳道组:泳道的集合。泳道组的作用主要是为了区分不同团队或不同场景。
基线环境:未打标的应用属于基线环境,是其他环境的兜底环境。
准备工作
开启MSE微服务治理
重要 请确保MSE Java 探针的版本3.2.3及以上,否则会影响业务运行。
步骤一:搭建业务应用的基线环境
步骤1:部署后端业务应用的基线版本
- 登录容器服务控制台。
- 在左侧导航栏,单击集群,然后单击目标集群名称。
- 在左侧导航栏,选择。
- 在页面上方,选择集群的命名空间,然后单击右上角的使用YAML创建资源。
部署基线应用。
服务来源为MSE Nacos
复制如下YAML文件内容,部署A、B、C三个应用的基线版本。
说明 代码中的{nacos server address}需要替换成您创建的MSE Nacos的内网域名,同时需要去掉大括号{}
。
展开查看YAML文件
# 应用A的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
labels:
app: spring-cloud-a
msePilotCreateAppName: spring-cloud-a
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-a
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 30
periodSeconds: 60
env:
- name: spring.cloud.nacos.discovery.server-addr
value: {nacos server address}
- name: dubbo.registry.address
value: 'nacos://{nacos server address}:8848'
---
# 应用B的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-b
template:
metadata:
labels:
app: spring-cloud-b
msePilotCreateAppName: spring-cloud-b
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-b
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20002
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 30
periodSeconds: 60
env:
- name: spring.cloud.nacos.discovery.server-addr
value: {nacos server address}
- name: dubbo.registry.address
value: 'nacos://{nacos server address}:8848'
---
# 应用C的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
labels:
app: spring-cloud-c
msePilotCreateAppName: spring-cloud-c
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-c
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 30
periodSeconds: 60
env:
- name: spring.cloud.nacos.discovery.server-addr
value: {nacos server address}
- name: dubbo.registry.address
value: 'nacos://{nacos server address}:8848'
服务来源为容器服务
复制如下YAML文件,部署Nacos,模拟自建服务注册中心的场景。
展开查看YAML文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nacos-server
spec:
replicas: 1
selector:
matchLabels:
app: nacos-server
template:
metadata:
labels:
msePilotAutoEnable: "off"
app: nacos-server
spec:
containers:
- name: nacos-server
image: 'registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2'
env:
- name: MODE
value: standalone
- name: JVM_XMS
value: 512M
- name: JVM_XMX
value: 512M
- name: JVM_XMN
value: 256M
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 15
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8848
timeoutSeconds: 3
readinessProbe:
failureThreshold: 5
initialDelaySeconds: 15
periodSeconds: 15
successThreshold: 1
tcpSocket:
port: 8848
timeoutSeconds: 3
resources:
requests:
cpu: '1'
memory: 2Gi
dnsPolicy: ClusterFirst
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: nacos-server
spec:
type: ClusterIP
ports:
- name: nacos-server-8848-8848
port: 8848
protocol: TCP
targetPort: 8848
- name: nacos-server-9848-9848
port: 9848
protocol: TCP
targetPort: 9848
selector:
app: nacos-server
复制如下YAML文件,部署A、B、C三个应用的基线版本。
展开查看YAML文件
# 应用A的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
labels:
app: spring-cloud-a
msePilotCreateAppName: spring-cloud-a
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-a
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 30
periodSeconds: 60
# 通过Nacos的Service访问自建Nacos
env:
- name: spring.cloud.nacos.discovery.server-addr
value: nacos-server
- name: dubbo.registry.address
value: 'nacos://nacos-server:8848'
---
# 应用B的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-b
template:
metadata:
labels:
app: spring-cloud-b
msePilotCreateAppName: spring-cloud-b
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-b
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20002
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 30
periodSeconds: 60
# 通过Nacos的Service访问自建Nacos
env:
- name: spring.cloud.nacos.discovery.server-addr
value: nacos-server
- name: dubbo.registry.address
value: 'nacos://nacos-server:8848'
---
# 应用C的基线版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
labels:
app: spring-cloud-c
msePilotCreateAppName: spring-cloud-c
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-c
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 30
periodSeconds: 60
# 通过Nacos的Service访问自建Nacos
env:
- name: spring.cloud.nacos.discovery.server-addr
value: nacos-server
- name: dubbo.registry.address
value: 'nacos://nacos-server:8848'
复制如下YAML文件,为入口应用A创建Service。
展开查看YAML文件
# 暴露应用A
apiVersion: v1
kind: Service
metadata:
name: sc-a
namespace: default
spec:
ports:
- port: 20001
protocol: TCP
targetPort: 20001
selector:
app: spring-cloud-a
type: ClusterIP
步骤2:通过MSE云原生网关暴露应用A
情况一:新服务
如果您未在云原生网关上添加过应用A,可以通过如下操作暴露应用A。
登录MSE管理控制台。选择云原生网关 > 网关列表,单击已创建的云原生网关实例。在左侧导航栏的路由管理页面,选择服务页签,单击创建服务。具体操作,请参见创建服务。
来源为容器服务
服务来源:选择容器服务。
命名空间:选择default。
服务列表:选择sc-a。
来源为MSE Nacos
服务来源:选择MSE Nacos。
命名空间:选择public。
服务列表:选择sc-A。
在路由管理页面,选择路由页签。单击创建路由,为服务sc-a/sc-A创建路由完成对外暴露。具体操作,请参见创建路由。
配置项 | 描述 |
路径(Path) | 选择匹配条件为前缀是,路径为/a。 |
路由指向 | 选择单服务。 |
后端服务 | 选择 sc-A |
情况二:线上已有服务
如果您的实际场景是针对已导入的服务,可通过如下操作暴露应用A。
登录MSE管理控制台。选择云原生网关 > 网关列表,选择已创建的云原生网关实例。在左侧导航栏路由管理页面,选择路由页签。编辑已有的路由,以便暴露服务。
配置项 | 描述 |
路径(Path) | 选择匹配条件为前缀是,路径为/a。 |
路由指向 | 选择单服务。 |
后端服务 | 选择 sc-A |
步骤3:测试基线版本流量
登录MSE网关管理控制台,并在顶部菜单栏选择地域。
在左侧导航栏,选择云原生网关 > 网关列表,单击目标网关名称。
在左侧导航栏,单击基本概览。
在网关入口页签,查看SLB的入口地址。
使用curl命令,测试基线版本流量,发现流量经过了A、B和C的基线版本。如下所示:
# 测试命令
curl x.x.1.1/a
# 测试结果
A[10.0.3.178][config=base] -> B[10.0.3.195] -> C[10.0.3.201]
如测试结果所示,B和C输出结果的应用名后面没有版本,则代表访问对应应用的基线版本。
步骤二:搭建业务应用的灰度环境
如果业务应用要发布新功能,新功能要求应用A和应用C同时发版,这时就需要借助全链路灰度来同时验证不同应用的灰度版本。
步骤1:部署后端业务应用的灰度版本
- 登录容器服务控制台。
- 在左侧导航栏,单击集群,然后单击目标集群名称。
- 在左侧导航栏,选择。
- 在页面上方,选择集群的命名空间,然后单击右上角的使用YAML创建资源。
复制以下YAML文件内容,部署A、C两个应用的灰度版本。
服务来源为MSE Nacos
说明 代码中的{nacos server address}需要替换成您创建的MSE Nacos的内网域名,同时需要去掉大括号{}
。
展开查看YAML文件
# 应用A的灰度版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a-gray
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-a-gray
template:
metadata:
labels:
alicloud.service.tag: gray
app: spring-cloud-a-gray
msePilotCreateAppName: spring-cloud-a
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-a
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 30
periodSeconds: 60
env:
- name: spring.cloud.nacos.discovery.server-addr
value: {nacos server address}
- name: dubbo.registry.address
value: 'nacos://{nacos server address}:8848'
---
# 应用C的灰度版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c-gray
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-c-gray
template:
metadata:
labels:
alicloud.service.tag: gray
app: spring-cloud-c-gray
msePilotCreateAppName: spring-cloud-c
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-c
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 30
periodSeconds: 60
env:
- name: spring.cloud.nacos.discovery.server-addr
value: {nacos server address}
- name: dubbo.registry.address
value: 'nacos://{nacos server address}:8848'
服务来源为容器服务
展开查看YAML文件
# 应用A的灰度版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a-gray
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
labels:
alicloud.service.tag: gray
app: spring-cloud-a
msePilotCreateAppName: spring-cloud-a
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-a
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 30
periodSeconds: 60
# 通过Nacos的Service访问自建Nacos
env:
- name: spring.cloud.nacos.discovery.server-addr
value: nacos-server
- name: dubbo.registry.address
value: 'nacos://nacos-server:8848'
---
# 应用C的灰度版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c-gray
namespace: default
spec:
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
labels:
alicloud.service.tag: gray
app: spring-cloud-c
msePilotCreateAppName: spring-cloud-c
msePilotAutoEnable: 'on'
spec:
containers:
- name: spring-cloud-c
image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
imagePullPolicy: Always
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 30
periodSeconds: 60
# 通过Nacos的Service访问自建Nacos
env:
- name: spring.cloud.nacos.discovery.server-addr
value: nacos-server
- name: dubbo.registry.address
value: 'nacos://nacos-server:8848'
步骤2:创建灰度环境泳道组
登录MSE治理中心控制台,并在顶部菜单栏选择地域。
在左侧导航栏,选择。
如果您选择的微服务命名空间内没有创建过泳道组,则单击创建泳道组及泳道。如果您选择的微服务命名空间内已经创建过泳道组,则单击+创建泳道组。
在创建泳道组面板,设置如下相关配置,然后单击确定。
配置项 | 说明 |
泳道组名称 | 自定义泳道组的名称。 |
入口类型 | 选择MSE 云原生网关。 |
入口网关 | 选择目标云原生网关。 |
泳道组涉及应用 | 选择spring-cloud-a、spring-cloud-b和spring-cloud-c。 |
泳道组创建完成后,在全链路灰度页面的泳道组区域,可以查看您创建的泳道组。如需变更泳道组信息,单击图标,可在页面自行修改相关信息。
步骤3:创建灰度环境泳道
说明 使用全链路灰度功能时,需要您给灰度应用添加一个特殊的 tag
标记,以便将这些节点和其他节点区分开来。容器环境下需要您在spec.template.metadata.labels
下增加alicloud.service.tag: ${tag}
信息;在ECS环境下需要添加Java启动参数-Dalicloud.service.tag=${tag}
。
以云原生网关作为全链路灰度入口时,MSE支持两种泳道灰度模式。
泳道路由模式在一个泳道组中需要保持一致。您只有在创建泳道组中第一条泳道时可以调整泳道路由的灰度模式。
在全链路灰度页面底部,如果您选择的微服务命名空间内没有创建过泳道,则单击点击创建第一个分流泳道。如果您选择的微服务命名空间内已经创建过泳道,则单击+创建泳道。
在创建泳道面板,设置流控泳道相关配置,然后单击确定。
创建按请求内容路由的泳道
配置项 | 说明 |
配置节点标签 | 需要您给灰度应用节点打上标签,以便与正常节点做区分。 |
填写泳道信息 | 泳道名称:输入便于理解和识别的泳道名称。 泳道标签:该泳道内的匹配的流量去往的目标标签。 确认匹配关系:检查您配置了该标签的应用节点数是否符合预期。 泳道状态:选择开启。 |
配置路由和灰度规则 | 设置流量进入该泳道的规则。 |
泳道路由模式说明
灰度条件 | 效果 |
以下条件同时满足 | 需要所有条件内容满足的流量才会去往对应的泳道。 |
以下条件任意满足 | 满足如下任意一个条件内容的流量就会去往对应的泳道。 |
条件内容说明
条件 | 描述 | 全链路灰度配置示例 | 云原生网关请求示例 |
== | 精确匹配,流量值与条件值需要完全相等。 | | |
!= | 不等匹配,流量值与条件值不相等时满足条件。 | | |
in | 包含匹配,流量值需要在指定的列表中时满足条件。 | | |
百分比 | 百分比匹配,原理:满足`hash(get(key)) % 100 < value` 成立时满足条件。 | | |
正则匹配 | 正则表达式匹配,按照正则表达式规则匹配时满足条件。 | | |
前缀匹配 | 前缀匹配,指定值是实际值的前缀时满足条件。 | | |
创建按比例路由的泳道
说明 创建按比例路由的泳道,需要ack-onepilot版本为3.0.18及以上,且探针版本为3.2.3及以上。
配置项 | 说明 |
配置节点标签 | 需要您手工给您的灰度应用节点打上标签,以便与正常节点做区分。 |
填写泳道信息 | 泳道名称:输入便于理解和识别的泳道名称。 泳道标签:该泳道内的匹配的流量去往的目标标签。 确认匹配关系:检查您配置了该标签的应用节点数是否符合预期。 泳道状态:选择开启。 |
配置路由和灰度规则 | 设置流量进入该泳道的规则。 选择路由:选择在步骤一创建的基线路由名称。 灰度模式:选择按比例路由。 流量比例:30%。
|
说明 您还可以为每条网关基线路由分别设置不同的流量比例,开启该功能时,您需要注意该网关基线路由当前在所有泳道组中配置的流量比例总和不应超过100%。
完成泳道创建后,在全链路灰度的流量分配区域,可以查看泳道详情,还可以进行如下操作。
步骤4:测试灰度版本流量
测试按请求内容路由的泳道
使用curl命令测试灰度流量,如测试结果所示,流量经过了A、C的灰度环境,由于B没有gray环境,所以流量自动容灾到基线版本。
# 测试命令
curl -H "canary: gray" x.x.x.x/a
# 测试结果
Agray[10.0.3.177][config=base] -> B[10.0.3.195] -> Cgray[10.0.3.180]
测试按比例路由的泳道
可以通过如下Python脚本测试按比例路由的分流情况(需要安装requests包),并将“x.x.x.x”替换为云原生网关的入口SLB地址。
展开查看Python脚本
# pip3 install requests
# python3 traffic.py
import requests
TOTAL_REQUEST = 100
ENTRY_URL = 'http://x.x.x.x/a'
def parse_tag(text:str):
'''
A[10.0.23.64][config=base] -> B[10.0.23.65] -> C[10.0.23.61]
Agray[10.0.23.64][config=base] -> B[10.0.23.65] -> Cgray[10.0.23.61]
Ablue[10.0.23.64][config=base] -> B[10.0.23.65] -> Cblue[10.0.23.61]
'''
print(text)
app_parts = text.split(' -> ')
# tag_app: C[10.0.23.61] / Cgray[10.0.23.61]
tag_app = app_parts[-1]
splits = tag_app.split('[')
# tag_part: C / Cgray
tag_part = splits[0]
tag = tag_part[1:]
return tag if len(tag) > 0 else 'base'
def get_tag(url:str):
resp = requests.get(url)
resp.encoding = resp.apparent_encoding
return parse_tag(resp.text)
def cal_tag_count(url:str, total_request:int):
count_map = {}
for i in range(total_request):
tag = get_tag(url)
if tag not in count_map:
count_map[tag] = 1
else:
count_map[tag] += 1
print()
print('Total Request:', total_request)
print('Traffic Distribution:', count_map)
if __name__ == '__main__':
cal_tag_count(ENTRY_URL, TOTAL_REQUEST)
从结果可以看到,约有30%比例的流量去往了灰度环境。
(可选)步骤三:可观测
若应用出现异常,您可以通过MSE提供的可观测能力查看异常数据,帮助您快速定位问题。
云原生网关可观测
在MSE云原生网关的路由管理页面,选择服务页签。单击目标服务,在监控区域查看服务的流量情况。
微服务治理可观测
在MSE微服务治理的全链路灰度页面,单击目标应用,在应用 QPS 监控区域,可查看对应泳道基线版本和灰度版本的流量情况。
总QPS:该应用总的QPS。
异常QPS:该应用出错的请求数。
GrayQPS:该应用gray版本的QPS。