基于位置的负载均衡

微服务需要相互交互才能为客户提供完整的功能。为了确保客户获得尽可能最佳的性能,将流量路由到最近(在aws体现为同一az)的微服务——而不是 Kubernetes 默认提供的round robin方式 ——是极其有意义的:

  • 成本考虑的大头,因为如果流量在az之间流动,大多数云提供商会收取出口费用。同一az内的流量被视为内部流量,因此不收费。Istio 提供基于位置的路由(locality-based routing),将流量路由到最靠近源 Pod 的 Pod。如果使用 Istio 构建网格,将流量路由到同一az内的服务将节省大量的出口费用。

  • 让用户体验低延迟,为大家带来双赢!

准备工作

本实验基于aws eks,我们的node分布在us-west-2的三个az(根据实际情况也可以是其他region,这里以美西演示)。


可参考第一章的内容来安装istio,不需要安装测试的book info demo。

Istio 使用节点标签(node label)来了解源流量的来源区域。 对于云上托管的 Kubernetes 集群,云提供商会负责标记节点:

image-20211107151551354

我们看到

  • Node ip-192-168-24-47.us-west-2.compute.internalus-west-2b.
  • Node ip-192-168-51-166.us-west-2.compute.internalus-west-2c.
  • Node ip-192-168-76-201.us-west-2.compute.internalus-west-2a.

创建两个namespace, frontendbackend:

$ kubectl create ns frontend
namespace/frontend created
$ kubectl create ns backend
namespace/backend created

标记命名空间以启用 Istio 的自动sidecar注入:

$ kubectl label namespace frontend istio-injection=enabled
namespace/frontend labeled
$ kubectl label namespace backend istio-injection=enabled
namespace/backend labeled

部署BackEnd Nginx服务

在 Kubernetes 上部署 nginx的两个版本(v1v2)。 在 nginx-v1 上添加 us-west-2a 节点选择器, 在 nginx-v2 上添加 us-west-2b 节点选择器.

部署 nginx-v1:

$ kubectl apply -f - <<EOF
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/bharamicrosystems/nginx:v1
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
      nodeSelector:
        topology.kubernetes.io/zone: us-west-2a
EOF

部署nginx-v2

kubectl apply -f - <<EOF
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/bharamicrosystems/nginx:v2
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
      nodeSelector:
        topology.kubernetes.io/zone: us-west-2b
EOF

创建 nginx service ,来将pod服务暴露出来:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: backend
  labels:
    app: nginx
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: nginx
EOF

列出 pod ,以检查它们是否在正确的区域中运行:

image-20211107152645821

执行结果和上面kubectl get no的结果匹配,v1运行在us-west-2a, v2运行在us-west-2b

部署frontend服务

创建具有3个replica的 sleep deployment。 我们用它来生成到后端 NGINX pod 的流量:

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
  namespace: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["sleep","3600000"]
        imagePullPolicy: IfNotPresent
EOF

列出 pod 以查看它们是否在所有节点上运行:

image-20211107154943579

可以看到三个pod分布在3个不同的节点上

我们还需要为 sleep pod 创建一个服务。 这对于 Istio 的服务发现以允许基于位置的负载平衡是必要的:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: sleep
  namespace: frontend
  labels:
    app: sleep
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: sleep
EOF

如果不为sleep pod创建service,则后面istio的基于位置的负载均衡不会生效!

将两个az的pod名称导出到环境变量:

export SLEEP_ZONE_1=sleep-dd5468498-p8wlk  #这个pod在us-west-2a
export SLEEP_ZONE_2=sleep-dd5468498-kfc8l  #这个pod在us-west-2b

us-west-2asleep pod上执行curl命令 ,调用后端 NGINX 服务:

for i in {1..10}
do
  kubectl exec -it $SLEEP_ZONE_1 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'
done

image-20211107155325728

它们现在完全以round-robin方式进行负载均衡。

Locality-Prioritised Load Balancing

Locality-prioritised负载平衡使用以下算法工作:

  • 默认寻找相同az的pod,并将流量转发过去
  • 如果同az的pod挂掉,则将流量转到另一个az的pod

img

为了启用基于位置的负载均衡,我们需要有一个virtualService和一个带有outlier策略的DestinationRule

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: nginx
  namespace: backend
spec:
  hosts:
    - nginx
  http:
  - route:
    - destination:
        host: nginx
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: nginx
  namespace: backend
spec:
  host: nginx
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 7
      interval: 30s
      baseEjectionTime: 30s
EOF

us-west-2asleep pod上执行curl命令 ,调用后端 NGINX 服务:

for i in {1..10}; do   kubectl exec -it $SLEEP_ZONE_1 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

image-20211107160348720

如我们所见,所有请求都将发送到 nginx-v1

us-west-2bsleep pod上执行curl命令 ,调用后端 NGINX 服务:

for i in {1..10}; do   kubectl exec -it $SLEEP_ZONE_2 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

image-20211107212153510

我们看到所有请求都将发送到 nginx-v2。 这表明基于位置的负载均衡工作正常!

让我们尝试下把一个az的后端服务干掉会怎样。 打开终端并运行以下命令:

$ for i in {1..100}; do   kubectl exec -it $SLEEP_ZONE_2 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

当上面的命令运行时,在另一个终端中删除 nginx-v2 deployment:

$ kubectl delete deployment nginx-v2 -n backend
deployment.apps "nginx-v2" deleted

切换回第一个终端,在我们删除 nginx-v2 部署后,你应该看到流量流向 nginx-v1

image-20211107212442860

基于位置的加权负载均衡(Locality-Weighted Load Balancing)

大多数情况下,我们想将流量转发到相同的az。 在某些场景下我们可能想将流量分发到多个az,对于此类场景,可以使用基于位置的加权负载均衡(下图中展示了,80%的流量将转发到相同az,20%的流量转发到另一个az):

img

在进行之前,重新创建下在上一节中删除的 nginx-v2 deployment:

kubectl apply -f - <<EOF
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/bharamicrosystems/nginx:v2
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
      nodeSelector:
        topology.kubernetes.io/zone: us-west-2b
EOF

应用如下的 YAML 规则:

  • 将 80% 的流量从 us-west-2a 路由到 us-west-2a,将剩余 20% 的流量路由到 us-west-2b
  • 将 80% 的流量从 us-west-2b 路由到 us-west-2b,将剩余 20% 的流量路由到 us-west-2a
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: nginx
  namespace: backend
spec:
  host: nginx
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 7
      interval: 30s
      baseEjectionTime: 30s
    loadBalancer:
      localityLbSetting:
        enabled: true
        distribute:
        - from: us-west-2/us-west-2a/*
          to:
            "us-west-2/us-west-2a/*": 80
            "us-west-2/us-west-2b/*": 20
        - from: us-west-2/us-west-2b/*
          to:
            "us-west-2/us-west-2a/*": 20
            "us-west-2/us-west-2b/*": 80
EOF

us-west-2asleep pod上执行curl命令 ,调用后端 NGINX 服务:

for i in {1..10}; do   kubectl exec -it $SLEEP_ZONE_1 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

image-20211107213658294

我们看到 20% 的流量流向了 nginx-v2,80% 流向了 nginx-v1

us-west-2bsleep pod上执行curl命令 ,调用后端 NGINX 服务:

for i in {1..10}; do   kubectl exec -it $SLEEP_ZONE_2 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

image-20211107213801452

我们看到 20% 的流量流向了 nginx-v1,80% 流向了 nginx-v2


在运行时删除 nginx-v2 deployment会发生什么? 让我们来测试一下。

打开一个终端窗口并运行以下命令:

$ for i in {1..100}; do   kubectl exec -it $SLEEP_ZONE_2 -c sleep -n frontend -- sh -c 'curl  http://nginx.backend:8000'; done

当上面的命令运行时,在另一个终端窗口中删除 nginx-v2 deployment:

$ kubectl delete deployment nginx-v2 -n backend
deployment.apps "nginx-v2" deleted

切换回第一个终端,将看到在我们删除了 nginx-v2 deployment后,所有流量现在都流向了 nginx-v1

image-20211107213905295

参考:

https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/

https://betterprogramming.pub/locality-based-load-balancing-in-kubernetes-using-istio-a4a9defa05d3