Kubernetes v1.33: Продовження переходу від Endpoints до EndpointSlices
Відтоді як EndpointSlices (KEP-752) з’явились у версії v1.15 як alpha, а згодом стали стабільними (GA) у v1.21, API Endpoints у Kubernetes поступово втрачало актуальність. Нові можливості Service, як-от dual-stack networking та traffic distribution, підтримуються лише через API EndpointSlice, тож усі проксі-сервери сервісів, реалізації Gateway API та подібні контролери мусили перейти від використання Endpoints до EndpointSlices. Станом на сьогодні API Endpoints зберігається здебільшого задля сумісності — щоб не ламати робочі навантаження та скрипти користувачів, які досі його використовують.
Починаючи з Kubernetes 1.33, API Endpoints офіційно оголошено застарілим, і API-сервер повертатиме попередження користувачам, які читають або записують ресурси Endpoints замість використання EndpointSlices.
Зрештою, згідно з KEP-4974, план полягає в тому, щоб змінити критерії Kubernetes Conformance, вилучивши з вимог обов’язкове використання контролера Endpoints (який генерує об’єкти Endpoints на основі Services і Pods), аби уникнути зайвої роботи в більшості сучасних кластерів.
Отже, хоча згідно з політикою застарівання Kubernetes тип Endpoints, ймовірно, ніколи не буде повністю вилучено, користувачам, які досі мають робочі навантаження або скрипти, що використовують API Endpoints, слід починати їх перенесення на EndpointSlices.
Примітки щодо міграції з Endpoints до EndpointSlices
Використання EndpointSlices замість Endpoints
Для кінцевих користувачів найбільшою відмінністю між API Endpoints та API EndpointSlice є те, що кожен Service із selector
має рівно один об’єкт Endpoints (з такою ж назвою, як і сам Service), тоді як у нього може бути будь-яка кількість пов’язаних EndpointSlice:
$ 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
У цьому прикладі сервіс є двостековим (dual-stack), тому має два EndpointSlice: один для IPv4-адрес, інший — для IPv6. (Endpoints не підтримує dual-stack, тому виводить адреси лише у головному стеку кластера.)
Хоча будь-який Service з кількома endpointʼами може мати кілька EndpointSlice, є три основні випадки, коли ви точно побачите це:
EndpointSlice може представляти лише одну IP-сім’ю, тож dual-stack-сервіси матимуть окремі EndpointSlice для IPv4 та IPv6.
Усі endpoints в одному EndpointSlice повинні мати однакові порти. Наприклад, якщо під час оновлення Podsʼи змінюють порт з 80 на 8080, то на час розгортання Service матиме два EndpointSlice: один для порту 80, інший — для 8080.
Якщо в Service більше 100 endpointʼів, контролер EndpointSlice розділить їх на кілька об’єктів, щоб уникнути створення надто великого ресурсу — на відміну від Endpoints.
Оскільки немає передбачуваного співвідношення 1:1 між Service і EndpointSlice, неможливо заздалегідь знати імена пов’язаних EndpointSlice. Натомість, слід фільтрувати їх за міткою kubernetes.io/service-name
, що вказує на назву сервісу:
$ kubectl get endpointslice -l kubernetes.io/service-name=myservice
Подібні зміни потрібні й у Go-коді. Для Endpoints це виглядало так:
// Отримати Endpoints з назвою `name` у просторі імен `namespace`.
endpoint, err := client.CoreV1().Endpoints(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
// Endpoints для сервісу ще не існує
...
}
// обробка інших помилок
...
}
// обробка `endpoint`
...
Для EndpointSlice це буде:
// Отримати всі EndpointSlice для сервісу `name` у просторі `namespace`.
slices, err := client.DiscoveryV1().EndpointSlices(namespace).List(ctx,
metav1.ListOptions{LabelSelector: discoveryv1.LabelServiceName + "=" + name})
if err != nil {
// обробка помилок
...
} else if len(slices.Items) == 0 {
// EndpointSlices для сервісу ще не існує
...
}
// обробка `slices.Items`
...
Генерація EndpointSlices замість Endpoints
Для користувачів або контролерів, які створюють об’єкти Endpoints, перехід на EndpointSlices зазвичай є простішим, оскільки в більшості випадків не потрібно опрацьовувати кілька sliceʼів. Достатньо оновити YAML або Go-код, щоб використовувати новий тип (із трохи зміненою структурою).
Наприклад, цей об’єкт 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-сервер сам додасть унікальний суфікс. Назва об’єкта не має значення — важлива мітка"kubernetes.io/service-name"
, що вказує на Service.Необхідно явно вказати
addressType: IPv4
(абоIPv6
).Один EndpointSlice відповідає одному елементу масиву
"subsets"
в Endpoints. Якщо Endpoints мав кількаsubsets
, треба створити кілька EndpointSlice з різними"ports"
.Поля
endpoints
іaddresses
— масиви, але зазвичайaddresses
містить лише один елемент. Якщо сервіс має кілька endpointʼів — це мають бути окремі елементи масивуendpoints
, кожен з однимaddresses
.В Endpoints "готові" й "не готові" адреси розділено, тоді як в EndpointSlice кожен endpoint може мати атрибут
conditions
(наприклад,ready: false
).
І, звісно, після переходу на EndpointSlice, можна використовувати специфічні для нього можливості, наприклад, topology hints чи terminating endpoints. Докладніше в документації до API EndpointSlice.