持续从 端点 过渡到 端点切片

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

需要注意的几点:

  1. 此示例使用显式 name,但您也可以使用 generateName 并让 API 服务器附加唯一的后缀。名称本身并不重要:重要的是指回 Service 的 "kubernetes.io/service-name" 标签。

  2. 您必须明确指明 addressType: IPv4(或 IPv6)。

  3. 一个 EndpointSlice 类似于 Endpoints 中 "subsets" 数组的一个元素。具有多个 subset 的 Endpoints 对象通常需要表示为多个 EndpointSlices,每个具有不同的 "ports"

  4. endpointsaddresses 字段都是数组,但按照惯例,每个 addresses 数组只包含一个元素。如果您的 Service 有多个端点,那么您需要在 endpoints 数组中有多个元素,每个元素在其 addresses 数组中都有一个元素。

  5. Endpoints API 分别列出“就绪”和“未就绪”端点,而 EndpointSlice API 允许每个端点拥有与其相关的条件(例如“ready: false”)。

当然,一旦您迁移到 EndpointSlice,就可以利用 EndpointSlice 特有的功能,例如拓扑提示和终止端点。请参阅EndpointSlice API 文档以获取更多信息。