动态子集路由

当您需要对微服务应用中的工作负载进行更精细化的管理和路由控制时,可以使用动态子集路由功能。在多应用多版本发布的场景下,ASM会自动根据应用特征将工作负载划分至对应的动态子集,减轻运维人员的手动配置负担。ASM还支持通过请求的特定Header与子集特征匹配,实现更灵活的请求路由。

前提条件

已添加集群到ASM实例,且ASM实例版本为1.18及以上。

功能介绍

ASM支持通过目标规则DestinationRule将服务下的工作负载通过标签划分为不同的子集,并支持声明路由规则,指向特定的子集。这种使用方式较为简明,也可以满足绝大多数情况下的需求。但在一些场景中,这种静态分组并静态路由的方式可能不够便利。例如,在以版本将工作负载划分为不同的子集的场景中,版本可能是不断增长的。这要求运维人员在新版本发布或旧版本下线时,新增或移除在DestinationRule中相应的子集配置。在多应用多版本频繁发布的场景下,这项工作会给运维人员增加一些额外的负担。

为了提高使用体验,ASM自1.18版本起,支持动态子集路由。您可以通过指定维度(例如版本),动态地将指定维度值相同的工作负载划分至同一个动态子集。使用动态子集应对上述场景则无需运维人员手动为每个新版本静态配置子集。部署工作负载时,ASM会自动地根据应用特征将其划分至对应动态子集。同时,ASM支持配置通过请求的特定Header与子集特征进行匹配,根据请求携带的Header将请求路由到指定动态子集。

步骤一:部署应用示例

本文以hashicorp/http-echo应用为例,部署dev和prod两套环境,分别模拟开发环境和生产环境。在dev环境下部署v1、v2、v3版本,在prod环境下只部署v2、v3版本。helloworld监听在5678端口,收到请求会回复自己的环境和版本。同时,部署服务helloworld,暴露8000端口,选中apphelloworld的工作负载。image.png

在ACK集群对应的KubeConfig环境下,使用以下YAML分别部署helloworld应用dev环境v1、v2和v3版本、prod环境v2和v3版本、helloworld服务以及用于发起测试的sleep应用。关于如何部署应用,请参见在ASM实例关联的集群中部署应用

dev环境

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-dev-v1
  labels:
    app: helloworld
    version: v1
    stage: dev 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld 
      version: v1
      stage: dev
  template:
    metadata:
      labels:
        app: helloworld 
        version: v1
        stage: dev
    spec:
      containers:
      - name: helloworld 
        env:
        - name: PODIP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STAGE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['stage']
        - name: VERSION 
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['version']
        command: ["/http-echo"]
        args:
          - "-text"
          - "Welcome to helloworld stage: $(STAGE), version: $(VERSION), ip: $(PODIP)"
        image: hashicorp/http-echo:0.2.3
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-dev-v2
  labels:
    app: helloworld
    version: v2
    stage: dev 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld 
      version: v2
      stage: dev
  template:
    metadata:
      labels:
        app: helloworld 
        version: v2
        stage: dev
    spec:
      containers:
      - name: helloworld 
        env:
        - name: PODIP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STAGE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['stage']
        - name: VERSION 
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['version']
        command: ["/http-echo"]
        args:
          - "-text"
          - "Welcome to helloworld stage: $(STAGE), version: $(VERSION), ip: $(PODIP)"
        image: hashicorp/http-echo:0.2.3
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-dev-v3
  labels:
    app: helloworld
    version: v3
    stage: dev 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld 
      version: v3
      stage: dev
  template:
    metadata:
      labels:
        app: helloworld 
        version: v3
        stage: dev
    spec:
      containers:
      - name: helloworld 
        env:
        - name: PODIP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STAGE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['stage']
        - name: VERSION 
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['version']
        command: ["/http-echo"]
        args:
          - "-text"
          - "Welcome to helloworld stage: $(STAGE), version: $(VERSION), ip: $(PODIP)"
        image: hashicorp/http-echo:0.2.3
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5678

prod环境

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-prod-v2
  labels:
    app: helloworld
    version: v2
    stage: prod 
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld 
      version: v2
      stage: prod
  template:
    metadata:
      labels:
        app: helloworld 
        version: v2
        stage: prod
    spec:
      containers:
      - name: helloworld 
        env:
        - name: PODIP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STAGE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['stage']
        - name: VERSION 
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['version']
        command: ["/http-echo"]
        args:
          - "-text"
          - "Welcome to helloworld stage: $(STAGE), version: $(VERSION), ip: $(PODIP)"
        image: hashicorp/http-echo:0.2.3
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-prod-v3
  labels:
    app: helloworld
    version: v3
    stage: prod 
spec:
  replicas: 2
  selector:
    matchLabels:
      app: helloworld 
      version: v3
      stage: prod
  template:
    metadata:
      labels:
        app: helloworld 
        version: v3
        stage: prod
    spec:
      containers:
      - name: helloworld 
        env:
        - name: PODIP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: STAGE
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['stage']
        - name: VERSION 
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['version']
        command: ["/http-echo"]
        args:
          - "-text"
          - "Welcome to helloworld stage: $(STAGE), version: $(VERSION), ip: $(PODIP)"
        image: hashicorp/http-echo:0.2.3
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5678

helloworld服务

apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
    service: helloworld
spec:
  ports:
  - port: 8000 
    name: http
    targetPort: 5678
  selector:
    app: helloworld

sleep应用

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /etc/sleep/tls
          name: secret-volume
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true

步骤二:访问指定环境的指定版本

  1. 部署目标规则和虚拟服务。

    1. 使用以下内容,为helloworld服务配置目标规则。具体操作,请参见管理目标规则

      Helloworld应用部署完毕后,在默认情况下,访问helloworld服务的请求将被K8s负载均衡分配到任意一个helloworld应用Pod。本文为helloworld应用部署2个环境多个版本(dev环境v1、v2和v3版本、prod环境v2和v3版本)。要访问指定环境和版本,需要为helloworld服务配置目标规则DestinationRule。

      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        name: helloworld
        namespace: default
      spec:
        host: helloworld.default.svc.cluster.local
        trafficPolicy:
          loadBalancer:
            dynamicSubset:
              subsetSelectors:
                - keys:
                    - stage
                    - version

      以上目标规则为helloworld服务配置了一个以stage和version标签进行分组的规则。本例中部署的工作负载将被该规则分为以下几个分组。

      分组

      Pod

      地址

      stage = dev

      version = v1

      helloworld-dev-v1-67b6876778-nf7pz

      192.168.0.5

      stage = dev

      version = v2

      helloworld-dev-v2-68f65bbc99-v957l

      192.168.0.1

      stage = dev

      version = v3

      helloworld-dev-v3-7f6978bc56-hqzgg

      192.168.0.252

      stage = prod

      version = v2

      helloworld-prod-v2-b5745b949-p8rc4

      192.168.0.103

      stage = prod

      version = v3

      helloworld-prod-v3-6768bf56f8-6bd6h

      192.168.0.104

      helloworld-prod-v3-6768bf56f8-6bd6h

      192.168.0.6

    2. 使用以下内容,为helloworld服务创建虚拟服务,建立请求到动态分组key之间的映射关系。具体操作,请参见管理虚拟服务

      展开查看虚拟服务YAML

      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: helloworld
        namespace: default
      spec:
        hosts:
          - helloworld.default.svc.cluster.local
        http:
          - headerToDynamicSubsetKey:
              - defaultValue: v3
                header: x-version
                key: version
              - defaultValue: prod
                header: x-stage
                key: stage
            name: default
            route:
              - destination:
                  host: helloworld.default.svc.cluster.local
                  port:
                    number: 8000
      

      以上虚拟服务指定:

      • 将请求中名为x-version的Header的值映射到分组名为version的分组key。若请求未携带x-version Header,则使用默认值v3

      • 将请求中名为x-stage的Header的值映射到名为stage的分组key。若请求未携带x-stage Header,则使用默认值prod

  2. 在ACK集群对应的KubeConfig环境下,执行以下命令,从sleep应用发起对helloworld应用dev环境的v1版本的访问。

    关于如何通过kubectl管理集群,请参见获取集群KubeConfig并通过kubectl工具连接集群

    kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: dev' -H 'x-version: v1' helloworld:8000

    预期输出:

    Welcome to helloworld stage: dev, version: v1, ip: 192.168.0.5

    由预期输出得到,请求被正确地路由至stage标签为devversion标签为v1的Pod。

步骤三:为分组配置回退策略

prod环境中未部署v1版本,若尝试访问prod环境的v1版本,会因为该分组不存在而报错。

kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' -H 'x-version: v1' helloworld:8000
预期输出:
no healthy upstream # 由于分组不存在,产生报错。

为了避免分组不存在时访问失败,您可以为动态分组规则配置回退策略。下文介绍ASM动态分组支持的三种回退策略:NO_FALLBACKANY_ENDPOINTDEFAULT_SUBSET

NO_FALLBACK

为动态分组规则使用该策略,当无法匹配任何分组时,请求将会报no healthy upstream错误。

  1. 使用如下目标规则,为stageversion分组规则指定fallbackPolicyNO_FALLBACK。具体操作,请参见管理目标规则

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: helloworld
      namespace: default
    spec:
      host: helloworld.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          dynamicSubset:
            subsetSelectors:
              - fallbackPolicy: NO_FALLBACK
                keys:
                  - stage
                  - version
  2. 执行以下命令,访问stageprodversionv1的helloworld应用。

    kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' -H 'x-version: v1' helloworld:8000

    预期输出:

    no healthy upstream # 由于分组不存在,产生报错。

ANY_ENDPOINT

为动态分组规则使用该策略,当无法匹配任何分组时,请求将被路由至服务下的任意端点。

  1. 使用如下目标规则,为stageversion分组规则指定fallbackPolicyANY_ENDPOINT。具体操作,请参见管理目标规则

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: helloworld
      namespace: default
    spec:
      host: helloworld.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          dynamicSubset:
            subsetSelectors:
              - fallbackPolicy: ANY_ENDPOINT
                keys:
                  - stage
                  - version
  2. 执行以下命令,访问stageprodversionv1的helloworld应用。

    • 第一次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v2, ip: 192.168.0.103 # 第一次访问被路由至prod, v2。
    • 第二次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' -H 'x-version: v1' helloworld:8000

      预期输出:

      Welcome to helloworld stage: dev, version: v2, ip: 192.168.0.1 # 第二次访问被路由至dev, v2。

      由预期输出得到,请求被路由至helloworld服务下的随机Pod。

DEFAULT_SUBSET

为动态分组规则使用该策略,当无法匹配任何分组时,请求将被路由至defaultSubset指定的key匹配的分组。

  1. 使用如下目标规则,为stageversion分组规则指定fallbackPolicyDEFAULT_SUBSET,并配置defaultSubsetkeyvalue。具体操作,请参见管理目标规则

    key

    value

    stage

    prod

    version

    v3

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: helloworld
      namespace: default
    spec:
      host: helloworld.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          dynamicSubset:
            defaultSubset:
              stage: prod
              version: v3
            subsetSelectors:
              - fallbackPolicy: DEFAULT_SUBSET
                keys:
                  - stage
                  - version
  2. 执行以下命令,访问stageprodversionv1的分组。

    • 第一次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' -H 'x-version: v1' helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v3, ip: 192.168.0.6
    • 第二次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-stage: prod' -H 'x-version: v1' helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v3, ip: 192.168.0.104

      由预期输出得到,由于分组不存在,触发回退,使用stagprodversionv3匹配分组,符合预期。

步骤四:访问指定Pod

若您需要访问到指定Pod,可以通过ASM动态分组内置keys%ip%进行分组,将每个Pod划分独立分组。

  1. 使用以下目标规则,新增一个以Pod IP维度进行分组的分组规则。具体操作,请参见管理目标规则

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: helloworld
      namespace: default
    spec:
      host: helloworld.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          dynamicSubset:
            defaultSubset:
              stage: prod
              version: v3
            subsetSelectors:
              - keys:
                  - '%ip%'
  2. 使用如下虚拟服务,将请求的x-ip Header映射到%ip%分组的规则,使请求可以通过x-ip指定希望访问的Pod IP。具体操作,请参见管理虚拟服务

    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: helloworld
      namespace: default
    spec:
      hosts:
        - helloworld.default.svc.cluster.local
      http:
        - headerToDynamicSubsetKey:
            - header: x-ip
              key: '%ip%'
          name: default
          route:
            - destination:
                host: helloworld.default.svc.cluster.local
                port:
                  number: 8000
    
  3. 执行以下命令,对Pod helloworld-prod-v3-6768bf56f8-6bd6h进行访问,指定Pod地址192.168.0.6,进行多次访问。

    • 第一次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-ip: 192.168.0.6'  helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v3, ip: 192.168.0.6
    • 第二次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-ip: 192.168.0.6'  helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v3, ip: 192.168.0.6
    • 第三次访问:

      kubectl exec -it deploy/sleep -c sleep -- curl -H 'x-ip: 192.168.0.6'  helloworld:8000

      预期输出:

      Welcome to helloworld stage: prod, version: v3, ip: 192.168.0.6

      由预期输出得到,请求都被路由至指定的Pod地址192.168.0.6,符合预期。

CRD说明

VirtualService

  • HTTPRoute

    ASM对HTTPRoute进行扩展,增加headerToDynamicSubsetKey字段。

    字段

    类型

    说明

    headerToDynamicSubsetKey

    HeaderToMetadataSubsetKey[]

    用于配置请求Header到动态子集key的映射。数组中的每一个元素为一个Header到一个key的映射。

  • HeaderToMetadataSubsetKey

    配置请求Header到动态子集key的映射关系或默认值。

    字段

    类型

    说明

    header

    string

    请求头名称。

    key

    string

    动态子集key名称,其中%开头结尾的表示工作负载内置属性。

    defaultValue

    string

    若请求没有携带key指定的Header,则使用该默认值。如果不配置默认值,则在请求未携带该Header时视作该维度缺失,该维度不参与匹配。

DestinationRule

ASM对trafficPolicy结构进行扩展,增加dynamicSubset字段。

  • TrafficPolicy

    字段

    类型

    说明

    dynamicSubset

    DynamicSubsetLB

    用于配置动态分组规则。

  • DyunamicSubsetLB

    字段

    类型

    说明

    defaultSubset

    map[string]string

    用于配置默认分组,当请求无法匹配任何动态分组,且分组规则的回退策略为DEFAULT_SUBSET时,使用该配置声明的维度值选择分组。

    subsetSelectors

    SubsetSelector[]

    用于配置动态分组规则,数组中每一项为一个独立的分组规则。

    fallbackPolicy

    DynamicSubsetLB_FallbackPolicy

    指定无法匹配动态子集时的回退策略。若不指定该配置,则默认为NO_FALLBACK。

  • SubsetSelector

    字段

    类型

    说明

    keys

    string[]

    分组维度列表,值映射至工作负载标签。例如,version表示以工作负载的名为version的标签作为分组维度。除标签外,还支持以工作负载内置属性作为维度。更多信息,请参见工作负载内置属性

    fallbackPolicy

    DynamicSubsetLB_FallbackPolicy

    指定无法匹配动态子集时的回退策略。若不指定该配置,则使用DyunamicSubsetLB中指定的fallbackPolicy。

  • DynamicSubsetLB_FallbackPolicy

    表示动态子集路由回退策略的枚举类型,支持的值如下:

    说明

    NO_FALLBACK

    不进行回退。

    ANY_ENDPOINT

    使用服务下任意端点。

    DEFAULT_SUBSET

    使用声明的默认子集进行回退。

工作负载内置属性

属性

类型

说明

%ip%

string

工作负载的Pod地址。

相关文档

  • 您可以启用控制平面日志采集和日志告警,及时发现和解决潜在的风险。具体操作,请参见启用控制平面日志采集和日志告警

  • 您可以安装诊断工具asmctl,检测ASM存在的配置问题。具体操作,请参见安装和使用诊断工具asmctl

  • 您可以为ASM资源(VirtualService、DestinationRule等)的变更行为添加审计告警能力,在重要资源变动时及时发出告警通知到告警联系人。具体操作,请参见为网格资源操作配置审计告警

  • 您可以基于VirtualService和DestinationRule等流量规则实现流量泳道,同时通过配置流量降级,在某个版本(或者其他特征)的应用不可用时,将流量发往一个指定的降级版本(或其他特征)的应用。具体操作,请参见基于流量规则配置实现流量泳道和流量降级

  • 如果您需要在客户端对一个目标服务的访问过程中,使流量尽可能的在同一个可用区内流转,以保证服务间的调用延迟最低,可以使用同可用区优先路由功能。具体操作,请参见使用网格拓扑观测同可用区优先路由