Kubernetes v1.34:通过PreferSameNode和PreferSameZone实现智能流量路由
Kubernetes已稳步发展成为容器编排的行业标准,从小型开发集群到超大规模AI和数据基础设施都在使用。每个新版本都会引入新特性,不仅使工作负载更易于管理,还提高了性能、成本效益和弹性。
在v1.34版本中,一个突出的增强功能是为Kubernetes服务引入了流量分发偏好。具体包括:
- PreferSameNode:如果可能,将流量路由到与客户端Pod相同的节点
- PreferSameZone:在跨区路由之前,优先考虑相同拓扑区域中的端点
这些策略为服务流量分发增加了更智能的、位置感知的路由功能。Kubernetes现在可以优先选择距离客户端更近的Pod,无论是在同一节点上还是在同一可用区中。
流量分发的重要性
传统上,Kubernetes服务会均匀地平衡与其选择器匹配的所有端点/Pod之间的流量。这种均匀的流量分发简单、可预测,适用于大多数用例。
然而,它没有考虑拓扑结构,即Pod在节点和区域间的物理或逻辑位置。
轮询调度的挑战
- 延迟增加:如果节点A上的客户端Pod路由到节点B上的服务端点(最坏情况是不同区域),额外的网络跳转会增加毫秒级的延迟
- 跨区成本:在云环境中,跨可用区流量通常由云提供商计费;即使数千个Pod之间只有几MB的跨区流量,也会产生显著成本
- 缓存效率低下:某些ML推理服务在每个Pod的内存中缓存模型。如果请求在Pod之间随机跳转,缓存命中率会降低,影响性能和资源效率
Kubernetes v1.34的新功能
新的trafficDistribution字段,Kubernetes服务现在支持spec下的可选字段:
1
2
|
spec:
trafficDistribution: PreferSameNode | PreferSameZone
|
- 默认行为(如果未设置):流量仍然在所有端点之间均匀分布
- PreferSameNode:kube-proxy(或服务代理)将尝试将流量发送到与客户端Pod运行在同一节点上的Pod。如果没有此类端点可用,它将回退到区域级或集群范围的平衡
- PreferSameZone:代理将优先考虑与客户端Pod在同一拓扑区域内的端点。如果没有可用端点,它将回退到集群范围的分布
流量分发高层示意图
这些偏好是可选的,如果未指定任何偏好,则默认情况下流量将在集群中的所有端点之间均匀分布。
优势
- 更低延迟:当在同一节点或同一区域内提供本地服务时,请求需要更少的网络跳转。这对于具有低SLA要求的微服务或以毫秒为单位测量推理时间的ML工作负载尤其关键
- 降低成本:云提供商通常对跨区流量收费。优先路由到本地Pod可以避免这些费用,除非必要
- 提高缓存利用率:诸如ML推理Pod之类的工作负载通常会在内存中保持模型、嵌入或特征存储的热状态,通过同一节点路由可以提高缓存命中率
- 内置容错能力:两种策略都是偏好,而不是硬性要求。如果由于节点被清空或区域中断而没有本地端点,Kubernetes可以无缝回退到集群范围的分布
使用案例
- 缓存热模型的ML推理服务
- 数据节点与区域对齐的分布式系统
- 跨多个可用区部署的大型组织可以实现智能故障转移,因为正常情况下流量保持本地,但如果区域发生中断,可以无缝故障转移
演示演练
我们将在下面的演示中涵盖流量分发场景——默认、PreferSameZone、PreferSameNode和回退。
演示:设置集群、部署Pod、服务和客户端
步骤1:在minikube上启动多节点集群并为节点标记区域:
1
2
3
4
5
|
minikube start -p mnode --nodes=3 --kubernetes-version=v1.34.0
kubectl config use-context mnode
kubectl label node mnode-m02 topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node mnode-m03 topology.kubernetes.io/zone=zone-b --overwrite
|
步骤2:部署echo应用程序,包含两个副本和echo服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
# echo-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
replicas: 2
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: hashicorp/http-echo
args:
- "-text=Hello from $(POD_NAME)"
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- containerPort: 5678
# echo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: echo-svc
spec:
selector:
app: echo
ports:
- port: 80
targetPort: 5678
|
1
2
3
4
5
6
7
8
|
kubectl apply -f echo-pod.yaml
kubectl apply -f echo-service.yaml
# 验证Pod是否在单独的节点和区域上运行
kubectl get pods -l app=echo -o=custom-columns=NAME:.metadata.name,NODE:.spec.nodeName --no-headers \
| while read pod node; do
zone=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
printf "%-35s %-15s %s\n" "$pod" "$node" "$zone"
done
|
如截图所示,两个echo Pod在不同的节点(mnode-m02,mnode-m03)和可用区(zone-a,zone-b)上启动。
步骤3:在区域A中部署客户端Pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# client.yaml
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
nodeSelector:
topology.kubernetes.io/zone: zone-a
restartPolicy: Never
containers:
- name: client
image: alpine:3.19
command: ["sh", "-c", "sleep infinity"]
stdin: true
tty: true
|
1
2
3
4
5
6
7
|
kubectl apply -f client.yaml
kubectl get pod client -o=custom-columns=NAME:.metadata.name,NODE:.spec.nodeName --no-headers \
| while read pod node; do
zone=$(kubectl get node "$node" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
printf "%-35s %-15s %s\n" "$pod" "$node" "$zone"
done
|
客户端Pod被调度在zone-a中的mnode-m02节点上。
步骤4:在客户端Pod中设置辅助脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
kubectl exec -it client -- sh
apk add --no-cache curl jq
cat > /hit.sh <<'EOS'
#!/bin/sh
COUNT="${1:-20}"
SVC="${2:-echo}"
PORT="${3:-80}"
i=1
while [ "$i" -le "$COUNT" ]; do
curl -s "http://${SVC}:${PORT}/" \
| jq -r '.env.POD_NAME + "@" + .env.NODE_NAME'
i=$((i+1))
done | sort | uniq -c
EOS
chmod +x /hit.sh
exit
|
演示:默认行为
从客户端shell运行脚本:hit.sh以生成从客户端Pod到echo服务的流量。
行为:在下面的截图中,您可以看到流量以轮询方式路由到两个Pod(每个10个请求)。
演示:PreferSameNode
修补echo服务定义spec以添加/修补流量分发:PreferSameNode。
1
|
kubectl patch svc echo --type merge -p '{"spec":{"trafficDistribution":"PreferSameNode"}}'
|
从客户端shell运行脚本:hit.sh以生成从客户端Pod到echo服务的流量。
行为:流量应该被路由到与客户端Pod在同一节点mnode-m02上的Pod:echo-687cbdc966-mgwn5@mnode-m02。
演示:PreferSameZone
更新echo服务定义spec以添加/修补流量分发:PreferSameZone
1
|
kubectl patch svc echo --type merge -p '{"spec":{"trafficDistribution":"PreferSameZone"}}'
|
从客户端shell运行脚本:hit.sh以生成从客户端Pod到echo服务的流量。
行为:流量应该被路由到与客户端Pod在同一区域(zone-a)中的Pod。
演示:回退
强制所有echo Pod到zone-b,然后再次测试:
1
2
3
4
|
kubectl scale deploy echo --replicas=1
kubectl patch deploy echo --type merge -p \
'{"spec":{"template":{"spec":{"nodeSelector":{"topology.kubernetes.io/zone":"zone-b"}}}}}'
kubectl rollout status deploy/echo
|
从客户端shell运行脚本:hit.sh以生成从客户端Pod到echo服务的流量。
结果总结
| 策略 |
行为 |
| 默认 |
流量以轮询方式在所有端点之间分布 |
| PreferSameNode |
优先选择同一节点上的Pod,如果没有可用则回退 |
| PreferSameZone |
优先选择同一区域中的Pod,如果没有可用则回退 |
结论
Kubernetes v1.34版本增加了两个小而强大的功能:PreferSameNode和PreferSameZone,这些偏好帮助开发者和k8s操作员实现更智能的流量路由,确保流量优先选择本地端点,同时通过回退机制保持弹性。
参考文献