本文介绍了MSE针对全链路灰度中配置灰度问题的解决方案。用户只需在MSE控制台上做简单的操作,无需针对环境信息修改业务代码,即可轻松使用配置灰度能力。
背景
微服务场景下,全链路灰度作为一种低成本的新功能验证方式,得到了越来越广泛的应用。除了微服务实例和流量的灰度,微服务应用中的配置项也应该具备相应的灰度能力,以应对灰度应用对特殊配置值的诉求。
微服务应用通常会引入配置中心做配置管理,其提供动态的配置推送能力使得应用无需重启就可以动态地改变运行逻辑。但配置中心的管理维度仅仅是配置项本身,并不能感知到前来获取配置的服务实例的环境信息,即无法区分请求配置的是正式环境的实例还是灰度环境的实例。在这种背景下,如果某项配置在正式环境和灰度环境中需要使用不同值,它们在配置中心中必须作为不同的配置项,我们可能需要写出这样的代码:
...
if (env == "gray") {
cfg = getConfig("cfg-1");
} else if (env == "gray2") {
cfg = getConfig("cfg-2");
} else {
cfg = getConfig("cfg-base");
}
...
这一场景在A/B测试中非常常见。随着配置项和灰度环境的增加,这类代码还会重复许多次。此外,一套灰度环境中往往存在多个服务,每个服务都需要独立维护一套类似的代码。最终的解决方案如图所示,同一配置项在不同环境中使用的配置值需要在用户应用中主动进行区分。
究其原因,还是来自于配置中心无法感知服务实例的环境信息,使得我们必须在代码中代替配置中心行使这一任务,从而导致了环境信息对业务代码产生了侵入。
针对这一问题,MSE的配置标签推送功能将配置管理场景下的环境信息的感知下沉到平台侧,由Agent负责。用户只需接入MSE,就可轻松在全链路灰度场景中使用配置推送能力,免去业务代码中繁琐的环境信息检测逻辑。如图所示:
接下来我们将通过实践的形式来演示MSE是如何通过配置标签推送来实现全链路灰度中的配置灰度的。
前提条件
1.开通 MSE 专业版,请参⻅开通 MSE 微服务治理专业版。
2.创建 ACK 集群,请参⻅创建 Kubernetes 集群。
操作步骤
步骤一:接入 MSE 微服务治理
安装 ack-onepilot
登录容器服务控制台。
在左侧导航栏单击应用市场 > 应用目录。
在应用场景中,选择微服务,并单击 ack-onepilot,确保其版本为3.0.3及以上。
在 ack-onepilot ⻚面右侧集群列表中选择集群,然后单击创建。
安装 MSE 微服务治理组件大约需要 2 分钟,请耐心等待。
为ACK 命名空间中的应用开启 MSE 微服务治理
登录MSE治理中心控制台。
在左侧导航栏选择 运维中心 > K8s集群列表。
在 K8s 集群列表⻚面搜索框列表中选择集群名称或集群 ID,然后输入相应的关键字,单击搜索图标。
单击目标集群操作列的管理。
在集群详情⻚面命名空间列表区域,单击目标命名空间操作列下的开启微服务治理。
在开启微服务治理对话框中单击确认。
步骤二:还原线上场景
本次实践中我们将部署 spring-cloud-zuul、spring-cloud-a、spring-cloud-b、spring-cloud-c 四个业务应用和注册中心 Nacos Server。其调用链路如下:
这些应用都是最简单的Spring Cloud和Dubbo应用,您可以在这里获取到项目源码:https://github.com/aliyun/alibabacloud-microservice-demo/tree/master/mse-simple-demo
接下来我们在ACK集群上进行应用部署。您可以使用 kubectl 工具,或者在ACK控制台上直接部署。使用的资源文件如下:
# 部署 Nacos Server
apiVersion: apps/v1
kind: Deployment
metadata:
name: nacos-server
spec:
selector:
matchLabels:
app: nacos-server
template:
metadata:
annotations:
msePilotAutoEnable: "off"
labels:
app: nacos-server
spec:
containers:
- env:
- name: MODE
value: "standalone"
image: registry.cn-shanghai.aliyuncs.com/yizhan/nacos-server:latest
imagePullPolicy: IfNotPresent
name: nacos-server
ports:
- containerPort: 8848
---
apiVersion: v1
kind: Service
metadata:
name: nacos-server
spec:
type: ClusterIP
selector:
app: nacos-server
ports:
- name: http
port: 8848
targetPort: 8848
# 部署业务应用
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-zuul
spec:
selector:
matchLabels:
app: spring-cloud-zuul
template:
metadata:
annotations:
msePilotCreateAppName: cfg-spring-cloud-zuul
labels:
app: spring-cloud-zuul
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-zuul:1.0.0
imagePullPolicy: Always
name: spring-cloud-zuul
ports:
- containerPort: 20000
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet
name: zuul-slb
spec:
ports:
- port: 80
protocol: TCP
targetPort: 20000
selector:
app: spring-cloud-zuul
type: LoadBalancer
status:
loadBalancer: {}
# A应用基准版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a
spec:
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: cfg-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-hangzhou.aliyuncs.com/mse-demo-hz/demo:sc-a-1.0.0-cfg
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30
# A应用灰度版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a-gray
spec:
selector:
matchLabels:
app: spring-cloud-a-gray
template:
metadata:
annotations:
alicloud.service.tag: gray
msePilotCreateAppName: cfg-spring-cloud-a
labels:
app: spring-cloud-a-gray
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/demo:sc-a-1.0.0-cfg
imagePullPolicy: Always
name: spring-cloud-a-gray
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b
spec:
selector:
matchLabels:
app: spring-cloud-b
template:
metadata:
annotations:
msePilotCreateAppName: cfg-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:1.0.0
imagePullPolicy: Always
name: spring-cloud-b
ports:
- containerPort: 20002
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c
spec:
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
annotations:
msePilotCreateAppName: cfg-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:1.0.0
imagePullPolicy: Always
name: spring-cloud-c
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30
步骤三:验证部署结果
接下来我们执行 kubectl get svc,deploy
,验证集群中Service和Deployment的状态。
~ kubectl get svc,deploy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP x.x.x.x <none> 443/TCP 21d
service/nacos-server ClusterIP x.x.x.x <none> 8848/TCP 39m
service/zuul-slb LoadBalancer x.x.x.x x.x.x.x 80:32104/TCP 39m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nacos-server 1/1 1 1 39m
deployment.apps/spring-cloud-a 1/1 1 1 39m
deployment.apps/spring-cloud-a-gray 1/1 1 1 39m
deployment.apps/spring-cloud-b 1/1 1 1 39m
deployment.apps/spring-cloud-c 1/1 1 1 39m
deployment.apps/spring-cloud-zuul 1/1 1 1 39m
之后登录MSE治理中心控制台,在左侧导航栏单击应用治理,输入 cfg-spring-cloud-a ,点击搜索图标。选择 cfg-spring-cloud-a 应用卡片,可进入应用详情页面。
接着在左侧导航栏选择 应用配置 > 配置列表,点击开关 configValue
前的 + ,可以看到此时A应用中基线版本和灰度版本的配置值相同,均为应用中的初始值。
步骤四:配置应用 spring-cloud-a 的灰度规则
在 cfg-spring-cloud-a 的应用详情页中,选择左侧导航栏的 流量治理,点击标签路由。如图
接着我们为灰度实例配置灰度规则。点击 标签gray >流量规则 > 添加,配置如下的灰度规则,点击确定。
步骤五:验证配置灰度
接下来我们来验证配置灰度。
对灰度实例执行标签推送
回到 cfg-spring-cloud-a 的应用详情页的 应用配置 > 配置列表,选择开关 configValue 后的按标签推送。在弹窗中选择标签gray,并设置灰度环境中的配置值。
之后点击下一步:值对比,再点击标签推送,完成推送。此时在控制台上可以看到灰度实例的配置值已经变为了我们刚刚设置的值。
验证配置灰度生效
登录容器服务控制台,点击左侧导航栏的 集群 ,进入部署了应用的集群。在集群详情页中选择 网络 > 服务,找到 zuul-slb,点击其外部端点,访问服务调用页面。
输入 /A/a
,可以访问到基线版本的A应用,可以看到其配置值仍为初始值。
输入 /A/a?name=xiaoming
,可以通过刚刚配置的灰度规则访问到灰度版本的A应用,可以看到其配置值已经变为了我们刚刚推送的值。
验证灰度配置值的持久性
标签推送的配置值是持久化的。这意味着即使灰度环境中的应用重启也能自动从MSE Agent获取到之前推送的配置值。
登录容器服务控制台,点击左侧导航栏的 集群 ,进入部署了应用的集群。在集群详情页中选择 工作负载 > 无状态。勾选 spring-cloud-a-gray 负载,点击批量重新部署。您也可以使用 kubectl 工具进行负载重部署,模拟灰度应用重启的过程。
应用重启完成后,重新执行 2. 中访问灰度应用的流程,可以发现配置值仍然为之前推送的配置值。
操作总结
整个过程完全在控制台完成,操作简单。
用户应用中只需使用同一份获取配置的代码,在编码层面无感知。
只需在MSE控制台执行一次标签推送,利用推送的持久化特性即可应对灰度应用的重启。