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

本文深入解析Kubernetes v1.34版本引入的智能流量分发功能,详细介绍PreferSameNode和PreferSameZone两种路由策略的工作原理、配置方法和实际应用场景,帮助优化服务性能并降低跨区域流量成本。

Kubernetes 已稳步发展成为容器编排的行业标准,从小型开发集群到超大规模 AI 和数据基础设施都在使用。每个新版本不仅让工作负载更易于管理,还提升了性能、成本效益和弹性。

在 v1.34 版本中,一个突出的增强是为 Kubernetes 服务引入了流量分发偏好。具体包括:

  • PreferSameNode:尽可能将流量路由到与客户端 Pod 相同的节点
  • PreferSameZone:在跨区域路由之前,优先选择相同拓扑区域中的端点

这些策略为服务流量分发增添了更智能的、感知局部性的路由能力。Kubernetes 现在可以优先选择距离客户端更近的 Pod,无论它们是在同一节点还是同一可用区(AZ)。

这一变化看似简单,但对性能敏感和成本敏感的工作负载具有重要意义,尤其是在大型多节点和多区域集群中。

流量分发的重要性

传统上,Kubernetes 服务会均匀地平衡与其选择器匹配的所有端点/Pod 的流量。这种均匀的流量分发简单、可预测,适用于大多数用例。

然而,它没有考虑拓扑结构,即 Pod 跨节点和区域的物理或逻辑位置。

轮询调度的挑战

  • 延迟增加:如果节点 A 上的客户端 Pod 路由到节点 B(或最坏情况下到不同区域)上的服务端点,额外的网络跳转会增加毫秒级延迟
  • 跨区域成本:在云环境中,跨可用区流量通常由云提供商计费;即使数千个 Pod 之间仅有少量跨区域流量,也会累积成显著成本
  • 缓存效率低下:某些 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 被调度到区域 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 版本增加了两个小而 impactful 的功能:PreferSameNode 和 PreferSameZone,这些偏好帮助开发者和 k8s 操作员使流量路由更智能,确保流量优先选择本地端点,同时通过回退机制保持弹性。

参考资料

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