← Kubernetes: разбор поломок
7 мин · средне · Урок 6 из 12

Сервис не отвечает: 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

kubectl get endpoints первая команда при отказе Service IP-адреса selector сошёлся, копай порты <none> метки не совпали с selector NotReadyAddresses Pod есть, readiness не прошла
Одна команда, три исхода, и дальше копают в разных местах.

Это первая команда, когда 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есть IPtargetPort не равен 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    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.