Сервис не отвечает: selector не сходится
Почему пустой список endpoints это главный симптом, как сверить selector с метками Pod и с какой стороны чинить расхождение.
Deployment раскатан, Pod в статусе Running и Ready, Service создан. При
обращении к нему клиент получает connection refused или таймаут.
Логи приложения при этом пустые: запросы туда просто не доходят.
Так выглядит расхождение между selector Service и метками Pod.
Как Service ищет Pod
Service - объект Kubernetes, который даёт группе Pod стабильный адрес. Про сами Pod он не знает ничего. Он знает только их метки (labels).
Контроллер EndpointSlice смотрит, у каких Pod в namespace метки
совпадают с полем spec.selector Service, и кладёт их IP в объект
EndpointSlice. Namespace тут важен: поиск идёт только внутри того же
namespace, где живёт Service. Дальше kube-proxy, компонент на каждой
ноде, читает EndpointSlice и настраивает сетевые правила: пакеты на
ClusterIP (внутренний адрес Service) уходят на IP конкретных Pod.
Если selector не сошёлся, список IP пуст, правил нет, пакету некуда идти. Клиент видит отказ или таймаут, в зависимости от типа Service и сетевого плагина.
Главный индикатор: список endpoints
Это первая команда, когда Service не отвечает.
kubectl get endpoints webapp
Вывод бывает трёх видов.
Есть IP-адреса: selector сошёлся, Pod живы. Значит проблема дальше - в
targetPort, в readiness или в том, на каком порту слушает контейнер.
Пусто, <none>: selector не нашёл ни одного Pod. Либо метки не
совпадают, либо подходящих Pod нет совсем, либо ни один из них не
Ready.
NotReadyAddresses: Pod нашлись, но не прошли readiness-проверку. IP
зарезервированы, трафик туда не идёт.
Объект v1 Endpoints признан устаревшим начиная с 1.33, и kubectl
печатает предупреждение. Актуальная команда с той же информацией и
чуть подробнее:
kubectl get endpointslice -l kubernetes.io/service-name=webapp
Как сверить selector и метки
Selector Service:
kubectl get svc webapp -o jsonpath='{.spec.selector}'
Метки Pod:
kubectl get pods --show-labels
Совпадение проверяется по каждому ключу отдельно и по точному
равенству. Selector {app: webapp} поймает Pod с метками
{app: webapp, version: v1}, потому что лишние метки не мешают. Но не
поймает {app: web} и не поймает {tier: webapp}. Ни подстрок, ни
регулярных выражений тут нет.
Кросс-проверка: достучаться до Pod напрямую
Чтобы убедиться, что приложение вообще живо:
# IP Pod
kubectl get pod -l app=webapp -o jsonpath='{.items[0].status.podIP}'
# Запрос с временного Pod в том же namespace
kubectl run probe --image=busybox:1.36 --rm -it --restart=Never -- \
wget -qO- <POD_IP>
Если по IP Pod ответ приходит, а через Service нет, причина в selector или в портах. Приложение исправно.
Selector mismatch и port mismatch
Два разных дефекта, которые снаружи выглядят одинаково: «сервис не работает». Различает их вывод endpoints.
| Симптом | Endpoints | Причина |
|---|---|---|
| ответа нет | пусто | selector не совпал с метками |
connection refused | есть IP | targetPort не равен containerPort |
| таймаут | есть IP | блокирует NetworkPolicy или Pod не Ready |
Поэтому endpoints смотрят первыми. Это развилка: пусто - разбираться с метками, есть IP - с портами и сетевыми политиками.
Как чинят
Расхождение убирают с одной из двух сторон, и стороны не равноценны.
Правка selector у Service делается одной командой, и endpoints наполняются сразу:
kubectl patch svc webapp -p '{"spec":{"selector":{"app":"webapp"}}}'
Правка меток Pod через Deployment сложнее. Поле
spec.selector.matchLabels у существующего Deployment изменить нельзя.
Если поправить метки в template, не тронув matchLabels, получится
ошибка валидации. Значит, Deployment придётся удалить и создать заново,
а это простой сервиса.
Отсюда практическое правило: при расхождении обычно правят Service.
Исключение - когда метки Pod откровенно неверные по смыслу, например
app: web вместо принятого в проекте app: webapp. Тогда чинят
источник, а не симптом.
Как не допустить
Метки и selector берут из одного места. В Kustomize это commonLabels,
в Helm - шаблон selectorLabels, который подставляется и в Deployment,
и в Service. Руками одинаковую строку в двух файлах рано или поздно
разъедет.
Проверка после каждой выкатки нового Service занимает секунду:
kubectl get endpoints <svc> должен показывать IP, а не <none>. В
CI это ровно одна строка с jsonpath и сравнением на пустоту.
И kubectl describe svc в конце показывает поле Endpoints. Пустое
значение там - тот же симптом, только замеченный на минуту раньше, чем
жалоба от клиента.
Домашка
Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.
./run.sh start k8s-service-selector-mismatch # развернуть стартовое состояние
./run.sh verify k8s-service-selector-mismatch # проверить текущий шаг
Коротко
- Service находит Pod только по меткам, совпадение точное и по каждому ключу.
- Пустой список endpoints это симптом расхождения selector и меток.
- Endpoints с IP плюс
connection refusedуказывают наtargetPort, а не на selector. spec.selector.matchLabelsу Deployment иммутабельно, поэтому чаще правят Service.- Единый источник меток в Kustomize или Helm убирает класс проблемы целиком.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс