
Kubernetes v1.21 [stable]
端点切片(EndpointSlices) 是一个新 API,它提供了 Endpoint API 可伸缩和可拓展的替代方案。EndpointSlice 会跟踪 Service Pod 的 IP 地址、端口、readiness 和拓扑信息。
在 Kubernetes v1.19 中,此功能将默认启用:从 EndpointSlice (不是 Endpoint)中读取 kube-proxy。尽管这个更改看起来并不起眼,但实际上它能让大型集群的可伸缩性得到显着提高。另外,它还与 Kubernetes 未来版本中的重要新功能有关,例如 Topology Aware Routing。
如果使用 Endpoint API,Service 只有一个 Endpoint 资源。这意味着它需要为 Service 的每个 Pod 都存储好 IP 地址和端口(网络端点),这需要大量的 API 资源。另外,kube-proxy 会在每个节点上运行,并监控 Endpoint 资源的任何更新。如果 Endpoint 资源中有一个端口发生更改,那么整个对象都会分发到 kube-proxy 的每个实例。
Endpoint API 另一个局限是,它会限制跟踪 Service 的网络端点数量。一般存储在 etcd 中的对象默认大小限制为 1.5MB。在某些情况下,它会将 Endpoint 资源限制为 5000 个 Pod IP。对于大多数用户而言,这没什么关系,但是对于接近这个大小的 Service 而言,就有大问题了。
为了说明这些问题的严重程度,这里举一个简单的例子。如果一个 Service 有 5000 个 Pod,它如果有 1.5MB 的 Endpoint 资源。当该列表中的某个网络端点发生了变化,那么就要将完整的 Endpoint 资源分发给集群中的每个节点。在具有 3000 个节点的大型集群中,这会是个很大的问题。每次更新将跨集群发送 4.5GB 的数据(1.5MB*3000,即 Endpoint 大小 * 节点个数),并且每次端点更新都要发送这么多数据。想象一下,如果进行一次滚动更新,共有 5000 个 Pod 全部被替换,那么传输的数据量将超过 22 TB。
EndpointSlice API 旨在通过类似于分片的方法来解决该问题。我们不跟踪 Service Pod IP 的单个 Endpoint 资源,而是将它们拆分为多个较小的 EndpointSlice。
举个例子,现在有 15 个 Pod 在支持一个 Service,那么就有跟踪这些的一个 Endpoint 资源。如果将 EndpointSlice 配置为每个 EndpointSlice 存储 5 个端点,就得到了 3 个不同的 EndpointSlice:

默认情况下,EndpointSlice 每个存储能多达 100 个端点,我们可以使用 kube-controller-manager 的 --max-endpoints-per-slice 标签进行配置。
EndpointSlice API 大大提高了网络的可伸缩性,因为现在添加或删除 Pod 时,只需更新 1 个小的 EndpointSlice。尤其是成百上千个 Pod 支持单个 Service 时,差异将非常明显。
更重要的是,既然 Service 的所有 Pod IP 都不需要存储在单个资源中,那么我们就不必担心 etcd 中存储对象的大小限制。EndpointSlice 可以用于扩展到超过 10 万个网络端点的 Service。
当这些与 kube-proxy 的一些重大性能改进结合在一起后,再大规模地使用 EndpointSlice 时,用于端点更新的数据将大大减少,kube-proxy 还可以更快更新 iptables 和 ipvs 规则。这样,Service 现在的可伸缩性能达到以前的至少 10 倍。
作为 Kubernetes v1.16 中的 alpha 功能引入的 EndpointSlice 在 Kubernetes 未来版本中,会和一些令人兴奋的新功能有关,例如 dual-stack Service、topology aware routing 和 endpoint subsetting。
原来的 Endpoints API 提供了在 Kubernetes 中跟踪网络端点的一种简单而直接的方法。随着 Kubernetes 集群和服务逐渐开始为更多的后端 Pod 处理和发送请求, 原来的 API 的局限性变得越来越明显。最明显的是那些因为要处理大量网络端点而带来的挑战。
由于任一 Service 的所有网络端点都保存在同一个 Endpoints 对象中,这些 Endpoints 对象可能变得非常巨大。对于保持稳定的服务(长时间使用同一组端点),影响不太明显; 即便如此,Kubernetes 的一些使用场景也没有得到很好的服务。
当某 Service 存在很多后端端点并且该工作负载频繁扩缩或上线新更改时,对该 Service 的单个 Endpoints 对象的每次更新都意味着(在控制平面内以及在节点和 API 服务器之间)Kubernetes 集群组件之间会出现大量流量。 这种额外的流量在 CPU 使用方面也有开销。
使用 EndpointSlices 时,添加或移除单个 Pod 对于正监视变更的客户端会触发相同数量的更新, 但这些更新消息的大小在大规模场景下要小得多。
EndpointSlices 还支持围绕双栈网络和拓扑感知路由等新功能的创新。
在 Kubernetes 中,EndpointSlice 包含对一组网络端点的引用。 控制面会自动为设置了选择算符的 Kubernetes Service 创建 EndpointSlice。 这些 EndpointSlice 将包含对与 Service 选择算符匹配的所有 Pod 的引用。 EndpointSlice 通过唯一的协议、端口号和 Service 名称将网络端点组织在一起。 EndpointSlice 的名称必须是合法的 DNS 子域名。
例如,下面是 Kubernetes Service example 所拥有的 EndpointSlice 对象示例。
- apiVersion: discovery.k8s.io/v1
- kind: EndpointSlice
- metadata:
- name: example-abc
- labels:
- kubernetes.io/service-name: example
- addressType: IPv4
- ports:
- - name: http
- protocol: TCP
- port: 80
- endpoints:
- - addresses:
- - "10.1.2.3"
- conditions:
- ready: true
- hostname: pod-1
- nodeName: node-1
- zone: us-west2-a
默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点。 你可以使用 kube-controller-manager 的 --max-endpoints-per-slice 标志设置此值,最大值为 1000。
当涉及如何路由内部流量时,EndpointSlice 可以充当 kube-proxy 的决策依据。
EndpointSlice 支持三种地址类型:
每个 EndpointSlice 对象代表一个特定的 IP 地址类型。如果你有一个支持 IPv4 和 IPv6 的 Service, 那么将至少有两个 EndpointSlice 对象(一个用于 IPv4,一个用于 IPv6)。
EndpointSlice API 存储了可能对使用者有用的、有关端点的状态。 这三个状态分别是 ready、serving 和 terminating。
ready 状态是映射 Pod 的 Ready 状态的。 对于处于运行中的 Pod,它的 Ready 状态被设置为 True,应该将此 EndpointSlice 状态也设置为 true。 出于兼容性原因,当 Pod 处于终止过程中,ready 永远不会为 true。 消费者应参考 serving 状态来检查处于终止中的 Pod 的就绪情况。 该规则的唯一例外是将spec.publishNotReadyAddresses设置为 true 的 Service。 这些 Service 的端点将始终将 ready 状况设置为 true。
Kubernetes v1.22 [beta]
serving 状态与 ready 状态相同,不同之处在于它不考虑终止状态。 如果 EndpointSlice API 的使用者关心 Pod 终止时的就绪情况,就应检查此状态。
说明:
尽管 serving 与 ready 几乎相同,但是它是为防止破坏 ready 的现有含义而增加的。 如果对于处于终止中的端点,ready 可能是 true,那么对于现有的客户端来说可能是有些意外的, 因为从始至终,Endpoints 或 EndpointSlice API 从未包含处于终止中的端点。 出于这个原因,ready 对于处于终止中的端点 总是 false, 并且在 v1.20 中添加了新的状态 serving,以便客户端可以独立于 ready 的现有语义来跟踪处于终止中的 Pod 的就绪情况。
Kubernetes v1.22 [beta]
Terminating 是表示端点是否处于终止中的状态。 对于 Pod 来说,这是设置了删除时间戳的 Pod。
EndpointSlice 中的每个端点都可以包含一定的拓扑信息。 拓扑信息包括端点的位置,对应节点、可用区的信息。 这些信息体现为 EndpointSlices 的如下端点字段:
在 v1 API 中,逐个端点设置的 topology 实际上被去除, 以鼓励使用专用的字段nodeName和zone。
对 EndpointSlice 对象的 endpoint 字段设置任意的拓扑结构信息这一操作已被废弃, 不再被 v1 API 所支持。取而代之的是 v1 API 所支持的 nodeName 和 zone 这些独立的字段。这些字段可以在不同的 API 版本之间自动完成转译。 例如,v1beta1 API 中 topology 字段的topology.kubernetes.io/zone 取值可以在 v1 API 中通过 zone 字段访问。
通常,控制面(尤其是端点切片的控制器) 会创建和管理 EndpointSlice 对象。EndpointSlice 对象还有一些其他使用场景, 例如作为服务网格(Service Mesh)的实现。 这些场景都会导致有其他实体或者控制器负责管理额外的 EndpointSlice 集合。
为了确保多个实体可以管理 EndpointSlice 而且不会相互产生干扰, Kubernetes 定义了标签 endpointslice.kubernetes.io/managed-by,用来标明哪个实体在管理某个 EndpointSlice。 端点切片控制器会在自己所管理的所有 EndpointSlice 上将该标签值设置为 endpointslice-controller.k8s.io。 管理 EndpointSlice 的其他实体也应该为此标签设置一个唯一值。
在大多数场合下,EndpointSlice 都由某个 Service 所有, (因为)该端点切片正是为该服务跟踪记录其端点。这一属主关系是通过为每个 EndpointSlice 设置一个属主(owner)引用,同时设置 kubernetes.io/service-name 标签来标明的, 目的是方便查找隶属于某 Service 的所有 EndpointSlice。
在某些场合,应用会创建定制的 Endpoints 资源。为了保证这些应用不需要并发的更改 Endpoints 和 EndpointSlice 资源,集群的控制面将大多数 Endpoints 映射到对应的 EndpointSlice 之上。
控制面对 Endpoints 资源进行映射的例外情况有:
每个 Endpoints 资源可能会被转译到多个 EndpointSlices 中去。 当 Endpoints 资源中包含多个子网或者包含多个 IP 协议族(IPv4 和 IPv6)的端点时, 就有可能发生这种状况。 每个子网最多有 1000 个地址会被镜像到 EndpointSlice 中。
每个 EndpointSlice 都有一组端口值,适用于资源内的所有端点。 当为 Service 使用命名端口时,Pod 可能会就同一命名端口获得不同的端口号, 因而需要不同的 EndpointSlice。这有点像 Endpoints 用来对子网进行分组的逻辑。
控制面尝试尽量将 EndpointSlice 填满,不过不会主动地在若干 EndpointSlice 之间执行再平衡操作。这里的逻辑也是相对直接的:
这里比较重要的是,与在 EndpointSlice 之间完成最佳的分布相比,第三步中更看重限制 EndpointSlice 更新的操作次数。例如,如果有 10 个端点待添加,有两个 EndpointSlice 中各有 5 个空位,上述方法会创建一个新的 EndpointSlice 而不是将现有的两个 EndpointSlice 都填满。换言之,与执行多个 EndpointSlice 更新操作相比较, 方法会优先考虑执行一个 EndpointSlice 创建操作。
由于 kube-proxy 在每个节点上运行并监视 EndpointSlice 状态,EndpointSlice 的每次变更都变得相对代价较高,因为这些状态变化要传递到集群中每个节点上。 这一方法尝试限制要发送到所有节点上的变更消息个数,即使这样做可能会导致有多个 EndpointSlice 没有被填满。
在实践中,上面这种并非最理想的分布是很少出现的。大多数被 EndpointSlice 控制器处理的变更都是足够小的,可以添加到某已有 EndpointSlice 中去的。 并且,假使无法添加到已有的切片中,不管怎样都很快就会创建一个新的 EndpointSlice 对象。Deployment 的滚动更新为重新为 EndpointSlice 打包提供了一个自然的机会,所有 Pod 及其对应的端点在这一期间都会被替换掉。
由于 EndpointSlice 变化的自身特点,端点可能会同时出现在不止一个 EndpointSlice 中。鉴于不同的 EndpointSlice 对象在不同时刻到达 Kubernetes 的监视/缓存中, 这种情况的出现是很自然的。
说明:
EndpointSlice API 的客户端必须能够处理特定端点地址出现在多个 EndpointSlice 中的情况。
你可以在 kube-proxy 中的 EndpointSliceCache 代码中找到有关如何执行这个端点去重的参考实现。
利用EndpointSlices扩展Kubernetes网络,提供更强的可伸缩性和功能 - alauda - 博客园
【K8s概念】端点切片(Endpoint Slices) - Varden - 博客园
使用 EndpointSlice 扩展 Kubernetes 网络_K8sMeetup 社区的博客-CSDN博客_endpoint slice