如何用20分钟就能获得同款企业级全链路灰度能力?

Posted 阿里云开发者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用20分钟就能获得同款企业级全链路灰度能力?相关的知识,希望对你有一定的参考价值。

简介:MSE 微服务引擎将推出服务治理专业版,提供开箱即用且完整专业的微服务治理解决方案,帮助企业更好地实现微服务治理能力。如果您的系统也可以像本文描述的那样,快速具备完整的全链路灰度能力,并基于该能力进行进一步的微服务治理实践,不仅可以节省客观的人力与成本,还可以让您的企业在微服务领域的探索更加有底气。

作者:十眠

审核&校对:望陶、亦盏

编辑&排版:雯燕


今年双 11,云原生中间件完成了开源、自研、商业化三位一体,全面升级到中间件云产品。MSE 微服务治理通过 Dubbo3.0 支撑了阿里集团核心业务双 11 的流量洪峰,截止目前集团内 50% 的用户已经习惯使用 MSE 微服务治理 HSF 和 Dubbo3.0 应用,今天我们来细聊一下 MSE 服务治理专业版中的全链路灰度能力,以及它在生产大规模实践的一些场景。


背景

微服务架构下,有一些需求开发,涉及到微服务调用链路上的多个微服务同时发生了改动,需要通过灰度发布方式来更好地控制新版本服务上线的风险和爆炸半径。通常每个微服务都会有灰度环境或分组来接受灰度流量,我们希望通过进入上游灰度环境的流量,也能进入下游灰度的环境中,确保 1 个请求始终在灰度环境中传递,即使这个调用链路上有一些微服务没有灰度环境,这些应用请求下游的时候依然能够回到灰度环境中。通过 MSE 提供的全链路灰度能力,可以在不需要修改任何您的业务代码的情况下,能够轻松实现上述能力。


MSE 微服务治理全链路灰度特点


全链路灰度作为 MSE 服务治理专业版中的拳头功能,具备以下六大特点


  • 可通过定制规则引入精细化流量


除了简单地按照比例进行流量引入外,我们还支持 Spring Cloud 与 Dubbo 流量按规则引入,Spring Cloud 流量可根据请求的 cookie、header、param 参数或随机百分比引入流量,Dubbo 流量可按照服务、方法、参数来引入。


 

  • 全链路隔离流量泳道


1) 通过设置流量规则对所需流量进行“染色,“染色流量会路由到灰度机器。


2) 灰度流量携带灰度标往下游传递,形成灰度专属环境流量泳道,无灰度环境应用会默认选择未打标的基线环境。


  • 端到端的稳定基线环境


未打标的应用属于基线稳定版本的应用,即稳定的线上环境。当我们将发布对应的灰度版本代码,然后可以配置规则定向引入特定的线上流量,控制灰度代码的风险。


  • 流量一键动态切流


流量规则定制后,可根据需求进行一键停启,增删改查,实时生效。灰度引流更便捷。


  • 低成本接入,基于 Java Agent 技术实现无需修改一行业务代码


MSE 微服务治理能力基于 Java Agent 字节码增强的技术实现,无缝支持市面上近 5 年的所有 Spring Cloud 和 Dubbo 的版本,用户不用改一行代码就可以使用,不需要改变业务的现有架构,随时可上可下,没有绑定。只需开启 MSE 微服务治理专业版,在线配置,实时生效。


  • 具备无损上下线能力,使得发布更加丝滑


应用开启 MSE 微服务治理后就具备无损上下线能力,大流量下的发布、回滚、扩容、缩容等场景,均能保证流量无损。


大规模生产实践的场景


本文主要介绍 MSE 微服务治理在支持大客户过程中总结抽象出来的常用的几个全链路灰度方案生产落地实践的场景。


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



  • 进入带 tag 的节点后续调用优先选择带有相同 tag 的节点,即对经过 tag 节点的流量进行“染色”。
  • 有 tag 的调用链路上找不到相同 tag 的节点,则 fallback 到无 tag 的节点。


  • 有 tag 的调用链路经过无 tag 的节点,如果链路后续调用有 tag 的节点,则恢复 tag 调用的模式。


场景二:通过给流量带上特定的 header 实现全链路灰度



客户端通过在请求中增加制定环境的标识,接入层根据表示进行转发至表示对应环境的网关,对应环境的网关通过隔离插件调用标识对应的项目隔离环境,请求在业务项目隔离环境中闭环。


场景三:通过自定义路由规则来进行全链路灰度



通过在灰度请求中增加指定的 header,且整条调用链路会将该 header 透传下去,只需在对应的应用配置该 header 相关的路由规则,带指定 header 的灰度请求进入灰度机器,即可按需实现全链路流量灰度。


全链路灰度的实践


我们如何快速获得上述同款全链路灰度的能力呢?下面我会带大家从 0 到 1 快速搭建我们的全链路灰度能力。


我们假设应用的架构由 Ingress-nginx 以及后端的微服务架构(Spring Cloud)来组成,后端调用链路有 3 跳,购物车(a),交易中心(b),库存中心(c),他们通过 Nacos 注册中心做服务发现,客户端通过客户端或者是 H5 页面来访问后端服务。


前提条件


安装 Ingress-nginx 组件


访问容器服务控制台,打开应用目录,搜索 ack-ingress-nginx ,选择命名空间 kube-system,点击创建,安装完成后,在 kube-system 命名空间中会看到一个 deployment ack-ingress-nginx-default-controller ,表明安装成功。


$ kubectl get deployment -n kube-system
NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
ack-ingress-nginx-default-controller      2/2     2            2           18h


开启 MSE 微服务治理专业版


  • 点击开通 MSE 微服务治理专业版 以使用全链路灰度能力。


  • 访问容器服务控制台,打开应用目录,搜索 ack-mse-pilot ,点击创建。


  • 在 MSE 服务治理控制台,打开 K8s 集群列表,选择对应集群,对应命名空间,并打开微服务治理。


部署 Demo 应用程序


将下面的文件保存到 ingress-gray.yaml 中,并执行 kubectl apply -f ingress-gray.yaml 以部署应用,这里我们将要部署 A, B, C 三个应用,每个应用分别部署一个基线版本和一个灰度版本。


# 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
      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
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
# A 应用 gray 版本
---            
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-a-new
  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: 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.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a-new
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        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
      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
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-b
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
# B 应用 gray 版本  
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-b-new
  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: LANG
          value: C.UTF-8
        - 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
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
# 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
      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.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-c
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
# C 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-c-new
  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: LANG
          value: C.UTF-8
        - 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
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
# 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: nacos/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


动手实践


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


有时候,我们可以通过不同的域名来区分线上基线环境和灰度环境,灰度环境有单独的域名可以配置,假设我们通过访问 www.gray.com 来请求灰度环境,访问 www.base.com 走基线环境。



调用链路 Ingress-nginx -> A -> B -> C ,其中 A 可以是一个 spring-boot 的应用。


注意:入口应用 A 的 gray 和 A 的 base 环境,需要在 MSE 服务治理控制台打开 A 应用的按照流量比例透传开关,表示开启向后透传当前环境的标签的功能。这样, 当 Ingress-nginx 路由 A 的 gray 之后,即使请求中没有携带任何 header,因为开启了此开关,所以往后调用的时候会自动添加 x-mse-tag:gray 这个 header,其中的 header的值 gray 来自于 A 应用配置的标签信息。如果原来的请求中带有 x-mse-tag:gray则会以原来请求中的标签优先。


针对入口应用 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


配置入口的 Ingress 规则,访问 www.base.com 路由到 A 应用的 base 版本,访问 www.gray.com 路由到 A 应用的 gray 版本。


apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: spring-cloud-a-base
spec:
  rules:
  - host: www.base.com
    http:
      paths:
      - backend:
          serviceName: spring-cloud-a-base
          servicePort: 20001
        path: /
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: spring-cloud-a-gray
spec:
  rules:
  - host: www.gray.com
    http:
      paths:
      - backend:
          serviceName: spring-cloud-a-gray
          servicePort: 20001
        path: /


结果验证


此时,访问 www.base.com 路由到 基线环境


curl -H"Host:www.base.com" http://106.14.155.223/a
A[172.18.144.155] -> B[172.18.144.120] -> C[172.18.144.79]

此时,访问 www.gray.com 路由到灰度环境


curl -H"Host:www.gray.com" http://106.14.155.223/a
Agray[172.18.144.160] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]

进一步的,如果入口应用 A 没有灰度环境,访问到 A 的 base 环境,又需要在 A -> B 的时候进入灰度环境,则可以通过增加一个特殊的 header x-mse-tag 来实现,header 的值是想要去的环境的标签,例如 gray


curl -H"Host:www.base.com"  -H"x-mse-tag:gray" http://106.14.155.223/a
A[172.18.144.155] -> Bgray[172.18.144.139] -> Cgray[172.18.144.8]

可以看到第一跳,进入了 A 的 base 环境,但是 A->B 的时候又重新回到了灰度环境。


这种使用方式的好处是,配置简单,只需要在 Ingress 处配置好规则,某个应用需要灰度发布的时候,只需要在灰度环境中部署好应用,灰度流量自然会进入到灰度机器中,如果验证没问题,则将灰度的镜像发布到基线环境中;如果一次变更有多个应用需要灰度发布,则把他们都加入到灰度环境中即可。


最佳实践


  1. 给所有灰度环境的应用打上 gray 标,基线环境的应用默认不打标。


  1. 线上常态化引流 2% 的流量进去灰度环境中


场景二:通过给流量带上特定的 header 实现全链路灰度


有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的 header 来路由到灰度环境。例如下图中,通过添加 x-mse-tag:gray 这个 header,来访问灰度环境。



个时候 demo 的Ingress 规则如下,注意这里增加了 nginx.ingress.kubernetes.io/canary 相关的多条规则


apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: spring-cloud-a-base
spec:
  rules:
  - host: www.demo.com
    http:
      paths:
      - backend:
          serviceName: spring-cloud-a-base
          servicePort: 20001
        path: /
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: spring-cloud-a-gray
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "x-mse-tag"
    nginx.ingress.kubernetes.io/canary-by-header-value: "gray"
    nginx.ingress.kubernetes.io/canary-weight: "0"
spec:
  rules:
  - host: www.base.com
    http:
      paths:
      - backend:
          serviceName: spring-cloud-a-gray
          servicePort: 20001
        path: /


结果验证


此时,访问 www.demo.com 路由到基线环境


curl -H"Host:www.demo.com" http://106.14.155.223/a
A[172.18.144.155] -> B[172.18.144.56] -> C[172.18.144.156]

如何访问灰度环境呢?只需要在请求中增加一个header x-mse-tag:gray 即可。