|
| 1 | +--- |
| 2 | +layout: blog |
| 3 | +title: "Continuing the transition from Endpoints to EndpointSlices" |
| 4 | +slug: endpoints-deprecation |
| 5 | +date: 2025-XX-XX |
| 6 | +draft: true |
| 7 | +author: > |
| 8 | + Dan Winship (Red Hat) |
| 9 | +--- |
| 10 | + |
| 11 | +Since the addition of [EndpointSlices] ([KEP-752]) as alpha in v1.15 |
| 12 | +and later GA in v1.21, the |
| 13 | +Endpoints API in Kubernetes has been gathering dust. New Service |
| 14 | +features like [dual-stack networking] and [traffic distribution] are |
| 15 | +only supported via the EndpointSlice API, so all service proxies, |
| 16 | +Gateway API implementations, and similar controllers have had to be |
| 17 | +ported from using Endpoints to using EndpointSlices. At this point, |
| 18 | +the Endpoints API is really only there to avoid breaking end user |
| 19 | +workloads and scripts that still make use of it. |
| 20 | + |
| 21 | +As of Kubernetes 1.33, the Endpoints API is now officially deprecated, |
| 22 | +and the API server will return warnings to users who read or write |
| 23 | +Endpoints resources rather than using EndpointSlices. |
| 24 | + |
| 25 | +Eventually, the plan (as documented in [KEP-4794]) is to change the |
| 26 | +[Kubernetes Conformance] criteria to no longer require that clusters |
| 27 | +run the _Endpoints controller_ (which generates Endpoints objects |
| 28 | +based on Services and Pods), to avoid doing work that is unneeded in |
| 29 | +most modern-day clusters. |
| 30 | + |
| 31 | +Thus, while the [Kubernetes deprecation policy] means that the |
| 32 | +Endpoints type itself will probably never completely go away, users |
| 33 | +who still have workloads or scripts that use the Endpoints API should |
| 34 | +start migrating them to EndpointSlices. |
| 35 | + |
| 36 | +[EndpointSlices]: /blog/2020/09/02/scaling-kubernetes-networking-with-endpointslices/ |
| 37 | +[KEP-752]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/0752-endpointslices/README.md |
| 38 | +[dual-stack networking]: /docs/concepts/services-networking/dual-stack/ |
| 39 | +[traffic distribution]: /docs/reference/networking/virtual-ips/#traffic-distribution |
| 40 | +[Kubernetes deprecation policy]: /docs/reference/using-api/deprecation-policy/ |
| 41 | +[KEP-4974]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/4974-deprecate-endpoints/README.md |
| 42 | +[Kubernetes Conformance]: https://www.cncf.io/training/certification/software-conformance/ |
| 43 | + |
| 44 | +## Notes on migrating from Endpoints to EndpointSlices |
| 45 | + |
| 46 | +### Consuming EndpointSlices rather than Endpoints |
| 47 | + |
| 48 | +For end users, the biggest change between the Endpoints API and the |
| 49 | +EndpointSlice API is that while every Service with a `selector` has |
| 50 | +exactly 1 Endpoints object (with the same name as the Service), a |
| 51 | +Service may have any number of EndpointSlices associated with it: |
| 52 | + |
| 53 | +```console |
| 54 | +$ kubectl get endpoints myservice |
| 55 | +Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice |
| 56 | +NAME ENDPOINTS AGE |
| 57 | +myservice 10.180.3.17:443 1h |
| 58 | + |
| 59 | +$ kubectl get endpointslice -l kubernetes.io/service-name=myservice |
| 60 | +NAME ADDRESSTYPE PORTS ENDPOINTS AGE |
| 61 | +myservice-7vzhx IPv4 443 10.180.3.17 21s |
| 62 | +myservice-jcv8s IPv6 443 2001:db8:0123::5 21s |
| 63 | +``` |
| 64 | + |
| 65 | +In this case, because the service is dual stack, it has 2 |
| 66 | +EndpointSlices: 1 for IPv4 addresses and 1 for IPv6 addresses. (The |
| 67 | +Endpoints API does not support dual stack, so the Endpoints object |
| 68 | +shows only the addresses in the cluster's primary address family.) |
| 69 | +Although any Service with multiple endpoints _can_ have multiple |
| 70 | +EndpointSlices, there are three main cases where you will see this: |
| 71 | + |
| 72 | + - An EndpointSlice can only represent endpoints of a single IP |
| 73 | + family, so dual-stack Services will have separate EndpointSlices |
| 74 | + for IPv4 and IPv6. |
| 75 | + |
| 76 | + - All of the endpoints in an EndpointSlice must target the same |
| 77 | + ports. So, for example, if you have a set of endpoint Pods |
| 78 | + listening on port 80, and roll out an update to make them listen |
| 79 | + on port 8080 instead, then while the rollout is in progress, the |
| 80 | + Service will need 2 EndpointSlices: 1 for the endpoints listening |
| 81 | + on port 80, and 1 for the endpoints listening on port 8080. |
| 82 | + |
| 83 | + - When a Service has more than 100 endpoints, the EndpointSlice |
| 84 | + controller will split the endpoints into multiple EndpointSlices |
| 85 | + rather than aggregating them into a single excessively-large |
| 86 | + object like the Endpoints controller does. |
| 87 | + |
| 88 | +Because there is not a predictable 1-to-1 mapping between Services and |
| 89 | +EndpointSlices, there is no way to know what the actual name of the |
| 90 | +EndpointSlice resource(s) for a Service will be ahead of time; thus, |
| 91 | +instead of fetching the EndpointSlice(s) by name, you instead ask for |
| 92 | +all EndpointSlices with a "`kubernetes.io/service-name`" |
| 93 | +[label](/docs/concepts/overview/working-with-objects/labels/) pointing |
| 94 | +to the Service: |
| 95 | + |
| 96 | +```console |
| 97 | +$ kubectl get endpointslice -l kubernetes.io/service-name=myservice |
| 98 | +``` |
| 99 | + |
| 100 | +A similar change is needed in Go code. With Endpoints, you would do |
| 101 | +something like: |
| 102 | + |
| 103 | +```go |
| 104 | +// Get the Endpoints named `name` in `namespace`. |
| 105 | +endpoint, err := client.CoreV1().Endpoints(namespace).Get(ctx, name, metav1.GetOptions{}) |
| 106 | +if err != nil { |
| 107 | + if apierrors.IsNotFound(err) { |
| 108 | + // No Endpoints exists for the Service (yet?) |
| 109 | + ... |
| 110 | + } |
| 111 | + // handle other errors |
| 112 | + ... |
| 113 | +} |
| 114 | + |
| 115 | +// process `endpoint` |
| 116 | +... |
| 117 | +``` |
| 118 | + |
| 119 | +With EndpointSlices, this becomes: |
| 120 | + |
| 121 | +```go |
| 122 | +// Get all EndpointSlices for Service `name` in `namespace`. |
| 123 | +slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx, |
| 124 | + metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + name}) |
| 125 | +if err != nil { |
| 126 | + // handle errors |
| 127 | + ... |
| 128 | +} else if len(slices.Items) == 0 { |
| 129 | + // No EndpointSlices exist for the Service (yet?) |
| 130 | + ... |
| 131 | +} |
| 132 | + |
| 133 | +// process `slices.Items` |
| 134 | +... |
| 135 | +``` |
| 136 | + |
| 137 | +### Generating EndpointSlices rather than Endpoints |
| 138 | + |
| 139 | +For people (or controllers) generating Endpoints, migrating to |
| 140 | +EndpointSlices is slightly easier, because in most cases you won't |
| 141 | +have to worry about multiple slices. You just need to update your YAML |
| 142 | +or Go code to use the new type (which organizes the information in a |
| 143 | +slightly different way than Endpoints did). |
| 144 | + |
| 145 | +For example, this Endpoints object: |
| 146 | + |
| 147 | +```yaml |
| 148 | +apiVersion: v1 |
| 149 | +kind: Endpoints |
| 150 | +metadata: |
| 151 | + name: myservice |
| 152 | +subsets: |
| 153 | + - addresses: |
| 154 | + - ip: 10.180.3.17 |
| 155 | + nodeName: node-4 |
| 156 | + - ip: 10.180.5.22 |
| 157 | + nodeName: node-9 |
| 158 | + - ip: 10.180.18.2 |
| 159 | + nodeName: node-7 |
| 160 | + notReadyAddresses: |
| 161 | + - ip: 10.180.6.6 |
| 162 | + nodeName: node-8 |
| 163 | + ports: |
| 164 | + - name: https |
| 165 | + protocol: TCP |
| 166 | + port: 443 |
| 167 | +``` |
| 168 | +
|
| 169 | +would become something like: |
| 170 | +
|
| 171 | +```yaml |
| 172 | +apiVersion: discovery.k8s.io/v1 |
| 173 | +kind: EndpointSlice |
| 174 | +metadata: |
| 175 | + name: myservice |
| 176 | + labels: |
| 177 | + kubernetes.io/service-name: myservice |
| 178 | +addressType: IPv4 |
| 179 | +endpoints: |
| 180 | + - addresses: |
| 181 | + - 10.180.3.17 |
| 182 | + nodeName: node-4 |
| 183 | + - addresses: |
| 184 | + - 10.180.5.22 |
| 185 | + nodeName: node-9 |
| 186 | + - addresses: |
| 187 | + - 10.180.18.12 |
| 188 | + nodeName: node-7 |
| 189 | + - addresses: |
| 190 | + - 10.180.6.6 |
| 191 | + nodeName: node-8 |
| 192 | + conditions: |
| 193 | + ready: false |
| 194 | +ports: |
| 195 | + - name: https |
| 196 | + protocol: TCP |
| 197 | + port: 443 |
| 198 | +``` |
| 199 | +
|
| 200 | +Some points to note: |
| 201 | +
|
| 202 | +1. This example uses an explicit `name`, but you could also use |
| 203 | +`generateName` and let the API server append a unique suffix. The name |
| 204 | +itself does not matter: what matters is the |
| 205 | +`"kubernetes.io/service-name"` label pointing back to the Service. |
| 206 | + |
| 207 | +2. You have to explicitly indicate `addressType: IPv4` (or `IPv6`). |
| 208 | + |
| 209 | +3. An EndpointSlice is similar to a single element of the `"subsets"` |
| 210 | +array in Endpoints. An Endpoints object with multiple subsets will |
| 211 | +normally need to be expressed as multiple EndpointSlices, each with |
| 212 | +different `"ports"`. |
| 213 | + |
| 214 | +4. The `endpoints` and `addresses` fields are both arrays, but by |
| 215 | +convention, each `addresses` array only contains a single element. If |
| 216 | +your Service has multiple endpoints, then you need to have multiple |
| 217 | +elements in the `endpoints` array, each with a single element in its |
| 218 | +`addresses` array. |
| 219 | + |
| 220 | +5. The Endpoints API lists "ready" and "not-ready" endpoints |
| 221 | +separately, while the EndpointSlice API allows each endpoint to have |
| 222 | +conditions (such as "`ready: false`") associated with it. |
| 223 | + |
| 224 | +And of course, once you have ported to EndpointSlice, you can make use |
| 225 | +of EndpointSlice-specific features, such as topology hints and |
| 226 | +terminating endpoints. Consult the |
| 227 | +[EndpointSlice API documentation](/docs/reference/kubernetes-api/service-resources/endpoint-slice-v1) |
| 228 | +for more information. |
0 commit comments