基于Argo CD实现应用服务的全链路灰度

Argo CD主要用于监听Git仓库中应用编排的变化,与集群中应用真实运行状态进行对比,自动或手动同步拉取应用编排的变更到部署集群中。当您面临微服务应用新版本上线的风险挑战,期望平稳过渡、逐步验证新功能时,可以借助Argo CD实现应用服务的全链路灰度发布,以精细化流量调度和版本管理手段,确保新旧版本无缝切换,最大限度地减少对线上服务的影响,同时提升系统的稳定性和可靠性。

前提条件

背景信息

Argo CD支持以GitOps的方式对应用服务进行方便快捷地部署与发布。开发者提交YAML编写的应用程序定义资源(Deployment、Service)和流量管理规则资源(VirtualService、Gateway、DestinationRule)等到Git仓库。Argo CD监控集群中各类资源的状态,与Git仓库中的资源期望编排进行比较,以Git仓库中的内容为基准,当Git仓库发生变更时,支持自动或手动同步资源。

服务网格ASM支持通过流量泳道将应用的相关版本(或者其他特征)隔离成一个独立的运行环境,以满足应用服务的全链路灰度发布场景。ASM自1.20.6.27版本起,支持通过ASMSwimLaneGroup和ASMSwimLane两种YAML编写的自定义资源来定义流量泳道,支持使用Argo CD管理这两种自定义资源,实现全链路灰度发布。更多信息,请参见流量泳道概述

步骤一:使用Argo CD部署应用服务和流量泳道规则

  1. 创建mock应用全链路灰度示例。

    1. 在Argo CD管理界面,单击NEW APP,进行如下配置。

      image

      配置项

      说明

      Application Name

      应用名称,本示例中填写mock

      SYNC POLICY

      应用同步策略,本示例中填写Automatically,即Git仓库中有变更后,自动从Git仓库中同步资源定义的最新版本。选中PRUNE RESOURCES,表示当资源定义在Git仓库中不再存在时删除、同步会删除该资源。

      Repository URL

      作为同步来源的Git仓库URL,本示例中填写示例仓库的URL https://github.com/AliyunContainerService/asm-labs.git。如果您希望在示例的基础上进行修改,可以Fork此仓库,并填写Fork后的Github仓库地址。

      Revision

      作为同步来源的Git Tag或分支名,本示例中填写示例仓库的分支argocd-asm

      Path

      作为同步来源的Git仓库中的代码路径,本示例中填写示例所用的资源所在路径argo-cd/swimlane

      Cluster URL

      作为同步目的地的集群API Server地址,本示例中填写Argo CD部署的Kubernetes集群API Server地址https://kubernetes.default.svc

  2. 配置完成后,单击页面上方的CREATE

    在Argo CD的Applications页面,可以查看刚创建的mock应用的状态。

    image

  3. 单击mock应用,查看资源的同步状态。

    image

    可以看到Git仓库中除了定义应用的Deployment和Service资源外,还有流量管理规则资源VirtualService、Gateway,以及ASM流量泳道规则资源ASMSwimLaneGroup、ASMSwimLane。

步骤二:验证流量泳道全链路灰度效果

在本示例中,通过ASMSwimLaneGroup和ASMSwimLane资源实现了v1和v2版本应用服务环境的隔离。通过VirtualService资源定义,使ASM网关以1:1的比例向两个版本的应用服务转发流量。您可以通过不断访问ASM网关验证全链路灰度的效果。

  1. 通过ASM控制台获取网关的公网IP。具体操作,请参见获取ASM网关地址

  2. 执行以下命令,设置环境变量。

    xxx.xxx.xxx.xxx为上一步获取的IP地址。

    export ASM_GATEWAY_IP=xxx.xxx.xxx.xxx
  3. 执行以下命令,不断访问ASM网关。

    for i in {1..100}; do curl http://${ASM_GATEWAY_IP}/mock; echo ''; sleep 1; done;

    预期输出:

    -> mocka(version: v2, ip: 10.0.239.73)-> mockb(version: v2, ip: 10.0.239.136)-> mockc(version: v2, ip: 10.0.239.139)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v2, ip: 10.0.239.73)-> mockb(version: v2, ip: 10.0.239.136)-> mockc(version: v2, ip: 10.0.239.139)
    -> mocka(version: v2, ip: 10.0.239.73)-> mockb(version: v2, ip: 10.0.239.136)-> mockc(version: v2, ip: 10.0.239.139)
    -> mocka(version: v2, ip: 10.0.239.73)-> mockb(version: v2, ip: 10.0.239.136)-> mockc(version: v2, ip: 10.0.239.139)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    -> mocka(version: v2, ip: 10.0.239.73)-> mockb(version: v2, ip: 10.0.239.136)-> mockc(version: v2, ip: 10.0.239.139)
    -> mocka(version: v1, ip: 10.0.239.75)-> mockb(version: v1, ip: 10.0.239.138)-> mockc(version: v1, ip: 10.0.239.137)
    ...

    可以看到示例的应用包含三个服务mocka、mockb、mockc,服务调用链路为mocka→mockb→mockc,每个服务包含v1和v2两个版本。请求以接近1:1的比例发送到示例应用的v1和v2版本,同时,每个版本的服务调用链路彼此隔离,实现了全链路灰度的效果。

相关操作

发布应用服务的新版本

在本示例中使用的示例Git仓库文件结构如下:

image.png

文件

说明

mock-v1.yaml

mock-v2.yaml

mocka、mockb、mockc三个服务的v1和v2版本部署定义。

swimlanegroup.yaml

泳道组定义。一个泳道组与一个或多个泳道相关联,泳道组中主要定义多条流量泳道需要共享的信息,包括services(指定应该为哪些K8s服务建立泳道隔离环境)、ingress(指定通过哪个网关规则对泳道组内的服务进行访问)等。

swimlanes.yaml

泳道定义,其中定义了v1和v2两个ASMSwimLane资源。泳道主要通过labelSelector定义区分服务不同版本的标签选择器。

mock-route.yaml

定义了作用于ASM入口网关的网关规则(Gateway)和虚拟服务(VirtualService)资源。这些流量管理规则资源负责控制ASM网关如何向泳道中的服务转发请求。

要发布应用服务的新版本,您可以Fork示例Git仓库https://github.com/AliyunContainerService/asm-labs.gitargocd-asm分支,并修改argo-cd/swimlane路径下的上述YAML资源,修改后向远程Git仓库进行提交。推送提交后,Argo CD将会自动同步您在Git仓库中的变更(步骤一中的Repository URL要对应填写Fork后的Git仓库地址)。

以mocka、mockb、mockc三个服务发布新的v3版本为例,您需要对Git仓库中的YAML资源进行以下修改:

  1. 增加argo-cd/swimlane/mock-v3.yaml文件。

    YAML文件提供mocka、mockb、mockc三个服务的v3版本的部署定义。

    展开查看YAML

    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: mocka-v3
     labels:
     app: mocka
     version: v3
    spec:
     replicas: 1
     selector:
     matchLabels:
     app: mocka
     version: v3
     ASM_TRAFFIC_TAG: v3
     template:
     metadata:
     labels:
     app: mocka
     version: v3
     ASM_TRAFFIC_TAG: v3
     spec:
     containers:
     - name: default
     image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/go-http-sample:1.0
     imagePullPolicy: IfNotPresent
     env:
     - name: version
     value: v3
     - name: app
     value: mocka
     - name: upstream_url
     value: "http://mockb:8000/"
     ports:
     - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: mockb-v3
     labels:
     app: mockb
     version: v3
    spec:
     replicas: 1
     selector:
     matchLabels:
     app: mockb
     version: v3
     ASM_TRAFFIC_TAG: v3
     template:
     metadata:
     labels:
     app: mockb
     version: v3
     ASM_TRAFFIC_TAG: v3
     spec:
     containers:
     - name: default
     image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/go-http-sample:1.0
     imagePullPolicy: IfNotPresent
     env:
     - name: version
     value: v3
     - name: app
     value: mockb
     - name: upstream_url
     value: "http://mockc:8000/"
     ports:
     - containerPort: 8000
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: mockc-v3
     labels:
     app: mockc
     version: v3
    spec:
     replicas: 1
     selector:
     matchLabels:
     app: mockc
     version: v3
     ASM_TRAFFIC_TAG: v3
     template:
     metadata:
     labels:
     app: mockc
     version: v3
     ASM_TRAFFIC_TAG: v3
     spec:
     containers:
     - name: default
     image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/go-http-sample:1.0
     imagePullPolicy: IfNotPresent
     env:
     - name: version
     value: v3
     - name: app
     value: mockc
     ports:
     - containerPort: 8000
  2. 修改argo-cd/swimlane/swimlanes.yaml文件为以下内容。

    本示例在文件中增加了名为v3的流量泳道(ASMSwimLane)资源。该资源关联名为mock的泳道组(ASMSwimLaneGroup)资源,并指定带有version:v3标签的Pod为v3版本的服务。

    展开查看YAML

    apiVersion: istio.alibabacloud.com/v1
    kind: ASMSwimLane
    metadata:
     labels:
     swimlane-group: mock
     name: v1
    spec:
     labelSelector:
     version: v1
    ---
    apiVersion: istio.alibabacloud.com/v1
    kind: ASMSwimLane
    metadata:
     labels:
     swimlane-group: mock
     name: v2
    spec:
     labelSelector:
     version: v2
    ---
    apiVersion: istio.alibabacloud.com/v1
    kind: ASMSwimLane
    metadata:
     labels:
     swimlane-group: mock
     name: v3
    spec:
     labelSelector:
     version: v3
  3. 修改argo-cd/swimlane/mock-route.yaml文件为以下内容。

    本示例修改了文件中名为mock的虚拟服务(VirtualService),在路由目标中增加v3版本的mocka服务,其中subset字段对应流量泳道(ASMSwimLane)的名称。同时,修改虚拟服务中的weight字段,将v1、v2、v3三个版本的流量灰度比例调整为6:3:1。

    展开查看YAML

    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
     name: ingressgateway
     namespace: istio-system
    spec:
     selector:
     istio: ingressgateway
     servers:
     - port:
     number: 80
     name: http
     protocol: HTTP
     hosts:
     - '*'
    ---
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
     name: mock
     namespace: istio-system
    spec:
     gateways:
     - ingressgateway
     hosts:
     - '*'
     http:
     - match:
     - uri:
     exact: /mock
     route:
     - destination:
     host: mocka.default.svc.cluster.local
     subset: v1
     weight: 60
     - destination:
     host: mocka.default.svc.cluster.local
     subset: v2
     weight: 30
     - destination:
     host: mocka.default.svc.cluster.local
     subset: v3
     weight: 10