持续从 端点 过渡到 端点切片
自 EndpointSlices (KEP-752) 在 v1.15 中作为 alpha 特性添加并在 v1.21 中进入 GA 以来,Kubernetes 中的 Endpoints API 一直在闲置。像双栈网络和流量分发这样的新 Service 特性仅通过 EndpointSlice API 支持,因此所有 Service 代理、Gateway API 实现以及类似的控制器都必须从使用 Endpoints 移植到使用 EndpointSlices。此时,Endpoints API 实际上仅是为了避免破坏仍然使用它的最终用户工作负载和脚本。
从 Kubernetes 1.33 开始,Endpoints API 现已正式弃用,API 服务器将向读取或写入 Endpoints 资源而不是使用 EndpointSlices 的用户返回警告。
最终,计划(如KEP-4974 中所述)是更改 Kubernetes 一致性标准,不再要求集群运行 Endpoints controller(它根据 Service 和 Pod 生成 Endpoints 对象),以避免在大多数现代集群中进行不必要的工作。
因此,虽然Kubernetes 弃用策略意味着 Endpoints 类型本身可能永远不会完全消失,但仍在使用 Endpoints API 的用户应该开始将其工作负载或脚本迁移到 EndpointSlices。
从 Endpoints 迁移到 EndpointSlices 的注意事项
使用 EndpointSlices 而不是 Endpoints
对于最终用户来说,Endpoints API 和 EndpointSlice API 之间最大的变化是,虽然每个带 selector
的 Service 只有一个 Endpoints 对象(与 Service 同名),但一个 Service 可以关联任意数量的 EndpointSlices。
$ kubectl get endpoints myservice
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME ENDPOINTS AGE
myservice 10.180.3.17:443 1h
$ kubectl get endpointslice -l kubernetes.io/service-name=myservice
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
myservice-7vzhx IPv4 443 10.180.3.17 21s
myservice-jcv8s IPv6 443 2001:db8:0123::5 21s
在这种情况下,由于 Service 是双栈的,它有两个 EndpointSlices:一个用于 IPv4 地址,一个用于 IPv6 地址。(Endpoints API 不支持双栈,因此 Endpoints 对象仅显示集群主地址族中的地址。)尽管任何具有多个端点的 Service 都可以有多个 EndpointSlices,但在以下三种主要情况下你会看到这种情况:
一个 EndpointSlice 只能表示单个 IP 家族的端点,因此双栈 Service 将具有独立的 IPv4 和 IPv6 的 EndpointSlices。
EndpointSlice 中的所有端点必须指向相同的端口。因此,例如,如果您有一组端点 Pod 正在监听端口 80,然后推出更新使它们改为监听端口 8080,那么在更新过程中,该 Service 将需要两个 EndpointSlices:一个用于监听端口 80 的端点,一个用于监听端口 8080 的端点。
当 Service 拥有超过 100 个端点时,EndpointSlice controller 会将这些端点分割成多个 EndpointSlices,而不是像 Endpoints controller 那样将它们聚合成一个过大的对象。
由于 Service 和 EndpointSlices 之间没有可预测的一对一映射关系,因此无法提前知道 Service 对应的 EndpointSlice 资源名称是什么;因此,您不能按名称获取 EndpointSlice(s),而是要求所有带有指向 Service 的“kubernetes.io/service-name
”标签的 EndpointSlice(s)。
$ kubectl get endpointslice -l kubernetes.io/service-name=myservice
Go 代码中也需要类似的更改。对于 Endpoints,您会做如下操作:
// Get the Endpoints named `name` in `namespace`.
endpoint, err := client.CoreV1().Endpoints(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
// No Endpoints exists for the Service (yet?)
...
}
// handle other errors
...
}
// process `endpoint`
...
对于 EndpointSlices,则变为:
// Get all EndpointSlices for Service `name` in `namespace`.
slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx,
metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + name})
if err != nil {
// handle errors
...
} else if len(slices.Items) == 0 {
// No EndpointSlices exist for the Service (yet?)
...
}
// process `slices.Items`
...
生成 EndpointSlices 而不是 Endpoints
对于生成 Endpoints 的人(或控制器)来说,迁移到 EndpointSlices 会稍微容易一些,因为在大多数情况下您不必担心多个 Slice。您只需要更新 YAML 或 Go 代码以使用新类型(它组织信息的方式与 Endpoints 略有不同)。
例如,这个 Endpoints 对象:
apiVersion: v1
kind: Endpoints
metadata:
name: myservice
subsets:
- addresses:
- ip: 10.180.3.17
nodeName: node-4
- ip: 10.180.5.22
nodeName: node-9
- ip: 10.180.18.2
nodeName: node-7
notReadyAddresses:
- ip: 10.180.6.6
nodeName: node-8
ports:
- name: https
protocol: TCP
port: 443
会变成类似这样:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: myservice
labels:
kubernetes.io/service-name: myservice
addressType: IPv4
endpoints:
- addresses:
- 10.180.3.17
nodeName: node-4
- addresses:
- 10.180.5.22
nodeName: node-9
- addresses:
- 10.180.18.12
nodeName: node-7
- addresses:
- 10.180.6.6
nodeName: node-8
conditions:
ready: false
ports:
- name: https
protocol: TCP
port: 443
需要注意的几点:
此示例使用显式
name
,但您也可以使用generateName
并让 API 服务器附加唯一的后缀。名称本身并不重要:重要的是指回 Service 的"kubernetes.io/service-name"
标签。您必须明确指明
addressType: IPv4
(或IPv6
)。一个 EndpointSlice 类似于 Endpoints 中
"subsets"
数组的一个元素。具有多个 subset 的 Endpoints 对象通常需要表示为多个 EndpointSlices,每个具有不同的"ports"
。endpoints
和addresses
字段都是数组,但按照惯例,每个addresses
数组只包含一个元素。如果您的 Service 有多个端点,那么您需要在endpoints
数组中有多个元素,每个元素在其addresses
数组中都有一个元素。Endpoints API 分别列出“就绪”和“未就绪”端点,而 EndpointSlice API 允许每个端点拥有与其相关的条件(例如“
ready: false
”)。
当然,一旦您迁移到 EndpointSlice,就可以利用 EndpointSlice 特有的功能,例如拓扑提示和终止端点。请参阅EndpointSlice API 文档以获取更多信息。