Endpoints и selector: как Service теряет поды
Откуда Service берёт список адресов, почему Endpoints уступил место EndpointSlice и что обнуляет список.
Кто такие Endpoints
Service хранит selector. Например:
spec:
selector:
app: api
Рядом с каждым таким Service kube-controller-manager автоматически
создаёт объект с адресами Pod, которые попали под selector и готовы
(Ready). Исторически это Endpoints, сейчас ему на смену пришёл
EndpointSlice. Оба живут рядом с Service и показывают одну и ту же
картину.
kubectl get svc api
kubectl get endpoints api
kubectl get endpointslice -l kubernetes.io/service-name=api
Endpoints устарел, актуален EndpointSlice
Объект Endpoints (API-группа v1) объявлен устаревшим начиная с
Kubernetes 1.33. Он никуда не делся и продолжает создаваться, но kubectl
теперь честно предупреждает:
kubectl get endpoints api
# Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
# NAME ENDPOINTS AGE
# api 10.42.0.142:8080,10.42.0.143:8080 1s
Замена - EndpointSlice (discovery.k8s.io/v1). Именно его читает
kube-proxy, когда программирует правила маршрутизации трафика.
kube-proxy - компонент на каждой ноде, который настраивает правила сети
для Service.
Причина замены в масштабе. Endpoints - один объект на Service, и все адреса лежат в нём списком. У Service с тысячей Pod любое изменение (поднялся Pod, упал Pod) переписывает весь объект целиком, и эта версия рассылается каждому узлу кластера. На больших кластерах это заметная нагрузка на control plane и сеть.
EndpointSlice режет тот же список на срезы, по умолчанию до 100 адресов в каждом. Меняется один Pod - переписывается только его срез, остальные не трогаются. Отсюда и название: slice, кусок общего списка.
kubectl get endpointslice -l kubernetes.io/service-name=api
# NAME ADDRESSTYPE PORTS ENDPOINTS AGE
# api-jdlhd IPv4 8080 10.42.0.142,10.42.0.143 1s
Имя среза генерируется автоматически (api-jdlhd), поэтому искать их
принято по лейблу kubernetes.io/service-name, а не по имени Service.
Что ещё умеет EndpointSlice, чего не умел Endpoints:
addressType- разделение IPv4, IPv6 и FQDN по разным срезам, за счёт этого работают dual-stack Service;- условия каждого адреса по отдельности:
ready,serving,terminating. Endpoints знает только «готов или нет» и держит неготовые адреса в отдельном поле; - подсказки топологии для маршрутизации в свою зону.
v1 Endpoints | discovery.k8s.io/v1 EndpointSlice | |
|---|---|---|
| Статус | deprecated с 1.33 | актуальный |
| Объектов на Service | один | по срезу на каждые 100 адресов |
| Изменился один Pod | переписывается весь список | переписывается один срез |
| Состояние адреса | готов или нет | ready, serving, terminating |
| Читает kube-proxy | нет | да |
Для быстрой ручной проверки «дошёл ли Pod до Service» короткая команда
kubectl get endpoints всё ещё удобнее, поэтому в чужих гайдах она
встречается постоянно. В новом коде и в скриптах опирайтесь на
EndpointSlice: у Endpoints впереди только удаление из API.
Что попадает в Endpoints
Контроллер собирает IP Pod, если выполнено всё:
- Pod в том же namespace что и Service.
- Все labels из
selectorсовпадают с labels Pod. - Pod в фазе
Running. - У всех readinessProbe пройден последний check (или probe нет - тогда готов сразу). readinessProbe - проверка, по которой Kubernetes понимает, готов ли контейнер принимать трафик.
- Pod не в процессе удаления (у него не выставлен
deletionTimestamp).
Стоит сломать любую галочку - IP уходит из Endpoints за пару секунд. kube-proxy перестаёт направлять туда трафик.
Что показывает kubectl
$ kubectl get endpoints api
NAME ENDPOINTS AGE
api 10.42.0.5:8080,10.42.0.6:8080 2m
В EndpointSlice вывод подробнее: видно условия Ready/NotReady/Terminating
для каждого адреса. Это полезно при отладке: если pod есть, а трафик
не идёт, скорее всего у него readinessProbe красный.
kubectl describe endpoints api
kubectl get endpointslice -l kubernetes.io/service-name=api -o yaml
Как ломается селектор
Три классических варианта.
1. Опечатка в selector. Service ищет app: aip (вместо api).
Endpoints пустые, запросы валятся в connection refused. Лечится
правкой Service:
kubectl patch svc api --type=merge -p '{"spec":{"selector":{"app":"api"}}}'
2. Pod перевесили на другие метки. Кто-то прогнал
kubectl label pod ... app=other --overwrite. Pod живы, но больше не
матчат Service. Лечится возвратом метки.
3. Service в одном namespace, Pod в другом. Selector работает
только внутри своего namespace. Никакой selector: { ns: other } его не
расширит.
Selector матчит «слишком много»: попали Pod другого приложения с тем же ярлыком. Балансировка летит в чужие IP.
Headless Service
Если в Service выставить clusterIP: None, Kubernetes не выдаёт
виртуальный IP. DNS возвращает напрямую адреса Pod. Endpoints всё равно
собирает тот же контроллер по тому же selector, отличается только то,
как клиент получает адрес.
Service без selector
Можно создать Service без selector и руками управлять Endpoints. Это
способ направить трафик внутри кластера на внешний адрес (база на
отдельном сервере, managed-инстанс, что угодно). Контроллер ничего сам
не пишет, IP прописывают вручную:
apiVersion: v1
kind: Endpoints
metadata:
name: external-db
subsets:
- addresses: [{ ip: 10.0.0.42 }]
ports: [{ port: 5432 }]
Эта схема показывает, что Endpoints - реально отдельный объект, который можно редактировать независимо. Просто обычно это делает контроллер.
Что обычно ломается
- Selector матчит «слишком мало»: опечатка. Endpoints пустые.
- Pod готов, но readinessProbe не настроен или красный - IP не попадает.
- Service в другом namespace, чем Pod. Selector кросс-ns не работает.
Домашка
Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.
./run.sh start k8s-endpoints-selector # развернуть стартовое состояние
./run.sh verify k8s-endpoints-selector # проверить текущий шаг
Коротко
- Список адресов за Service ведёт контроллер, а не сам Service.
v1 Endpointsdeprecated с Kubernetes 1.33, актуаленdiscovery.k8s.io/v1 EndpointSlice, и именно его читает kube-proxy.- EndpointSlice режет список на срезы до 100 адресов, поэтому изменение одного Pod не переписывает весь список.
- Адрес попадает в список только при совпадении namespace, labels, фазы
Running, зелёного readinessProbe и отсутствии удаления. - Service без selector позволяет прописать адреса вручную и увести трафик на внешний хост.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс