Kubernetes v1.33:继续从 Endpoints 过渡到 EndpointSlices

自从 EndpointSlices (KEP-752) 在 v1.15 中作为 alpha 版本引入,并在 v1.21 中正式发布(GA)以来,Kubernetes 中的 Endpoints API 就一直被搁置。新的 Service 功能,如双栈网络流量分发,仅通过 EndpointSlice API 支持,因此所有服务代理、Gateway API 实现和类似的控制器都必须从使用 Endpoints 移植到使用 EndpointSlices。目前,Endpoints API 实际上只是为了避免破坏仍然使用它的最终用户工作负载和脚本而存在。

从 Kubernetes 1.33 开始,Endpoints API 已被正式弃用,API 服务器将向读取或写入 Endpoints 资源而不是使用 EndpointSlices 的用户返回警告。

最终,计划(如 KEP-4974 中所述)是更改 Kubernetes 一致性标准,不再要求集群运行 Endpoints 控制器(该控制器根据 Services 和 Pods 生成 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

在这种情况下,由于该服务是双栈的,它有两个 EndpointSlices:一个用于 IPv4 地址,一个用于 IPv6 地址。(Endpoints API 不支持双栈,因此 Endpoints 对象只显示集群主地址族中的地址。)尽管任何具有多个端点的 Service 都*可以*有多个 EndpointSlices,但您会看到这种情况主要有三种情况:

  • 一个 EndpointSlice 只能表示单个 IP 族的端点,因此双栈服务将为 IPv4 和 IPv6 分别设置 EndpointSlices。

  • 一个 EndpointSlice 中的所有端点必须指向相同的端口。因此,例如,如果您有一组在端口 80 上监听的端点 Pod,然后推出更新使其在端口 8080 上监听,那么在部署过程中,该 Service 将需要 2 个 EndpointSlices:一个用于在端口 80 上监听的端点,另一个用于在端口 8080 上监听的端点。

  • 当一个 Service 有超过 100 个端点时,EndpointSlice 控制器会将端点拆分为多个 EndpointSlices,而不是像 Endpoints 控制器那样将它们聚合到一个过大的对象中。

由于 Services 和 EndpointSlices 之间没有可预测的一对一映射,因此无法预先知道一个 Service 的 EndpointSlice 资源的实际名称;因此,您不是按名称获取 EndpointSlice,而是请求所有带有指向该 Service 的 "kubernetes.io/service-name" 标签的 EndpointSlices。

$ 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 稍微容易一些,因为在大多数情况下,您不必担心多个分片。您只需更新您的 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 服务器追加一个唯一的后缀。名称本身并不重要:重要的是 "kubernetes.io/service-name" 标签指回 Service。

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

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

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

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

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