微服务架构下,有一些需求开发涉及到微服务调用链路上的多个微服务同时改动。通常每个微服务都会有灰度环境或分组来接受灰度流量。我们希望进入上游灰度环境的流量也能进入下游灰度的环境中,确保1个请求始终在灰度环境中传递。即使这个调用链路上有一些微服务应用不存在灰度环境,那么这些微服务应用在请求下游应用的时候依然能够回到下游应用的灰度环境中。我们通过将 APISIX 提供的灵活的路由能力以及 MSE 提供的全链路灰度能力结合,可以在不需要修改任何业务代码的情况下,轻松实现上述所说的全链路灰度能力。
1. 前提条件
已创建Kubernetes集群,请参见创建Kubernetes托管版集群。
已开通MSE微服务治理专业版,请参见开通MSE微服务治理。
2. 概述
2.1 方案介绍
本文主要介绍基于 Ingress-APISIX 的全链路灰度功能。假设应用的架构由 Ingress-APISIX 网关以及后端的微服务架构(Spring Cloud)组成,后端调用链路有3个:购物车(A),交易中心(B),库存中心(C),可以通过客户端或者是HTML来访问后端服务,这些服务之间通过Nacos注册中心实现服务发现。
2.2 目标读者
希望通过极简的配置与无代码侵入的方式,使用 Ingress-APISIX/APISIX 网关来实现全链路灰度功能。
2.3 适用场景
场景一:按照域名路由,实现全链路灰度
有时候,我们可以通过不同的域名来区分线上基线环境和灰度环境,灰度环境有单独的域名可以配置,假设我们通过访问www.gray.com 来请求灰度环境,访问 www.base.com 走基线环境。
调用链路 Ingress-APISIX -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。
场景二:按照指定请求参数进行路由,实现全链路灰度
有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的参数来路由到灰度环境。例如下图中,通过env=gray这个请求参数,来访问灰度环境。
调用链路 Ingress-APISIX -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。
2.4 相关概念
MSE :阿里云微服务引擎
全链路:请求从上往下的过程达到超过2个应用。
Ingress:对集群中服务的外部访问进行管理的API 对象。
APISIX:Apache APISIX 是一个动态、实时、高性能的API 网关。
实例:通常对应一个应用进程,是服务的承载实体,通常一个服务集群有多个实例。
3. 方案实施
3.1 前提条件
安装 Ingress-APISIX 组件
APISIX 架构如下图所示,我们需要安装APISIX、
安装apisix、apisix-ingress-controller、etcd等组件
helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
kubectl create ns ingress-apisix
helm install apisix apisix/apisix \
--set gateway.type=LoadBalancer \
--set ingress-controller.enabled=true \
--set etcd.persistence.storageClass="alicloud-disk-ssd" \
--set etcd.persistence.size="20Gi" \
--namespace ingress-apisix \
--set ingress-controller.config.apisix.serviceNamespace=ingress-apisix
kubectl get service --namespace ingress-apisix
看到在ingress-apisix命名空间下看到无状态 apisix、apisix-ingress-controller应用、以及有状态的etcd应用
安装APISIX Admin
helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix-dashboard apisix/apisix-dashboard --namespace ingress-apisix
安装完成后,可以绑定一个SLB
通过{slb-ip}:9000访问APISIX控制台(默认密码admin/admin)
开启 MSE 微服务治理
开通微服务治理专业版:
单击开通MSE微服务治理。
微服务治理版本选择专业版,选中服务协议,然后单击立即开通。关于微服务治理的计费详情,请参见价格说明。
安装MSE微服务治理组件:
在容器服务控制台左侧导航栏中,选择市场 > 应用市场。
在应用市场页面单击应用目录页签,然后搜索并单击ack-onepilot组件。
在ack-onepilot页面右上方单击一键部署,在创建面板中选择集群和命名空间,设置组件发布名称,然后单击下一步。说明 推荐使用默认的命名空间ack-onepilot。
在参数配置向导中确认组件参数信息,然后单击确定。安装完成后,在命名空间ack-onepilot中出现ack-onepilot应用,表示安装成功。
为应用开启微服务治理:
登录MSE治理中心控制台。
在左侧导航栏选择微服务治理中心 > K8s集群列表。
在K8s集群列表页面搜索目标集群,单击图标,然后单击目标集群操作列下方的管理。
在集群详情页面命名空间列表区域,单击目标命名空间操作列下方的开启微服务治理。
在开启微服务治理对话框中单击确认。
部署Demo应用程序
在容器服务控制台左侧导航栏中,单击集群。
在集群列表页面中,单击目标集群名称或者目标集群右侧操作列下的详情。
在集群管理页左侧导航栏中,选择工作负载 > 无状态。
在无状态页面选择命名空间,然后单击使用YAML创建资源。
对模板进行相关配置,完成配置后单击创建
本文示例中部署A、B、C三个应用,每个应用分别部署一个基线版本和一个灰度版本;并部署一个Nacos server应用,用于实现服务发现。
# A 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30
# A 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a-new
strategy:
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a-new
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: profiler.micro.service.tag.trace.enable
value: "true"
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-a-new
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30
# B 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-b
strategy:
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-b
ports:
- containerPort: 8080
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30
# B 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-b-new
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b-new
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-b-new
ports:
- containerPort: 8080
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30
# C 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
imagePullPolicy: Always
name: spring-cloud-c
ports:
- containerPort: 8080
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30
# C 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c-new
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-c-new
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c-new
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
imagePullPolicy: IfNotPresent
name: spring-cloud-c-new
ports:
- containerPort: 8080
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30
# Nacos Server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nacos-server
spec:
replicas: 1
selector:
matchLabels:
app: nacos-server
template:
metadata:
labels:
app: nacos-server
spec:
containers:
- env:
- name: MODE
value: standalone
image: nacos/nacos-server:latest
imagePullPolicy: Always
name: nacos-server
dnsPolicy: ClusterFirst
restartPolicy: Always
# Nacos Server Service 配置
---
apiVersion: v1
kind: Service
metadata:
name: nacos-server
spec:
ports:
- port: 8848
protocol: TCP
targetPort: 8848
selector:
app: nacos-server
type: ClusterIP
网络配置
针对入口应用 A ,配置两个 k8s service, spring-cloud-a-base 对应 A 的 base 版本,spring-cloud-a-gray 对应 A 的 gray 版本。
apiVersion: v1
kind: Service
metadata:
name: spring-cloud-a-base
spec:
ports:
- name: http
port: 20001
protocol: TCP
targetPort: 20001
selector:
app: spring-cloud-a
---
apiVersion: v1
kind: Service
metadata:
name: spring-cloud-a-gray
spec:
ports:
- name: http
port: 20001
protocol: TCP
targetPort: 20001
selector:
app: spring-cloud-a-new
在APISIX Admin上面对步骤1中配置service进行上游服务配置
spring-cloud-a-svc 的上游配置如下
{
"nodes": [
{
"host": "spring-cloud-a-svc",
"port": 20001,
"weight": 1
}
],
"timeout": {
"connect": 6,
"send": 6,
"read": 6
},
"type": "roundrobin",
"scheme": "http",
"pass_host": "pass",
"name": "spring-cloud-a-svc",
"keepalive_pool": {
"idle_timeout": 60,
"requests": 1000,
"size": 320
}
}
spring-cloud-a-gray-svc的上游配置如下
{
"nodes": [
{
"host": "spring-cloud-a-gray-svc",
"port": 20001,
"weight": 1
}
],
"timeout": {
"connect": 6,
"send": 6,
"read": 6
},
"type": "roundrobin",
"scheme": "http",
"pass_host": "pass",
"name": "spring-cloud-a-gray-svc",
"keepalive_pool": {
"idle_timeout": 60,
"requests": 1000,
"size": 320
}
}
应用场景一:按照域名路由,实现全链路灰度
有时候,我们可以通过不同的域名来区分线上基线环境和灰度环境,灰度环境有单独的域名可以配置,假设我们通过访问www.gray.com 来请求灰度环境,访问 www.base.com 走基线环境。
调用链路 Ingress-nginx -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。
3.2 配置APISIX路由规则
在APISIX控制台选择路由
点击创建
匹配条件中域名、请求路径选择/*,选择对应的上游
分别配置如下路由,当host为www.base.com时,路由到 id 为 401152455435354xxx 所对应的上游,即spring-cloud-a-svc;当host为www.gray.com时,路由到 id 为 401163331936715xxx 所对应的上游,即spring-cloud-a-gray-svc。
base对应的路由配置
{
"uri": "/*",
"name": "spring-cloud-a",
"methods": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"CONNECT",
"TRACE"
],
"host": "www.base.com",
"upstream_id": "401152455435354xxx",
"labels": {
"API_VERSION": "0.0.1"
},
"status": 1
}
gray对应的路由配置
{
"uri": "/*",
"name": "spring-cloud-a-gray",
"priority": 1,
"methods": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"CONNECT",
"TRACE"
],
"host": "www.gray.com",
"upstream_id": "401163331936715xxx",
"labels": {
"API_VERSION": "0.0.1"
},
"status": 1
}
3.3 配置MSE全链路灰度
登录MSE治理中心控制台。
在顶部菜单栏选择地域。
在左侧导航栏选择微服务治理中心 > 全链路灰度。
在全链路灰度页面,单击创建泳道组及泳道。如果您选择的微服务空间内已经创建过泳道组,则单击+创建泳道组。
添加spring-cloud-a、spring-cloud-b、spring-cloud-c应用进入泳道组。
在创建泳道面板中设置泳道组相关参数,然后单击确定。
在全链路灰度页面上方选择创建和泳道组时相同的微服务空间,然后底部单击点击创建第一个分流泳道。
在创建泳道面板中设置流控泳道相关参数,选择标签gray,然后单击确定。
创建gray泳道完成。
结果验证
此时,访问 www.base.com 路由到基线环境
curl -H"Host:www.base.com" http://47.97.xxx.xxx/a
A[172.18.144.15] -> B[172.18.144.125] -> C[172.18.144.90]%
此时,访问 www.gray.com 路由到灰度环境6
curl -H"Host:www.gray.com" http://47.97.xxx.xxx/a
Agray[172.18.144.16] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]%
注意:其中47.97.xxx.xxx为apisix的公网ip
应用场景二:按照指定请求参数进行路由,实现全链路灰度
有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的参数来路由到灰度环境。例如下图中,通过env=gray这个请求参数,来访问灰度环境。
调用链路 Ingress-APISIX -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。
3.4 配置APISIX路由规则
在APISIX控制台选择路由
点击创建
匹配条件中新建高级匹配规则、请求路径选择/*,选择对应的上游
分别配置如下路由,当host为www.demo.com,请求参数env=gray时,路由优先匹配到 id 为 401163331936715xxx 所对应的上游,即spring-cloud-a-gray-svc;否则host为www.demo.com时,路由到 id 为 401152455435354xxx 所对应的上游,即spring-cloud-a-svc。
base对应的路由配置
{
"uri": "/*",
"name": "spring-cloud-a",
"methods": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"CONNECT",
"TRACE"
],
"host": "www.demo.com",
"upstream_id": "401152455435354xxx",
"labels": {
"API_VERSION": "0.0.1"
},
"status": 1
}
gray对应的路由配置
{
"uri": "/*",
"name": "spring-cloud-a-gray",
"priority": 1,
"methods": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"CONNECT",
"TRACE"
],
"host": "www.demo.com",
"vars": [
[
"arg_env",
"==",
"gray"
]
],
"upstream_id": "401163331936715xxx",
"labels": {
"API_VERSION": "0.0.1"
},
"status": 1
}
3.5 配置MSE全链路灰度
配置MSE全链路灰度步骤与应用场景一一致。
结果验证
此时,访问 www.demo.com 路由到基线环境
curl -H"Host:www.demo.com" http://47.97.xxx.xxx/a
A[172.18.144.15] -> B[172.18.144.125] -> C[172.18.144.90]%
此时,访问 www.demo.com 同时env=gray时路由到灰度环境
curl -H"Host:www.demo.com" http://47.97.xxx.xxx/a?env=gray
Agray[172.18.144.16] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]%
注意:其中47.97.xxx.xxx为apisix的公网ip