全链路灰度之配置灰度

洵沐
  • 收获赞:3
  • 擅长领域:曾任阿里云微服务产品实习研发工程师,关注服务框架、微服务治理、云原生。

本文介绍了MSE针对全链路灰度中配置灰度问题的解决方案。用户只需在MSE控制台上做简单的操作,无需针对环境信息修改业务代码,即可轻松使用配置灰度能力。

背景

微服务场景下,全链路灰度作为一种低成本的新功能验证方式,得到了越来越广泛的应用。除了微服务实例和流量的灰度,微服务应用中的配置项也应该具备相应的灰度能力,以应对灰度应用对特殊配置值的诉求。

微服务应用通常会引入配置中心做配置管理,其提供动态的配置推送能力使得应用无需重启就可以动态地改变运行逻辑。但配置中心的管理维度仅仅是配置项本身,并不能感知到前来获取配置的服务实例的环境信息,即无法区分请求配置的是正式环境的实例还是灰度环境的实例。在这种背景下,如果某项配置在正式环境和灰度环境中需要使用不同值,它们在配置中心中必须作为不同的配置项,我们可能需要写出这样的代码:

...
if (env == "gray") {
    cfg = getConfig("cfg-1");
} else if (env == "gray2") {
    cfg = getConfig("cfg-2");
} else {
    cfg = getConfig("cfg-base");
}
...

这一场景在A/B测试中非常常见。随着配置项和灰度环境的增加,这类代码还会重复许多次。此外,一套灰度环境中往往存在多个服务,每个服务都需要独立维护一套类似的代码。最终的解决方案如图所示,同一配置项在不同环境中使用的配置值需要在用户应用中主动进行区分。

image.png

究其原因,还是来自于配置中心无法感知服务实例的环境信息,使得我们必须在代码中代替配置中心行使这一任务,从而导致了环境信息对业务代码产生了侵入。

针对这一问题,MSE的配置标签推送功能将配置管理场景下的环境信息的感知下沉到平台侧,由Agent负责。用户只需接入MSE,就可轻松在全链路灰度场景中使用配置推送能力,免去业务代码中繁琐的环境信息检测逻辑。如图所示:

image.png

接下来我们将通过实践的形式来演示MSE是如何通过配置标签推送来实现全链路灰度中的配置灰度的。

前提条件

1.开通 MSE 专业版,请参⻅开通 MSE 微服务治理专业版

2.创建 ACK 集群,请参⻅创建 Kubernetes 集群

操作步骤

步骤一:接入 MSE 微服务治理

  1. 安装 ack-onepilot

  1. 登录容器服务控制台

  2. 在左侧导航栏单击应用市场 > 应用目录

  3. 应用场景中,选择微服务,并单击 ack-onepilot,确保其版本为3.0.3及以上。

  4. 在 ack-onepilot ⻚面右侧集群列表中选择集群,然后单击创建。

截屏2022-08-15 17.17.39.png

安装 MSE 微服务治理组件大约需要 2 分钟,请耐心等待。

  1. 为ACK 命名空间中的应用开启 MSE 微服务治理

  1. 登录MSE治理中心控制台

  2. 在左侧导航栏选择 运维中心 > K8s集群列表

  3. 在 K8s 集群列表⻚面搜索框列表中选择集群名称或集群 ID,然后输入相应的关键字,单击搜索图标。

  4. 单击目标集群操作列的管理。

  5. 在集群详情⻚面命名空间列表区域,单击目标命名空间操作列下的开启微服务治理

  6. 在开启微服务治理对话框中单击确认。

步骤二:还原线上场景

本次实践中我们将部署 spring-cloud-zuul、spring-cloud-a、spring-cloud-b、spring-cloud-c 四个业务应用和注册中心 Nacos Server。其调用链路如下:

image.png

这些应用都是最简单的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应用中基线版本和灰度版本的配置值相同,均为应用中的初始值。

截屏2022-08-15 23_mosaic.png

步骤四:配置应用 spring-cloud-a 的灰度规则

在 cfg-spring-cloud-a 的应用详情页中,选择左侧导航栏的 流量治理,点击标签路由。如图

截屏2022-08-15 23.50.30.png

接着我们为灰度实例配置灰度规则。点击 标签gray >流量规则 > 添加,配置如下的灰度规则,点击确定。

截屏2022-08-15 23.52.57.png

步骤五:验证配置灰度

接下来我们来验证配置灰度。​

  1. 对灰度实例执行标签推送

回到 cfg-spring-cloud-a 的应用详情页的 应用配置 > 配置列表,选择开关 configValue 后的按标签推送。在弹窗中选择标签gray,并设置灰度环境中的配置值。

截屏2022-08-16 00.04.54.png

之后点击下一步:值对比,再点击标签推送,完成推送。此时在控制台上可以看到灰度实例的配置值已经变为了我们刚刚设置的值。

截屏2022-08-16 00_mosaic.png
  1. 验证配置灰度生效

登录容器服务控制台,点击左侧导航栏的 集群 ,进入部署了应用的集群。在集群详情页中选择 网络 > 服务,找到 zuul-slb,点击其外部端点,访问服务调用页面。

输入 /A/a ,可以访问到基线版本的A应用,可以看到其配置值仍为初始值。

截屏2022-08-16 10_mosaic.png

输入 /A/a?name=xiaoming ,可以通过刚刚配置的灰度规则访问到灰度版本的A应用,可以看到其配置值已经变为了我们刚刚推送的值。

截屏2022-08-16 10_mosaic (1).png
  1. 验证灰度配置值的持久性

标签推送的配置值是持久化的。这意味着即使灰度环境中的应用重启也能自动从MSE Agent获取到之前推送的配置值。

登录容器服务控制台,点击左侧导航栏的 集群 ,进入部署了应用的集群。在集群详情页中选择 工作负载 > 无状态。勾选 spring-cloud-a-gray 负载,点击批量重新部署。您也可以使用 kubectl 工具进行负载重部署,模拟灰度应用重启的过程。

应用重启完成后,重新执行 2. 中访问灰度应用的流程,可以发现配置值仍然为之前推送的配置值。

操作总结

  1. 整个过程完全在控制台完成,操作简单。

  2. 用户应用中只需使用同一份获取配置的代码,在编码层面无感知。

  3. 只需在MSE控制台执行一次标签推送,利用推送的持久化特性即可应对灰度应用的重启。