Ця стаття була опублікована більше року тому. Старіші статті можуть містити застарілу інформацію. Перевірте, чи інформація на цій сторінці не втратила актуальності з моменту її публікації.
Kubernetes v1.33: Продовження переходу від Endpoints до EndpointSlices
Відтоді як EndpointSlices (KEP-752) зʼявились у версії v1.15 як alpha, а згодом стали стабільними (GA) у v1.21, API Endpoints у Kubernetes поступово втрачало актуальність. Нові можливості Service, як-от двостекова мережа та розподіл трафіку, підтримуються лише через API EndpointSlice, тож усі проксі-сервери сервісів, реалізації Gateway API та подібні контролери мусили перейти від використання Endpoints до EndpointSlices. Наразі API Endpoints існує фактично лише для того, щоб не ламати робочі навантаження та скрипти кінцевих користувачів, які досі його використовують.
Починаючи з Kubernetes 1.33, API Endpoints офіційно оголошено застарілим, і API-сервер повертатиме попередження користувачам, які читають або записують ресурси Endpoints замість використання EndpointSlices.
Зрештою, план (як описано в KEP-4974) полягає в тому, щоб змінити критерії відповідності Kubernetes так, щоб вони більше не вимагали від кластерів запускати контролер Endpoints (який генерує обʼєкти Endpoints на основі Service і Podʼів), аби уникнути роботи, яка не потрібна в більшості сучасних кластерів.
Отже, хоча згідно з політикою застарівання 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. (API Endpoints не підтримує подвійний стек, тому обʼєкт Endpoints показує лише адреси з основного сімейства адрес кластера.) Хоча будь-який Service з кількома точками доступу може мати кілька EndpointSlice, є три основні випадки, коли ви це побачите:
EndpointSlice може представляти лише точки доступу одного сімейства IP, тож двостекові Service матимуть окремі EndpointSlice для IPv4 та IPv6.
Усі точки доступу в EndpointSlice мають бути спрямовані на однакові порти. Тож, наприклад, якщо у вас є набір Podʼів точок доступу, які слухають порт 80, і ви розгортаєте оновлення, щоб натомість вони слухали порт 8080, то поки розгортання триває, Service потребуватиме двох EndpointSlice: одного для точок доступу, що слухають порт 80, і одного для точок доступу, що слухають порт 8080.
Коли Service має понад 100 точок доступу, контролер EndpointSlice розділить їх на кілька EndpointSlice, замість того щоб обʼєднувати їх в один надмірно великий обʼєкт, як це робить контролер Endpoints.
Оскільки між Service і EndpointSlice немає передбачуваного відображення 1:1, неможливо заздалегідь дізнатися, якою буде фактична назва ресурсу (ресурсів) EndpointSlice для Service; тож замість того, щоб отримувати EndpointSlice за назвою, ви запитуєте всі EndpointSlice з міткою "kubernetes.io/service-name", що вказує на Service:
$ 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) {
// Для Service (ще?) не існує Endpoints
...
}
// обробка інших помилок
...
}
// обробка `endpoint`
...
Для EndpointSlice це буде:
// Отримати всі EndpointSlice для Service `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 {
// Для Service (ще?) не існує EndpointSlice
...
}
// обробка `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-сервер сам додасть унікальний суфікс. Назва обʼєкта не має значення — важлива мітка"kubernetes.io/service-name", що вказує на Service.Необхідно явно вказати
addressType: IPv4(абоIPv6).EndpointSlice подібний до одного елемента масиву
"subsets"в Endpoints. Обʼєкт Endpoints з кількома підмножинами (subsets) зазвичай потрібно буде виразити як кілька EndpointSlice, кожен з іншими"ports".Поля
endpointsіaddresses— масиви, але за домовленістю кожен масивaddressesмістить лише один елемент. Якщо ваш Service має кілька точок доступу, вам потрібно мати кілька елементів у масивіendpoints, кожен з одним елементом у своєму масивіaddresses.API Endpoints перелічує «готові» та «неготові» точки доступу окремо, тоді як API EndpointSlice дозволяє кожній точці доступу мати повʼязані з нею стани (як-от "
ready: false").
І, звісно, після переходу на EndpointSlice можна використовувати специфічні для нього можливості, як-от підказки топології та точки доступу, що завершують роботу. Докладніше в документації до API EndpointSlice.