基于阿里云微服务引擎 MSE 的全链路灰度实践


前提条件

本文假设您的 ACK 集群已经创建完成。

开启 MSE 微服务治理

基于阿里云微服务引擎 MSE 的全链路灰度实践

  • 安装 ack-kubernetes-cronhpa-controller ,安装步骤请参考之前的说明
  • 安装 ack-ingress-nginx,安装步骤请参考之前的说明
  • 访问 MSE 控制台,在K8s集群列表中选择相应集群,点击管理,选择default 命名空间,点击开启服务治理能力。

基于阿里云微服务引擎 MSE 的全链路灰度实践


开启 AHAS 应用防护

  • 点击开通 AHAS 应用防护 以使用应用流控能力,请注意,需要开通 AHAS 流量防护专业版

基于阿里云微服务引擎 MSE 的全链路灰度实践

  • 访问容器服务控制台,打开应用目录,搜索 ack-ahas-sentinel-pilot ,选择对应集群,点击创建。


部署 Demo 应用程序


将下面的文件保存到 mse-demo.yaml 中,并执行 kubectl apply -f mse-demo.yaml 以部署应用,这里我们将要部署 zuul,A, B, C 三个应用,其中 A、B 两个应用分别部署一个基线版本和一个灰度版本,B应用的基线版本关闭了无损下线能力,灰度版本开启了无损下线能力。C应用有一个 spring-cloud-c-warmup 应用,开启了服务预热能力,其中预热时长为2分钟。


# 入口 zuul 应用
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-cloud-zuul
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-cloud-zuul
  template:
    metadata:
      annotations:
        msePilotCreateAppName: spring-cloud-zuul
        alibabacloud.com/burst-resource: eci
      labels:
        app: spring-cloud-zuul
    spec:
      containers:
        - env:
            - name: JAVA_HOME
              value: /usr/lib/jvm/java-1.8-openjdk/jre
            - name: LANG
              value: C.UTF-8
          image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-zuul:1.0.1
          imagePullPolicy: Always
          name: spring-cloud-zuul
          ports:
            - containerPort: 20000


# A 应用 base 版本,开启按照机器纬度全链路透传
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-a
  name: spring-cloud-a
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-a
  template:
    metadata:
      annotations:
        msePilotCreateAppName: spring-cloud-a
        ahasPilotAutoEnable: "on"
        ahasAppName: spring-cloud-a
        armsPilotAutoEnable: "on"
        armsPilotCreateAppName: spring-cloud-a
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-a
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.tag.trace.enable
          value: "true"
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
      
# A 应用 gray 版本,开启按照机器纬度全链路透传
---            
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-a-gray
  name: spring-cloud-a-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-a-gray
  strategy:
  template:
    metadata:
      annotations:
        alicloud.service.tag: gray
        msePilotCreateAppName: spring-cloud-a
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-a-gray
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.tag.trace.enable
          value: "true"
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a-gray
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
            
# B 应用 base 版本,关闭无损下线能力
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-b
  name: spring-cloud-b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-b
  strategy:
  template:
    metadata:
      annotations:
        msePilotCreateAppName: spring-cloud-b
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-b
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: micro.service.shutdown.server.enable
          value: "false"
        - name: profiler.micro.service.http.server.enable
          value: "false"
        - name: profiler.micro.service.warmup.enable
          value: "true"
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-b:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-b
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
            
# B 应用 gray 版本,默认开启无损下线功能
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-b-gray
  name: spring-cloud-b-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-b-gray
  template:
    metadata:
      annotations:
        alicloud.service.tag: gray
        msePilotCreateAppName: spring-cloud-b
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-b-gray
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.warmup.enable
          value: "true"
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-b:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-b-gray
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
        lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - '-c'
                  - >-
                    wget http://127.0.0.1:54199/offline 2>/tmp/null;sleep
                    30;exit 0
            
# C 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-c
  name: spring-cloud-c
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-c
  template:
    metadata:
      annotations:
        msePilotCreateAppName: spring-cloud-c
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-c
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-c:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-c
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
            
# C 应用 warmup 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-c-warmup
  name: spring-cloud-c-warmup
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-cloud-c-warmup
  template:
    metadata:
      annotations:
        msePilotCreateAppName: spring-cloud-c-warmup
        alibabacloud.com/burst-resource: eci
        k8s.aliyun.com/eci-use-specs: 4-8Gi
      labels:
        app: spring-cloud-c-warmup
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.warmup.enable
          value: "true"
        - name: profiler.micro.service.warmup.time
          value: "120"
        image: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/spring-cloud-c:0.1-SNAPSHOT
        imagePullPolicy: IfNotPresent
        name: spring-cloud-c-warmup
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: '4'
            memory: 8Gi
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
        lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - '-c'
                  - >-
                    wget http://127.0.0.1:54199/offline 2>/tmp/null;sleep
                    30;exit 0

# Nacos Server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nacos-server
  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: registry-vpc.cn-beijing.aliyuncs.com/wangtao-mse/nacos-server:latest
        imagePullPolicy: Always
        name: nacos-server
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
      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

#HPA 配置
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-b
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-b
   jobs:
   - name: "scale-down"
     schedule: "0 */4 * * * *"
     targetSize: 2
   - name: "scale-up"
     schedule: "2 */4 * * * *"
     targetSize: 4
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-b-gray
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-b-gray
   jobs:
   - name: "scale-down"
     schedule: "0 */5 * * * *"
     targetSize: 2
   - name: "scale-up"
     schedule: "3 */5 * * * *"
     targetSize: 4
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-c-warmup
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-c-warmup
   jobs:
   - name: "scale-down"
     schedule: "0 */4 * * * *"
     targetSize: 0
   - name: "scale-up"
     schedule: "1 */4 * * * *"
     targetSize: 1


# zuul 网关开启 SLB 暴露展示页面   
---     
apiVersion: v1
kind: Service
metadata:
  name: zuul-slb
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 20000
  selector:
    app: spring-cloud-zuul
  type: ClusterIP


# 为 zuul 网关开启 Ingress
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: zuul
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: zuul-slb
              servicePort: 80
            path: /


流量压力来源

spring-cloud-zuul应用中,每个 pod 具备并发为 100 qps 的访问本地 zuul 端口的 127.0.0.1:20000:/A/a 的http请求流量,另外有10 qps 会访问 127.0.0.1:20000:/A/a并且带上 x-mse-tag=gray这个header,当前 zuul 的 pod 数为1,所以 spring-cloud-a 应用的平均qps为 110。压力可以通过 环境变量 demo.qps来调节压力大小,默认为100

流量架构图如下:

基于阿里云微服务引擎 MSE 的全链路灰度实践

结果验证

MSE 场景一:对经过机器的流量进行自动染色,实现全链路灰度

我们在 MSE 控制台对 spring-cloud-a  应用配置如下流量比例规则,由于我们对 spring-cloud-a 应用增加了

profiler.micro.service.tag.trace.enable=true 的开关,spring-cloud-a 应用会对经过他的流量进行染色透传,从而实现全链路灰度。

spring-cloud-a中,灰度分组的流量是10,基线分组的流量是100.

基于阿里云微服务引擎 MSE 的全链路灰度实践


spring-cloud-b 中,灰度分组的流量是也是10,基线分组的流量是100.

基于阿里云微服务引擎 MSE 的全链路灰度实践


找到 容器服务对外暴露的 Ingress 的端点。

基于阿里云微服务引擎 MSE 的全链路灰度实践


在命令行中输入以下命令,发现正常的流量走到基线的机器上


curl http://$IP/A/a
A[172.16.2.16] -> B[172.16.2.20] -> C[172.16.2.21]


在命令行中输入一下命令,带上特殊的 header ,发现灰度的流量走到灰度的机器上,并且会向后透传经过 A 和 B 的灰度机器,最后回到 C的基线版本中。


curl -H"x-mse-tag:gray" http://$IP/A/a
Agray[172.16.2.23] -> Bgray[172.16.2.18] -> C[172.16.2.21]



MSE 场景二:服务预热


我们在 spring-cloud-c-warmup 应用开启了定时HPA模拟应用启动的过程,同时对spring-cloud-c-warmup 应用开启了默认2分钟时长的预热功能 profiler.micro.service.warmup.time=120

基于阿里云微服务引擎 MSE 的全链路灰度实践

可以直接在spring-cloud-c-warmup应用直观地看到效果

基于阿里云微服务引擎 MSE 的全链路灰度实践

从上图可以看出来流量缓慢增大,直观地服务预热看到服务预热的效果。

MSE 场景三:无损下线

由于我们对 spring-cloud-bspring-cloud-b-gray应用均开启了定时HPA,模拟每4分钟进行一次定时的扩缩容。

基于阿里云微服务引擎 MSE 的全链路灰度实践

基于阿里云微服务引擎 MSE 的全链路灰度实践


我们可以直接从 spring-cloud-a应用的监控看出来,未开启无损下线功能的监控存在错误。而 gray的流量因为默认开启了无损下线功能,流量默认无损。

基于阿里云微服务引擎 MSE 的全链路灰度实践

可以看到gray的流量在pod扩缩容的过程中无流量损失,未打标的流量由于关闭了无损下线功能,在pod扩缩容的过程中流量出现了损失,如上图所示未打标的流量有 122 的请求错误。

通过 AHAS 实现限流降级


打开 AHAS 控制台 ,找到 spring-cloud-a 应用,选择接口详情,找到 /a 接口,点击 + 好号新增流控规则

基于阿里云微服务引擎 MSE 的全链路灰度实践

选择流控规则,输入单机 QPS 限流阈值 10,点击下一步,最后点击新增。

基于阿里云微服务引擎 MSE 的全链路灰度实践

点击新增添加流控规则

基于阿里云微服务引擎 MSE 的全链路灰度实践


等待约 10s 左右,观察 接口的 QPS 情况,发现通过 QPS 从 100 下降到 20,拒绝 QPS 从 0 上升到 80,因为 spring-cloud-a 有两台机器,因此总的通过 QPS 为 20.

基于阿里云微服务引擎 MSE 的全链路灰度实践

接下来,将该限流规则关闭

基于阿里云微服务引擎 MSE 的全链路灰度实践

观察 QPS , 发现 通过 QPS 回到 100,拒绝 QPS 降低为 0。

基于阿里云微服务引擎 MSE 的全链路灰度实践

上一篇:《规范敏捷交付:企业级敏捷软件交付的方法与实践》——2.4 精益开发原则


下一篇:软件工程 项目管理的目标和细节