在大规模服务场景下,成千上万个服务运行在不同的地域,这些服务需要相互调用来完成完整的功能。为了确保获得最佳性能,应当将流量路由到最近的服务,使得流量尽可能在同一个区域内,而不是只依赖于Kubernetes默认提供的轮询方式进行负载均衡。基于Istio的阿里云服务网格ASM产品提供了基于位置的路由能力,可以将流量路由到最靠近的容器。这样可以确保服务调用的低延迟,并尽量保持服务调用在同一区域内,降低流量费用。本文介绍如何在ASM内实现基于位置的路由请求,以提高性能并节省成本。

前提条件

  • 创建ASM实例
  • 创建一个多可用区的托管版ACK集群,集群使用的节点分别位于同一地域下的不同可用区(本文示例中分别为北京地域下的可用区G和H),详情请参见创建Kubernetes托管版集群

部署后端示例服务

在ACK集群中分别部署Nginx的两个版本(V1和V2),并通过Nodelabel将版本V1部署到可用区G,将版本V2部署到可用区H,操作步骤如下所示。

  1. 在ACK集群中部署Nginx版本V1,对应的YAML如下。
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mynginx-configmap-v1
      namespace: backend
    data:
      default.conf: |-
        server {
            listen       80;
            server_name  localhost;
    
            #charset koi8-r;
            #access_log  /var/log/nginx/host.access.log  main;
    
            location / {
                return 200 'v1\n';
            }
        } 
  2. 通过Nodelabel将版本V1部署到可用区G,对应的YAML如下。
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-v1
      namespace: backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
          version: v1
      template:
        metadata:
          labels:
            app: nginx
            version: v1
        spec:
          containers:
          - image: docker.io/nginx:1.15.9
            imagePullPolicy: IfNotPresent
            name: nginx
            ports:
            - containerPort: 80
            volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/conf.d
              readOnly: true
          volumes:
          - name: nginx-config
            configMap:
              name: mynginx-configmap-v1    
          nodeSelector:
            failure-domain.beta.kubernetes.io/zone: "cn-beijing-g"
  3. 在ACK集群中部署Nginx版本V2,对应的YAML如下。
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mynginx-configmap-v2
      namespace: backend
    data:
      default.conf: |-
        server {
            listen       80;
            server_name  localhost;
    
            #charset koi8-r;
            #access_log  /var/log/nginx/host.access.log  main;
    
            location / {
                return 200 'v2\n';
            }
        } 
  4. 通过Nodelabel将版本V2部署到可用区H,对应的YAML如下。
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-v2
      namespace: backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
          version: v2
      template:
        metadata:
          labels:
            app: nginx
            version: v2
        spec:
          containers:
          - image: docker.io/nginx:1.15.9
            imagePullPolicy: IfNotPresent
            name: nginx
            ports:
            - containerPort: 80
            volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/conf.d
              readOnly: true
          volumes:
          - name: nginx-config
            configMap:
              name: mynginx-configmap-v2 
          nodeSelector:
            failure-domain.beta.kubernetes.io/zone: "cn-beijing-h"     
  5. 部署后端示例应用服务,对应的YAML如下。
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      namespace: backend
      labels:
        app: nginx
    spec:
      ports:
      - name: http
        port: 8000
        targetPort: 80
      selector:
        app: nginx

部署客户端示例服务

在ACK集群中分别部署2个客户端示例服务,并通过Nodelabel将版本V1部署到可用区G,将版本V2部署到可用区H,对应的操作步骤如下。

  1. 在ACK集群中部署客户端示例服务,通过Nodelabel将版本V1部署到可用区G,对应的YAML如下所示。
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep-cn-beijing-g
      namespace: backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
          version: v1
      template:
        metadata:
          labels:
            app: sleep
            version: v1
        spec:
          containers:
          - name: sleep
            image: tutum/curl
            command: ["/bin/sleep","infinity"]
            imagePullPolicy: IfNotPresent
          nodeSelector:
            failure-domain.beta.kubernetes.io/zone: "cn-beijing-g"  
  2. 在ACK集群中部署客户端示例服务,通过Nodelabel将版本V2部署到可用区H,对应的YAML如下所示。
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep-cn-beijing-h
      namespace: backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
          version: v2
      template:
        metadata:
          labels:
            app: sleep
            version: v2
        spec:
          containers:
          - name: sleep
            image: tutum/curl
            command: ["/bin/sleep","infinity"]
            imagePullPolicy: IfNotPresent
          nodeSelector:
            failure-domain.beta.kubernetes.io/zone: "cn-beijing-h" 
  3. 部署客户端示例服务,对应的YAML如下。
    apiVersion: v1
    kind: Service
    metadata:
      name: sleep
      namespace: backend
      labels:
        app: sleep
    spec:
      ports:
      - name: http
        port: 80
      selector:
        app: sleep
  4. 执行如下脚本,在2个Sleep Pod中访问后端示例服务。
    echo "entering into the 1st container"
    export SLEEP_ZONE_1=$(kubectl get pods -lapp=sleep,version=v1 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_1 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    echo "entering into the 2nd container"
    export SLEEP_ZONE_2=$(kubectl get pods -lapp=sleep,version=v2 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_2 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    执行结果

    通过执行结果可以看到后端示例服务以循环方式进行负载均衡。

    entering into the 1st container
    v2
    v1
    v1
    v2
    v2
    v1
    v1
    v2
    v2
    v1
    v1
    v2
    v2
    v1
    v2
    v1
    v1
    v2
    v2
    v1
    entering into the 2nd container
    v2
    v2
    v1
    v1
    v2
    v1
    v2
    v1
    v2
    v2
    v1
    v1
    v2
    v2
    v1
    v2
    v1
    v2
    v1
    v1

本地优先负载均衡

本地优先的负载均衡算法会优先访问同一可用区的Pod,并在本可用区Pod不可用时,调用其他可用区的Pod。

为了实现本地优先的负载均衡,您需要具有虚拟服务和带有异常策略的目标规则。离群策略检查Pod是否健康,并做出路由决策。

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 控制平面区域,单击VirtualService页签,然后单击新建
  5. 新建页面,按照以下步骤定义VirtualService,然后单击确定
    1. 选择相应的命名空间。本文以选择backend命名空间为例。
    2. 在文本框中,定义Istio虚拟服务,YAML定义如下所示。
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: nginx
        namespace: backend
      spec:
        hosts:
          - nginx
        http:
        - route:
          - destination:
              host: nginx
  6. 新建页面,按照以下步骤定义DestinationRule,然后单击确定
    1. 选择相应的命名空间。本文以选择backend命名空间为例。
    2. 在文本框中,定义Istio目标规则,YAML定义如下所示。
      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: nginx
        namespace: backend
      spec:
        host: nginx
        trafficPolicy:
          outlierDetection:
            consecutiveErrors: 7
            interval: 30s
            baseEjectionTime: 30s
  7. 执行如下脚本,在2个Sleep Pod中再次访问后端示例服务。
    echo "entering into the 1st container"
    export SLEEP_ZONE_1=$(kubectl get pods -lapp=sleep,version=v1 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_1 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    echo "entering into the 2nd container"
    export SLEEP_ZONE_2=$(kubectl get pods -lapp=sleep,version=v2 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_2 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    执行结果

    通过执行结果可以看到以本地优先方式进行负载均衡,执行结果如下。

    entering into the 1st container
    v1
    v1
    v1
    v1
    v1
    v1
    v1
    v2
    v1
    v1
    v2
    v1
    v1
    v1
    v1
    v1
    v1
    v1
    v1
    v1
    entering into the 2nd container
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2

局部加权的负载均衡

大多数情况下使用本地优先的负载均衡可以满足平衡的要求,但是有时候您可能需要将流量分成多个区域,如果所有请求都来自单个区域,则容易使一个区域超载。此时可以使用局部加权的负载均衡。

譬如设置如下的加权规则:

  • 将从cn-beijing/cn-beijing-g的80%流量路由到cn-beijing/cn-beijing-g,20%的流量路由到cn-beijing/cn-beijing-h。
  • 将从cn-beijing/cn-beijing-h的20%流量路由到cn-beijing/cn-beijing-g,80%的流量路由到cn-beijing/cn-beijing-h。
  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 控制平面区域,单击VirtualService页签,然后单击新建
  5. 新建页面,按照以下步骤定义VirtualService,然后单击确定
    1. 选择相应的命名空间。本文以选择backend命名空间为例。
    2. 在文本框中,定义Istio虚拟服务,YAML定义如下所示。
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: nginx
        namespace: backend
      spec:
        hosts:
          - nginx
        http:
        - route:
          - destination:
              host: nginx
  6. 新建页面,按照以下步骤定义DestinationRule,然后单击确定
    1. 选择相应的命名空间。本文以选择backend命名空间为例。
    2. 在文本框中,定义Istio目标规则,YAML定义如下所示。
      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: nginx
        namespace: backend
      spec:
        host: nginx
        trafficPolicy:
          outlierDetection:
            consecutiveErrors: 7
            interval: 30s
            baseEjectionTime: 30s
          loadBalancer:
            localityLbSetting:
              enabled: true
              distribute:
              - from: cn-beijing/cn-beijing-g/*
                to:
                  "cn-beijing/cn-beijing-g/*": 80
                  "cn-beijing/cn-beijing-h/*": 20
              - from: cn-beijing/cn-beijing-h/*
                to:
                  "cn-beijing/cn-beijing-g/*": 20
                  "cn-beijing/cn-beijing-h/*": 80
  7. 执行如下脚本,在2个Sleep Pod中再次访问后端示例服务。
    echo "entering into the 1st container"
    export SLEEP_ZONE_1=$(kubectl get pods -lapp=sleep,version=v1 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_1 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    echo "entering into the 2nd container"
    export SLEEP_ZONE_2=$(kubectl get pods -lapp=sleep,version=v2 -n backend  -o 'jsonpath={.items[0].metadata.name}')
    for i in {1..20}
    do
      kubectl exec -it $SLEEP_ZONE_2 -c sleep -n backend -- sh -c 'curl  http://nginx.backend:8000'
    done
    执行结果
    通过执行结果可以看到以局部加权比例进行负载均衡,执行结果如下。
    entering into the 1st container
    v1
    v1
    v1
    v1
    v2
    v1
    v1
    v2
    v1
    v1
    v1
    v2
    v1
    v1
    v1
    v1
    v1
    v2
    v1
    v1
    entering into the 2nd container
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v2
    v1
    v2
    v1
    v2
    v2