Kubernetes v1.34智能流量路由:PreferSameNode与PreferSameZone详解

本文深入解析Kubernetes v1.34新增的流量分发偏好功能,包括PreferSameNode和PreferSameZone策略,通过实际演示展示如何实现智能流量路由,降低延迟和跨区成本,提升应用性能。

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服务的流量。

1
/hit.sh 20

行为:在下面的截图中,您可以看到流量以轮询方式路由到两个Pod(每个10个请求)。

演示:PreferSameNode

修补echo服务定义spec以添加/修补流量分发:PreferSameNode。

1
kubectl patch svc echo --type merge -p '{"spec":{"trafficDistribution":"PreferSameNode"}}'

从客户端shell运行脚本:hit.sh以生成从客户端Pod到echo服务的流量。

1
/hit.sh 40

行为:流量应该被路由到与客户端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服务的流量。

1
/hit.sh 40

行为:流量应该被路由到与客户端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操作员实现更智能的流量路由,确保流量优先选择本地端点,同时通过回退机制保持弹性。

参考文献

  • PreferClose流量分发已被弃用
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计