微服务需要相互交互才能为客户提供完整的功能。为了确保客户获得尽可能最佳的性能,将流量路由到最近(在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 集群,云提供商会负责标记节点:
我们看到
ip-192-168-24-47.us-west-2.compute.internal
在 us-west-2b
. ip-192-168-51-166.us-west-2.compute.internal
在 us-west-2c
. ip-192-168-76-201.us-west-2.compute.internal
在 us-west-2a
.创建两个namespace, frontend
和 backend
:
$ 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
在 Kubernetes 上部署 nginx的两个版本(v1
和 v2
)。 在 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 ,以检查它们是否在正确的区域中运行:
执行结果和上面kubectl get no的结果匹配,v1运行在us-west-2a, v2运行在us-west-2b
创建具有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 以查看它们是否在所有节点上运行:
可以看到三个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-2a
的 sleep
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
它们现在完全以round-robin方式进行负载均衡。
Locality-prioritised负载平衡使用以下算法工作:
为了启用基于位置的负载均衡
,我们需要有一个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-2a
的 sleep
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
如我们所见,所有请求都将发送到 nginx-v1
!
在 us-west-2b
的 sleep
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
我们看到所有请求都将发送到 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
:
大多数情况下,我们想将流量转发到相同的az。 在某些场景下我们可能想将流量分发到多个az,对于此类场景,可以使用基于位置的加权负载均衡(下图中展示了,80%的流量将转发到相同az,20%的流量转发到另一个az):
在进行之前,重新创建下在上一节中删除的 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 规则:
us-west-2a
路由到 us-west-2a
,将剩余 20% 的流量路由到 us-west-2b
。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-2a
的 sleep
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
我们看到 20% 的流量流向了 nginx-v2
,80% 流向了 nginx-v1
。
在 us-west-2b
的 sleep
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
我们看到 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
:
参考:
https://istio.io/latest/docs/tasks/traffic-management/locality-load-balancing/
https://betterprogramming.pub/locality-based-load-balancing-in-kubernetes-using-istio-a4a9defa05d3