基于Ingress-APISIX网关实现全链路灰度

简介: 本文介绍了通过将 APISIX 提供的灵活的路由能力以及 MSE 提供的全链路灰度能力结合,可以在不需要修改任何业务代码的情况下,轻松实现全链路灰度能力。


微服务架构下,有一些需求开发涉及到微服务调用链路上的多个微服务同时改动。通常每个微服务都会有灰度环境或分组来接受灰度流量。我们希望进入上游灰度环境的流量也能进入下游灰度的环境中,确保1个请求始终在灰度环境中传递。即使这个调用链路上有一些微服务应用不存在灰度环境,那么这些微服务应用在请求下游应用的时候依然能够回到下游应用的灰度环境中。我们通过将 APISIX 提供的灵活的路由能力以及 MSE 提供的全链路灰度能力结合,可以在不需要修改任何业务代码的情况下,轻松实现上述所说的全链路灰度能力。

1. 前提条件

2. 概述

2.1 方案介绍

本文主要介绍基于 Ingress-APISIX 的全链路灰度功能。假设应用的架构由 Ingress-APISIX 网关以及后端的微服务架构(Spring Cloud)组成,后端调用链路有3个:购物车(A),交易中心(B),库存中心(C),可以通过客户端或者是HTML来访问后端服务,这些服务之间通过Nacos注册中心实现服务发现。

2.2 目标读者

希望通过极简的配置与无代码侵入的方式,使用 Ingress-APISIX/APISIX 网关来实现全链路灰度功能。

2.3 适用场景

场景一:按照域名路由,实现全链路灰度

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

image.png

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

场景二:按照指定请求参数进行路由,实现全链路灰度

有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的参数来路由到灰度环境。例如下图中,通过env=gray这个请求参数,来访问灰度环境。

image.png

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

2.4 相关概念

  • MSE :阿里云微服务引擎

  • 全链路:请求从上往下的过程达到超过2个应用。

  • Ingress:对集群中服务的外部访问进行管理的API 对象。

  • APISIX:Apache APISIX 是一个动态、实时、高性能的API 网关。

  • 实例:通常对应一个应用进程,是服务的承载实体,通常一个服务集群有多个实例。

3. 方案实施

3.1 前提条件

  • 安装 Ingress-APISIX 组件

APISIX 架构如下图所示,我们需要安装APISIX、

image.png

  1. 安装apisix、apisix-ingress-controller、etcd等组件

helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
kubectl create ns ingress-apisix
helm install apisix apisix/apisix \
  --set gateway.type=LoadBalancer \
  --set ingress-controller.enabled=true \
  --set etcd.persistence.storageClass="alicloud-disk-ssd" \
  --set etcd.persistence.size="20Gi" \
  --namespace ingress-apisix \
  --set ingress-controller.config.apisix.serviceNamespace=ingress-apisix
kubectl get service --namespace ingress-apisix

看到在ingress-apisix命名空间下看到无状态 apisix、apisix-ingress-controller应用、以及有状态的etcd应用

  1. 安装APISIX Admin

helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix-dashboard apisix/apisix-dashboard --namespace ingress-apisix

安装完成后,可以绑定一个SLB

image.png

通过{slb-ip}:9000访问APISIX控制台(默认密码admin/admin)

image.png

  • 开启 MSE 微服务治理

  1. 开通微服务治理专业版:

  1. 单击开通MSE微服务治理

  2. 微服务治理版本选择专业版,选中服务协议,然后单击立即开通。关于微服务治理的计费详情,请参见价格说明

  1. 安装MSE微服务治理组件:

  1. 容器服务控制台左侧导航栏中,选择市场 > 应用市场

  2. 应用市场页面单击应用目录页签,然后搜索并单击ack-onepilot组件。

  3. ack-onepilot页面右上方单击一键部署,在创建面板中选择集群命名空间,设置组件发布名称,然后单击下一步说明 推荐使用默认的命名空间ack-onepilot

  4. 参数配置向导中确认组件参数信息,然后单击确定。安装完成后,在命名空间ack-onepilot中出现ack-onepilot应用,表示安装成功。

  1. 为应用开启微服务治理:

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

  2. 在左侧导航栏选择微服务治理中心 > K8s集群列表

  3. K8s集群列表页面搜索目标集群,单击图标,然后单击目标集群操作列下方的管理

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

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

  • 部署Demo应用程序

  1. 容器服务控制台左侧导航栏中,单击集群

  2. 集群列表页面中,单击目标集群名称或者目标集群右侧操作列下的详情

  3. 在集群管理页左侧导航栏中,选择工作负载 > 无状态

  4. 无状态页面选择命名空间,然后单击使用YAML创建资源

  5. 对模板进行相关配置,完成配置后单击创建

本文示例中部署A、B、C三个应用,每个应用分别部署一个基线版本和一个灰度版本;并部署一个Nacos server应用,用于实现服务发现。

# A 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
      
# A 应用 gray 版本
---            
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
            
# B 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
            
# B 应用 gray 版本  
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
            
# C 应用 base 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
            
# C 应用 gray 版本
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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: 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
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30

# Nacos Server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  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
      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
  • 网络配置

  1. 针对入口应用 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
  1. 在APISIX Admin上面对步骤1中配置service进行上游服务配置

image.png

spring-cloud-a-svc 的上游配置如下

{
  "nodes": [
    {
      "host": "spring-cloud-a-svc",
      "port": 20001,
      "weight": 1
    }
  ],
  "timeout": {
    "connect": 6,
    "send": 6,
    "read": 6
  },
  "type": "roundrobin",
  "scheme": "http",
  "pass_host": "pass",
  "name": "spring-cloud-a-svc",
  "keepalive_pool": {
    "idle_timeout": 60,
    "requests": 1000,
    "size": 320
  }
}

spring-cloud-a-gray-svc的上游配置如下

{
  "nodes": [
    {
      "host": "spring-cloud-a-gray-svc",
      "port": 20001,
      "weight": 1
    }
  ],
  "timeout": {
    "connect": 6,
    "send": 6,
    "read": 6
  },
  "type": "roundrobin",
  "scheme": "http",
  "pass_host": "pass",
  "name": "spring-cloud-a-gray-svc",
  "keepalive_pool": {
    "idle_timeout": 60,
    "requests": 1000,
    "size": 320
  }
}

image.png

应用场景一:按照域名路由,实现全链路灰度

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

image.png

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

3.2 配置APISIX路由规则

  1. 在APISIX控制台选择路由

  2. 点击创建

  3. 匹配条件中域名、请求路径选择/*,选择对应的上游

分别配置如下路由,当host为www.base.com时,路由到 id 为 401152455435354xxx 所对应的上游,即spring-cloud-a-svc;当host为www.gray.com时,路由到 id 为 401163331936715xxx 所对应的上游,即spring-cloud-a-gray-svc。

  • base对应的路由配置

{
  "uri": "/*",
  "name": "spring-cloud-a",
  "methods": [
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "PATCH",
    "HEAD",
    "OPTIONS",
    "CONNECT",
    "TRACE"
  ],
  "host": "www.base.com",
  "upstream_id": "401152455435354xxx",
  "labels": {
    "API_VERSION": "0.0.1"
  },
  "status": 1
}
  • gray对应的路由配置

{
  "uri": "/*",
  "name": "spring-cloud-a-gray",
  "priority": 1,
  "methods": [
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "PATCH",
    "HEAD",
    "OPTIONS",
    "CONNECT",
    "TRACE"
  ],
  "host": "www.gray.com",
  "upstream_id": "401163331936715xxx",
  "labels": {
    "API_VERSION": "0.0.1"
  },
  "status": 1
}

3.3 配置MSE全链路灰度

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

  2. 在顶部菜单栏选择地域。

  3. 在左侧导航栏选择微服务治理中心 > 全链路灰度

  4. 全链路灰度页面,单击创建泳道组及泳道。如果您选择的微服务空间内已经创建过泳道组,则单击+创建泳道组

  5. 添加spring-cloud-a、spring-cloud-b、spring-cloud-c应用进入泳道组。

image.png

  1. 创建泳道面板中设置泳道组相关参数,然后单击确定

  2. 全链路灰度页面上方选择创建和泳道组时相同的微服务空间,然后底部单击点击创建第一个分流泳道

  3. 创建泳道面板中设置流控泳道相关参数,选择标签gray,然后单击确定。

image.png

  1. 创建gray泳道完成。

image.png

结果验证

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

curl -H"Host:www.base.com" http://47.97.xxx.xxx/a
A[172.18.144.15] -> B[172.18.144.125] -> C[172.18.144.90]%

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

curl -H"Host:www.gray.com" http://47.97.xxx.xxx/a
Agray[172.18.144.16] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]% 

注意:其中47.97.xxx.xxx为apisix的公网ip

应用场景二:按照指定请求参数进行路由,实现全链路灰度

有些客户端没法改写域名,希望能访问 www.demo.com 通过传入不同的参数来路由到灰度环境。例如下图中,通过env=gray这个请求参数,来访问灰度环境。

image.png

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

3.4 配置APISIX路由规则

  1. 在APISIX控制台选择路由

  2. 点击创建

  3. 匹配条件中新建高级匹配规则、请求路径选择/*,选择对应的上游

分别配置如下路由,当host为www.demo.com,请求参数env=gray时,路由优先匹配到 id 为 401163331936715xxx 所对应的上游,即spring-cloud-a-gray-svc;否则host为www.demo.com时,路由到 id 为 401152455435354xxx 所对应的上游,即spring-cloud-a-svc。

  • base对应的路由配置

{
  "uri": "/*",
  "name": "spring-cloud-a",
  "methods": [
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "PATCH",
    "HEAD",
    "OPTIONS",
    "CONNECT",
    "TRACE"
  ],
  "host": "www.demo.com",
  "upstream_id": "401152455435354xxx",
  "labels": {
    "API_VERSION": "0.0.1"
  },
  "status": 1
}
  • gray对应的路由配置

image.png

{
  "uri": "/*",
  "name": "spring-cloud-a-gray",
  "priority": 1,
  "methods": [
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "PATCH",
    "HEAD",
    "OPTIONS",
    "CONNECT",
    "TRACE"
  ],
  "host": "www.demo.com",
  "vars": [
    [
      "arg_env",
      "==",
      "gray"
    ]
  ],
  "upstream_id": "401163331936715xxx",
  "labels": {
    "API_VERSION": "0.0.1"
  },
  "status": 1
}

3.5 配置MSE全链路灰度

配置MSE全链路灰度步骤与应用场景一一致。

结果验证

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

curl -H"Host:www.demo.com" http://47.97.xxx.xxx/a
A[172.18.144.15] -> B[172.18.144.125] -> C[172.18.144.90]%

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

curl -H"Host:www.demo.com" http://47.97.xxx.xxx/a?env=gray
Agray[172.18.144.16] -> Bgray[172.18.144.57] -> Cgray[172.18.144.157]% 

注意:其中47.97.xxx.xxx为apisix的公网ip

讲解视频


作者介绍
目录