虚拟 IP 和服务代理

Kubernetes 集群中的每个 节点会运行一个 kube-proxy (除非你已经部署了自己的替换组件来替代 kube-proxy)。

kube-proxy 组件负责除 typeExternalName 以外的服务,实现虚拟 IP 机制。

kube-proxy 的每个实例都会监视 Kubernetes 控制平面 中 Service 和 EndpointSlice 对象 的添加和删除。对于每个 Service,kube-proxy 调用适当的 API(取决于 kube-proxy 模式)来配置节点,以捕获流向 Service 的 clusterIPport 的流量,并将这些流量重定向到 Service 的某个端点(通常是 Pod,但也可能是用户提供的任意 IP 地址)。一个控制回路确保每个节点上的规则与 API 服务器指示的 Service 和 EndpointSlice 状态可靠同步。

iptables 模式下 Service 的虚拟 IP 机制

一个时不时出现的问题是,为什么 Kubernetes 依赖代理将入站流量转发到后端。 其他方案呢?例如,是否可以配置具有多个 A 值(或 IPv6 的 AAAA)的 DNS 记录, 使用轮询域名解析?

使用代理转发方式实现 Service 的原因有以下几个:

  • DNS 的实现不遵守记录的 TTL 约定的历史由来已久,在记录过期后可能仍有结果缓存。
  • 有些应用只做一次 DNS 查询,然后永久缓存结果。
  • 即使应用程序和库进行了适当的重新解析,TTL 取值较低或为零的 DNS 记录可能会给 DNS 带来很大的压力, 从而变得难以管理。

在下文中,你可以了解到 kube-proxy 各种实现方式的工作原理。 总的来说,你应该注意到,在运行 kube-proxy 时, 可能会修改内核级别的规则(例如,可能会创建 iptables 规则), 在某些情况下,这些规则直到重启才会被清理。 因此,运行 kube-proxy 这件事应该只由了解在计算机上使用低级别、特权网络代理服务会带来的后果的管理员执行。 尽管 kube-proxy 可执行文件支持 cleanup 功能,但这个功能并不是官方特性,因此只能根据具体情况使用。

本文中的一些细节会引用这样一个例子: 运行了 3 个 Pod 副本的无状态图像处理后端工作负载。 这些副本是可互换的;前端不需要关心它们调用了哪个后端副本。 即使组成这一组后端程序的 Pod 实际上可能会发生变化, 前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。

代理模式

kube-proxy 会根据不同配置以不同的模式启动。

在 Linux 节点上,kube-proxy 的可用模式是:

iptables
kube-proxy 使用 iptables 配置数据包转发规则的一种模式。
ipvs
kube-proxy 使用 ipvs 配置数据包转发规则的一种模式。
nftables
kube-proxy 使用 nftables 配置数据包转发规则的一种模式。

Windows 上的 kube-proxy 只有一种模式可用:

kernelspace
kube-proxy 在 Windows 内核中配置数据包转发规则的一种模式。

iptables 代理模式

此代理模式仅适用于 Linux 节点。

在这种模式下,kube-proxy 使用内核 netfilter 子系统的 iptables API 配置数据包转发规则。对于每个端点,kube-proxy 会添加 iptables 规则,这些规则默认情况下会随机选择一个后端 Pod。

示例

例如,考虑本页中前面描述的图像处理应用程序。 当创建后端 Service 时,Kubernetes 控制平面会分配一个虚拟 IP 地址,例如 10.0.0.1。 对于这个例子而言,假设 Service 端口是 1234。 集群中的所有 kube-proxy 实例都会观察到新 Service 的创建。

当节点上的 kube-proxy 观察到新 Service 时,它会添加一系列 iptables 规则, 这些规则从虚拟 IP 地址重定向到更多 iptables 规则,每个 Service 都定义了这些规则。 每个 Service 规则链接到每个后端端点的更多规则, 并且每个端点规则将流量重定向(使用目标 NAT)到后端。

当客户端连接到 Service 的虚拟 IP 地址时,iptables 规则会生效。 会选择一个后端(基于会话亲和性或随机选择),并将数据包重定向到后端,无需重写客户端 IP 地址。

当流量通过节点端口或负载均衡器进入时,也会执行相同的基本流程, 只是在这些情况下,客户端 IP 地址会被更改。

优化 iptables 模式性能

在 iptables 模式下,kube-proxy 为每个 Service 创建一些 iptables 规则,并为每个端点 IP 地址创建一些 iptables 规则。在拥有数万个 Pod 和 Service 的集群中,这意味着数万个 iptables 规则,当 Service(或其 EndpointSlice)发生变化时,kube-proxy 在更新内核中的规则时可能要用很长时间。你可以通过(kube-proxy --config <path> 指定的) kube-proxy 配置文件iptables 章节中的选项来调整 kube-proxy 的同步行为:

...
iptables:
  minSyncPeriod: 1s
  syncPeriod: 30s
...
minSyncPeriod

minSyncPeriod 参数设置尝试同步 iptables 规则与内核之间的最短时长。 如果是 0s,那么每次有任一 Service 或 Endpoint 发生变更时,kube-proxy 都会立即同步这些规则。 这种方式在较小的集群中可以工作得很好,但如果在很短的时间内很多东西发生变更时,它会导致大量冗余工作。 例如,如果你有一个由 Deployment 支持的 Service,共有 100 个 Pod,你删除了这个 Deployment, 且设置了 minSyncPeriod: 0s,kube-proxy 最终会从 iptables 规则中逐个删除 Service 的 Endpoint, 总共更新 100 次。使用较大的 minSyncPeriod 值时,多个 Pod 删除事件将被聚合在一起, 因此 kube-proxy 最终可能会进行例如 5 次更新,每次移除 20 个端点, 这样在 CPU 利用率方面更有效率,能够更快地同步所有变更。

minSyncPeriod 的值越大,可以聚合的工作越多, 但缺点是每个独立的变更可能最终要等待整个 minSyncPeriod 周期后才能被处理, 这意味着 iptables 规则要用更多时间才能与当前的 API 服务器状态同步。

默认值 1s 适用于大多数集群,在大型集群中,可能需要将其设置为更大的值。 (特别是,如果 kube-proxy 的 sync_proxy_rules_duration_seconds 指标表明平均时间远大于 1 秒, 那么提高 minSyncPeriod 可能会使更新更有效率。)

更新原有的 minSyncPeriod 配置

旧版本的 kube-proxy 在每次同步时为所有 Service 更新规则; 这在大型集群中会造成性能问题(更新延迟),建议的解决方案是设置较大的 minSyncPeriod。 自 Kubernetes v1.28 开始,kube-proxy 的 iptables 模式采用了更精简的方法, 只有在 Service 或 EndpointSlice 实际发生变化时才会进行更新。

如果你之前覆盖了 minSyncPeriod,你应该尝试删除该覆盖并让 kube-proxy 使用默认值(1s)或至少比升级前使用的值小。

如果你运行的不是 Kubernetes 1.31 版本的 kube-proxy, 请检查你实际运行的版本的行为和相关建议。

syncPeriod

syncPeriod 参数控制与单次 Service 和 EndpointSlice 的变更没有直接关系的少数同步操作。 特别是,它控制 kube-proxy 在外部组件已干涉 kube-proxy 的 iptables 规则时通知的速度。 在大型集群中,kube-proxy 也仅在每隔 syncPeriod 时长执行某些清理操作,以避免不必要的工作。

在大多数情况下,提高 syncPeriod 预计不会对性能产生太大影响, 但在过去,有时将其设置为非常大的值(例如 1h)很有用。 现在不再推荐这种做法,因为它对功能的破坏可能会超过对性能的改进。

IPVS 代理模式

此代理模式仅适用于 Linux 节点。

ipvs 模式下,kube-proxy 使用内核 IPVS 和 iptables API 创建规则,将流量从 Service IP 重定向到端点 IP。

IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但它使用哈希表作为底层数据结构,在内核空间中生效。 这意味着 IPVS 模式下的 kube-proxy 比 iptables 模式下的 kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 与 iptables 代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。

IPVS 为将流量均衡到后端 Pod 提供了更多选择:

  • rr(轮询):流量被平均分发给后端服务器。

  • wrr(加权轮询):流量基于服务器的权重被路由到后端服务器。 高权重的服务器接收新的连接并处理比低权重服务器更多的请求。

  • lc(最少连接):将更多流量分配给活跃连接数较少的服务器。

  • wlc(加权最少连接):将更多流量按照服务器权重分配给连接数较少的服务器,即基于连接数除以权重。

  • lblc(基于地域的最少连接):如果服务器未超载且可用,则针对相同 IP 地址的流量被发送到同一后端服务器; 否则,流量被发送到连接较少的服务器,并在未来的流量分配中保持这一分配决定。

  • lblcr(带副本的基于地域的最少连接):针对相同 IP 地址的流量被发送到连接数最少的服务器。 如果所有后端服务器都超载,则选择连接较少的服务器并将其添加到目标集中。 如果目标集在指定时间内未发生变化,则从此集合中移除负载最高的服务器,以避免副本的负载过高。
  • sh(源哈希):通过查找基于源 IP 地址的静态分配哈希表,将流量发送到某后端服务器。

  • dh(目标哈希):通过查找基于目标地址的静态分配哈希表,将流量发送到某后端服务器。

  • sed(最短预期延迟):流量被转发到具有最短预期延迟的后端服务器。 如果流量被发送给服务器,预期延迟为 (C + 1) / U,其中 C 是服务器上的连接数, U 是服务器的固定服务速率(权重)。

  • nq(永不排队):流量被发送到一台空闲服务器(如果有的话),而不是等待一台快速服务器; 如果所有服务器都忙碌,算法将退回到 sed 行为。

IPVS 模式下 Service 的虚拟 IP 地址机制

nftables 代理模式

特性状态: Kubernetes v1.31 [beta]

此代理模式仅适用于 Linux 节点,并且需要 5.13 或更高的内核版本。

在这种模式下,kube-proxy 使用内核 netfilter 子系统的 nftables API 配置数据包转发规则。对于每个端点,它会添加 nftables 规则,这些规则默认情况下会随机选择一个后端 Pod。

nftables API 是 iptables API 的后继,旨在提供比 iptables 更好的性能和可扩展性。 nftables 代理模式能够比 iptables 模式更快、更高效地处理服务端点的变化, 并且在内核中处理数据包的效率也更高(尽管这只有在拥有数万个服务的集群中才会比较明显)。

在 Kubernetes 1.31 中,nftables 模式仍然相对较新,可能还不兼容所有的网络插件;请查阅你的网络插件文档。

iptables 模式到 nftables 模式的迁移

想要从默认的 iptables 模式切换到 nftables 模式的用户应注意,在 nftables 模式下,一些特性的工作方式略有不同:

  • NodePort 接口:在 iptables 模式下,默认情况下, NodePort 服务 可以在所有本地 IP 地址上访问。这通常不是用户想要的,因此 nftables 模式默认使用 --nodeport-addresses primary,这意味着 NodePort 服务只能在节点的主 IPv4 和/或 IPv6 地址上访问。你可以通过为该选项指定一个明确的值来覆盖此设置:例如,使用 --nodeport-addresses 0.0.0.0/0 以监听所有(本地)IPv4 IP。
  • 127.0.0.1 上的 NodePort 服务:在 iptables 模式下,如果 --nodeport-addresses 范围包括 127.0.0.1(且未传递 --iptables-localhost-nodeports false 选项), 则 NodePort 服务甚至可以在 "localhost" (127.0.0.1) 上访问。 在 nftables 模式(和 ipvs 模式)下,这将不起作用。如果你不确定是否依赖此功能, 可以检查 kube-proxy 的 iptables_localhost_nodeports_accepted_packets_total 指标; 如果该值非 0,则表示某些客户端已通过 127.0.0.1 连接到 NodePort 服务。
  • NodePort 与防火墙的交互:kube-proxy 的 iptables 模式尝试与过于激进的防火墙兼容; 对于每个 NodePort 服务,它会添加规则以接受该端口的入站流量,以防该流量被防火墙阻止。 这种方法不适用于基于 nftables 的防火墙,因此 kube-proxy 的 nftables 模式在这里不会做任何事情; 如果你有本地防火墙,必须确保其配置正确以允许 Kubernetes 流量通过(例如,允许整个 NodePort 范围的入站流量)。
  • Conntrack 错误规避:6.1 之前的 Linux 内核存在一个错误,可能导致与服务 IP 的长时间 TCP 连接被关闭,并出现“Connection reset by peer(对方重置连接)”的错误。kube-proxy 的 iptables 模式为此错误配备了一个修复程序,但后来发现该修复程序在某些集群中会导致其他问题。 nftables 模式默认不安装任何修复程序,但你可以检查 kube-proxy 的 iptables_ct_state_invalid_dropped_packets_total 指标,看看你的集群是否依赖于该修复程序,如果是,你可以使用 --conntrack-tcp-be-liberal 选项运行 kube-proxy,以在 nftables 模式下解决该问题。

kernelspace 代理模式

此代理模式仅适用于 Windows 节点。

kube-proxy 在 Windows 虚拟过滤平台 (VFP)(Windows vSwitch 的扩展)中配置数据包过滤规则。 这些规则处理节点级虚拟网络中的封装数据包,并重写数据包,使目标 IP 地址(和第 2 层信息)正确, 以便将数据包路由到正确的目的地。Windows VFP 类似于 Linux nftablesiptables 等工具。 Windows VFP 是最初为支持虚拟机网络而实现的 Hyper-V Switch 的扩展。

当节点上的 Pod 将流量发送到某虚拟 IP 地址,且 kube-proxy 选择不同节点上的 Pod 作为负载均衡目标时,kernelspace 代理模式会重写该数据包以将其发送到对应目标后端 Pod。 Windows 主机网络服务(HSN)会配置数据包重写规则,确保返回流量看起来来自虚拟 IP 地址, 而不是特定的后端 Pod。

kernelspace 模式的 Direct Server Return(DSR)

特性状态: Kubernetes v1.14 [alpha]

作为基本操作的替代方案,托管服务后端 Pod 的节点可以直接应用数据包重写, 而不用将此工作交给运行客户端 Pod 的节点来执行。这称为 Direct Server Return(DSR)

要使用这种技术,你必须使用 --enable-dsr 命令行参数运行 kube-proxy 启用 WinDSR 特性门控

即使两个 Pod 在同一节点上运行,DSR 也可优化 Pod 的返回流量。

会话亲和性

在这些代理模型中,绑定到 Service IP:Port 的流量被代理到合适的后端, 客户端不需要知道任何关于 Kubernetes、Service 或 Pod 的信息。

如果要确保来自特定客户端的连接每次都传递给同一个 Pod, 你可以通过设置 Service 的 .spec.sessionAffinityClientIP 来设置基于客户端 IP 地址的会话亲和性(默认为 None)。

会话粘性超时

你还可以通过设置 Service 的 .spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话粘性时间(默认值为 10800,即 3 小时)。

将 IP 地址分配给 Service

与实际路由到固定目标的 Pod IP 地址不同,Service IP 实际上不是由单个主机回答的。 相反,kube-proxy 使用数据包处理逻辑(例如 Linux 的 iptables) 来定义虚拟 IP 地址,这些地址会按需被透明重定向。

当客户端连接到 VIP 时,其流量会自动传输到适当的端点。 实际上,Service 的环境变量和 DNS 是根据 Service 的虚拟 IP 地址(和端口)填充的。

避免冲突

Kubernetes 的主要哲学之一是, 你不应需要在完全不是你的问题的情况下面对可能导致你的操作失败的情形。 对于 Service 资源的设计,也就是如果你选择的端口号可能与其他人的选择冲突, 就不应该让你自己选择 IP 地址。这是一种失败隔离。

为了允许你为 Service 选择 IP 地址,我们必须确保没有任何两个 Service 会发生冲突。 Kubernetes 通过从为 API 服务器配置的 service-cluster-ip-range CIDR 范围内为每个 Service 分配自己的 IP 地址来实现这一点。

IP 地址分配追踪

为了确保每个 Service 都获得唯一的 IP 地址,内部分配器在创建每个 Service 之前更新 etcd 中的全局分配映射,这种更新操作具有原子性。 映射对象必须存在于数据库中,这样 Service 才能获得 IP 地址分配, 否则创建将失败,并显示无法分配 IP 地址。

在控制平面中,后台控制器负责创建该映射(从使用内存锁定的旧版本的 Kubernetes 迁移时需要这一映射)。 Kubernetes 还使用控制器来检查无效的分配(例如,因管理员干预而导致无效分配) 以及清理已分配但没有 Service 使用的 IP 地址。

使用 Kubernetes API 跟踪IP 地址分配

特性状态: Kubernetes v1.31 [beta]

如果你启用 MultiCIDRServiceAllocator 特性门控networking.k8s.io/v1alpha1 API 组, 控制平面用一个改进后的实现替换现有的 etcd 分配器,使用 IPAddress 和 ServiceCIDR 对象而不是内部的全局分配映射。与某 Service 关联的每个 ClusterIP 地址将有一个对应的 IPAddress 对象。

启用该特性门控还会用替代实现将后台控制器替换,来处理 IPAddress 对象并支持从旧的分配器模型迁移。 Kubernetes 1.31 不支持从 IPAddress 对象迁移到内部分配映射。

改进后的分配器的主要优点之一是它取消了对可用于 Service 的集群 IP 地址的范围大小限制。 启用 MultiCIDRServiceAllocator 后,对 IPv4 没有大小限制,而对于 IPv6,你可以使用等于或小于 /64 的 IP 地址子网掩码(与旧实现中的 /108 相比)。

通过 API 提供 IP 地址分配,意味着作为集群管理员,你可以允许用户检查分配给他们的 Service 的 IP 地址。 Kubernetes 扩展(例如 Gateway API) 可以使用 IPAddress API 来扩展 Kubernetes 的固有网络功能。

以下是用户查询 IP 地址的简短示例:

kubectl get services
NAME         TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   2001:db8:1:2::1   <none>        443/TCP   3d1h
kubectl get ipaddresses
NAME              PARENTREF
2001:db8:1:2::1   services/default/kubernetes
2001:db8:1:2::a   services/kube-system/kube-dns

Kubernetes 还允许用户使用 ServiceCIDR 对象动态定义 Service 的可用 IP 范围。在引导过程中,集群会根据 kube-apiserver 的 --service-cluster-ip-range 命令行参数的值创建一个名为 kubernetes 的默认 ServiceCIDR 对象:

kubectl get servicecidrs
NAME         CIDRS         AGE
kubernetes   10.96.0.0/28  17m

用户可以创建或删除新的 ServiceCIDR 对象来管理 Service 的可用 IP 范围:

cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1beta1
kind: ServiceCIDR
metadata:
  name: newservicecidr
spec:
  cidrs:
  - 10.96.0.0/24
EOF
servicecidr.networking.k8s.io/newcidr1 created
kubectl get servicecidrs
NAME             CIDRS         AGE
kubernetes       10.96.0.0/28  17m
newservicecidr   10.96.0.0/24  7m

Service 虚拟 IP 地址的地址段

特性状态: Kubernetes v1.26 [stable]

Kubernetes 根据配置的 service-cluster-ip-range 的大小使用公式 min(max(16, cidrSize / 16), 256)ClusterIP 范围分为两段。 该公式可以解释为:介于 16 和 256 之间,并在上下界之间存在渐进阶梯函数的分配。

Kubernetes 优先通过从高段中选择来为 Service 分配动态 IP 地址, 这意味着如果要将特定 IP 地址分配给 type: ClusterIP Service, 则应手动从段中分配 IP 地址。该方法降低了分配导致冲突的风险。

流量策略

你可以设置 .spec.internalTrafficPolicy.spec.externalTrafficPolicy 字段来控制 Kubernetes 如何将流量路由到健康(“就绪”)的后端。

内部流量策略

特性状态: Kubernetes v1.26 [stable]

你可以设置 .spec.internalTrafficPolicy 字段来控制来自内部源的流量如何被路由。 有效值为 ClusterLocal。 将字段设置为 Cluster 会将内部流量路由到所有准备就绪的端点, 将字段设置为 Local 仅会将流量路由到本地节点准备就绪的端点。 如果流量策略为 Local 但没有本地节点端点,那么 kube-proxy 会丢弃该流量。

外部流量策略

你可以设置 .spec.externalTrafficPolicy 字段来控制从外部源路由的流量。 有效值为 ClusterLocal。 将字段设置为 Cluster 会将外部流量路由到所有准备就绪的端点, 将字段设置为 Local 仅会将流量路由到本地节点上准备就绪的端点。 如果流量策略为 Local 并且没有本地节点端点, 那么 kube-proxy 不会转发与相关 Service 相关的任何流量。

如果指定了 Cluster,则所有节点都可以作为负载均衡目标,只要节点没有被删除且 kube-proxy 是健康的。在这种模式下:负载均衡器健康检查被配置为针对服务代理的就绪端口和路径。对于 kube-proxy,这个健康检查端点为:${NODE_IP}:10256/healthz。kube-proxy 将返回 HTTP 状态码 200 或 503。如果满足以下条件,kube-proxy 的负载均衡器健康检查端点将返回 200:

  1. kube-proxy 是健康的,意味着:
    • 它能够继续进行网络编程,并且在此过程中不会超时(超时时间定义为:2 × iptables.syncPeriod);并且
  2. 节点没有被删除(Node 对象上没有设置删除时间戳)。

kube-proxy 在节点被删除时返回 503 并将节点标记为不符合条件的原因在于 kube-proxy 对处于终止过程中的节点支持连接腾空。从 Kubernetes 管理的负载均衡器的角度来看, 当节点正在/ 被删除时,会发生一些重要的事情。

当节点被删除时:

  • kube-proxy 的就绪探针将开始失败,并将该节点标记为不胜任接收负载均衡器流量。 负载均衡器健康检查失败会导致支持连接排空的负载均衡器允许现有连接终止,并阻止新连接建立。

当节点被删除后:

  • Kubernetes 云控制器管理器中的服务控制器会将节点从所引用的候选目标集中移除。 从负载均衡器的后端目标集中移除任何实例会立即终止所有连接。 这也是 kube-proxy 在节点删除过程中首先使健康检查失败的原因。

需要注意的是,对于 Kubernetes 供应商,如果任何供应商将 kube-proxy 的就绪探针配置为存活探针:当节点正在删除直到完全删除时,kube-proxy 将开始不断重启。kube-proxy 公开了一个 /livez 路径,与 /healthz 路径不同, /livez 路径考虑节点的删除状态,仅考虑其网络编程进度。因此,对于任何希望为 kube-proxy 定义存活探针的人来说,推荐使用 /livez 路径。

部署 kube-proxy 的用户可以通过评估指标 proxy_livez_total / proxy_healthz_total 来检查就绪/存活状态。这两个指标都发布了两个序列,一个带有 200 标签,另一个带有 503 标签。

对于 Local Service:如果满足以下条件,kube-proxy 将返回 200:

  1. kube-proxy 是健康/就绪的,并且
  2. 在相关节点上有一个本地端点。

对于负载均衡器健康检查而言,节点删除不会对 kube-proxy 的返回代码产生影响。原因是:如果所有端点同时在上述节点上运行,则删除节点最终可能会导致入站流量中断。

Kubernetes 项目建议云提供商集成代码配置负载均衡器健康检查,以针对服务代理的 healthz 端口。 如果你正在使用或实现自己的虚拟 IP 实现,供人们使用它替代 kube-proxy,你应该设置一个类似的健康检查端口, 其逻辑应与 kube-proxy 实现相匹配。

流向正终止的端点的流量

特性状态: Kubernetes v1.28 [stable]

如果为 kube-proxy 启用了 ProxyTerminatingEndpoints 特性门控且流量策略为 Local, 则节点的 kube-proxy 将使用更复杂的算法为 Service 选择端点。 启用此特性时,kube-proxy 会检查节点是否具有本地端点以及是否所有本地端点都标记为正在终止过程中。 如果有本地端点并且所有本地端点都被标记为处于终止过程中, 则 kube-proxy 会将转发流量到这些正在终止过程中的端点。 否则,kube-proxy 会始终选择将流量转发到并未处于终止过程中的端点。

这种对处于终止过程中的端点的转发行为使得 NodePortLoadBalancer Service 能有条不紊地腾空设置了 externalTrafficPolicy: Local 时的连接。

当一个 Deployment 被滚动更新时,处于负载均衡器后端的节点可能会将该 Deployment 的 N 个副本缩减到 0 个副本。在某些情况下,外部负载均衡器可能在两次执行健康检查探针之间将流量发送到具有 0 个副本的节点。 将流量路由到处于终止过程中的端点可确保正在缩减 Pod 的节点能够正常接收流量, 并逐渐降低指向那些处于终止过程中的 Pod 的流量。 到 Pod 完成终止时,外部负载均衡器应该已经发现节点的健康检查失败并从后端池中完全移除该节点。

流量分发

特性状态: Kubernetes v1.31 [beta]

Kubernetes Service 中的 spec.trafficDistribution 字段允许你定义流量应如何路由到 Service 端点的偏好。像 kube-proxy 这样的实现会将 spec.trafficDistribution 字段作为指导。不同实现之间,与给定偏好相关的行为可能会略有不同。

PreferClose 与 kube-proxy 结合
对于 kube-proxy,这意味着优先将流量发送到与客户端位于同一区域的端点。 EndpointSlice 控制器使用 hints 来更新 EndpointSlices 以传达此偏好, 之后,kube-proxy 会使用这些提示进行路由决策。如果客户端的区域没有可用的端点, 则流量将在整个集群范围内路由。

如果 trafficDistribution 没有任何值,kube-proxy 的默认路由策略是将流量分配到集群中的任一端点。

service.kubernetes.io/topology-mode: Auto 的比较

trafficDistribution 字段中的 PreferCloseservice.kubernetes.io/topology-mode: Auto 注解都旨在优先处理同一区域的流量。 然而,它们的方法存在一些关键差异:

  • service.kubernetes.io/topology-mode: Auto:尝试根据可分配的 CPU 资源在各区域之间按比例分配流量。此启发式方法包括一些保障措施 (例如针对少量端点的回退行为), 并在某些场景下可能因负载均衡原因导致该特性被禁用。这种方法在一定程度上牺牲了可预测性, 以换取潜在的负载均衡。
  • trafficDistribution: PreferClose:这种方法偏重更简单和更可预测: “如果区域内有端点,它们将接收该区域的所有流量;如果区域内没有端点,流量将分配到其他区域”。 虽然这种方法可能提供更多的可预测性,但这意味着你需要管理潜在的过载

如果 service.kubernetes.io/topology-mode 注解设置为 Auto,它将优先于 trafficDistribution。(该注解将来可能会被弃用,取而代之的是 trafficDistribution 字段)。

与流量策略的交互

trafficDistribution 字段相比,流量策略字段 (externalTrafficPolicyinternalTrafficPolicy)旨在提供更严格的流量局域化要求。 以下是 trafficDistribution 与它们的交互方式:

  • 流量策略的优先序:对于给定的 Service,如果流量策略 (externalTrafficPolicyinternalTrafficPolicy)设置为 Local, 则它优先于相应流量类型(分别为外部或内部)的 trafficDistribution: PreferClose
  • trafficDistribution 的影响:对于给定的 Service,如果流量策略 (externalTrafficPolicyinternalTrafficPolicy)设置为 Cluster(默认值), 或者这些字段未设置,那么 trafficDistribution: PreferClose 将指导相应流量类型 (分别为外部或内部)的路由行为。这意味着 kube-proxy 将尝试将流量路由到与客户端位于同一区域的端点。

使用流量分配控制的注意事项

  • 端点过载的概率增加: PreferClose 启发式方法将尝试将流量路由到最近的健康端点, 而不是将流量均匀分布到所有端点。如果某个区域内的端点数量不足,它们可能会过载。 如果传入流量在各区域之间分布不均,这种情况更有可能发生。为减轻这种情况,请考虑以下策略:

    • Pod 拓扑分布约束: 使用 Pod 拓扑分布约束在各区域之间更均匀地分布你的 Pod。

    • 区域特定的 Deployment:如果你预计会看到不均衡的流量模式, 可以为每个区域创建一个单独的 Deployment。这种方法允许独立扩展各个工作负载。 生态系统中还有一些 Kubernetes 项目之外的工作负载管理插件,可以在这方面提供帮助。

  • 特定于具体实现的行为: 各个数据平面实现处理此字段的方式可能会稍有不同。 如果你使用的是 kube-proxy 以外的实现,请参阅该实现的特定文档以了解该实现是如何处理此字段的。

接下来

要了解有关 Service 的更多信息, 请阅读使用 Service 连接应用

也可以: