Поды в статусе Running, приложение пишет в лог, что слушает порт, а запросы к Service падают с connection refused или висят до таймаута. Первый вопрос диагностики звучит так: попадают ли поды в endpoints Service. Ответ делит задачу на две ветки и убирает половину бесполезных проверок.
Если endpoints пустые, Service не знает, куда отправлять трафик. Типовые причины: селектор не совпал с метками подов, поды не прошли readiness-пробу, Service и поды оказались в разных namespace. Если endpoints заполнены, а ответа нет, проверяем targetPort, NetworkPolicy, DNS и политику внутреннего трафика. Утверждения о поле internalTrafficPolicy и о настройке DNS-файла kubelet ниже подкреплены официальной документацией Kubernetes; остальные проверки опираются на поведение стандартных объектов API, которое воспроизводится командами из статьи.
Почему Service не отвечает, хотя поды работают: первые шаги диагностики
Service не маршрутизирует трафик сам по себе. Объект описывает правило, а список адресов, куда доставлять пакеты, формируется отдельно: в Endpoints или в EndpointSlice. Пока этот набор пуст, обращение к ClusterIP уходит в никуда, и состояние приложения внутри пода значения не имеет.
Порядок действий простой: сначала убеждаемся, что за Service есть адреса, потом идём в приложение, сеть или DNS. Проверка занимает меньше минуты.
Три команды, которые сразу покажут, где искать проблему
kubectl get pods -o wide kubectl get endpoints <service-name> kubectl describe svc <service-name>
kubectl get pods -o wide: колонка READY показывает готовность (например, 1/1), колонка IP пригодится для проверки доступа в обход Service. Контейнер в статусе Running с READY 0/1 в endpoints не попадёт.
kubectl get endpoints <service-name>: ключевая команда. Пустая колонка ENDPOINTS означает, что проблема выше уровня сети: селектор, готовность подов или namespace. Заполненная колонка с парами IP:порт говорит, что Service видит поды, и дальше проверяем доставку трафика.
kubectl describe svc <service-name>: в выводе смотрим блоки Selector, Port и Endpoints. События у Service заполняются редко, поэтому за событиями идите к подам, а здесь сверяйте конфигурацию. Полный список Service в кластере удобно получить командой kubectl get svc -A, чтобы сразу заметить дубли имён и лишние namespace.
Адреса Service хранятся в срезах EndpointSlice. API Endpoints официально устарел начиная с Kubernetes 1.33, и API-сервер возвращает предупреждения тем, кто читает или пишет ресурсы Endpoints вместо EndpointSlices; сам объект Endpoints сохранён в основном для того, чтобы не ломать существующие рабочие нагрузки и скрипты (документация Kubernetes). Поэтому детальный разбор удобнее делать по срезам:
kubectl get endpointslices -l kubernetes.io/service-name=<service-name>
Отдельная причина, которую пропускают: поле internalTrafficPolicy. По документации Kubernetes политика внутреннего трафика Service ограничивает трафик так, чтобы он направлялся только к эндпоинтам на том же узле, откуда пришёл запрос. Значение Local указывает kube-proxy использовать исключительно локальные для узла эндпоинты. Для подов на узле, у которого нет эндпоинтов этого Service, сервис ведёт себя так, как будто эндпоинтов нет вообще, даже если они есть на других узлах. Значение по умолчанию (поле не задано) заставляет учитывать все эндпоинты.
kubectl get svc <service-name> -o jsonpath='{.spec.internalTrafficPolicy}'
Пустой вывод - норма. Вывод Local - повод проверить, есть ли на узле-источнике готовые поды и как настроено планирование.
Проверка endpoints и селекторов: почему kubectl get endpoints пустой
Селектор Service - это карта пар ключ=значение, и под попадает в endpoints только при совпадении всех пар. Одно лишнее слово в значении метки ломает маршрутизацию целиком, при этом сам под продолжает работать и отвечать на прямые запросы по своему IP.
kubectl get svc <service-name> -o jsonpath='{.spec.selector}'
kubectl get pods --show-labels
kubectl get pods -l app=myapp
kubectl describe svc <service-name>
Namespace учитывается тоже: Service ищет поды только в своём namespace. Если Deployment выкатили в другой namespace, endpoints останутся пустыми. Проверьте состав подов с метками именно там, где живёт Service:
kubectl get pods -n <namespace> --show-labels
Как сопоставить selector Service и labels подов
- Вытащите селектор: kubectl get svc <service-name> -o jsonpath='{.spec.selector}'.
- Посмотрите метки подов: kubectl get pods --show-labels или kubectl get pods -n <namespace> --show-labels.
- Сравните значения посимвольно. Проверьте раскладку, регистр и разделители: myapp и my-app - разные значения, а App=api не совпадёт с ключом app.
- Убедитесь, что под селектор попадает нужное количество подов: kubectl get pods -l app=myapp. Пустой список означает, что селектор не находит ни одного пода.
Пример типичной ошибки: в манифесте Deployment указано labels: app: my-app, а в Service - selector: app: myapp. Обе строки выглядят правдоподобно, endpoints при этом пустые. Исправление одно: привести метки к одному значению и применить обновлённый манифест.
Для детального анализа адресов и портов используйте kubectl get endpoints <service-name> -o yaml: в subsets видно, какие IP и порты Service считает готовыми. Если поле subsets отсутствует, значит, за Service не закреплён ни один адрес. Широкий набор сетевых проверок с командами kubectl get endpoints, docker network inspect и ip addr собран в отдельном материале про диагностику сетевых проблем в Docker и Kubernetes.
Поды в статусе Running, но не в endpoints: роль readiness-проб
В endpoints попадают только Ready-поды. Running описывает состояние контейнера, а Ready отражает результат readiness-пробы, и это разные вещи. Под может годами висеть в Running и никогда не войти в балансировку.
kubectl get pods kubectl describe pod <pod-name> kubectl logs <pod-name> --previous
В выводе kubectl describe pod смотрите секцию Conditions: строка Ready со значением False и сообщение вида ContainersNotReady - прямой указатель на пробу. Ниже, в секции Events, обычно видно, какая именно проверка не проходит и с какой периодичностью.
spec:
containers:
- name: api
image: myapp:1.4
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Что проверять в пробе: путь (endpoint /healthz должен существовать), порт (должен совпадать с реальным портом приложения), initialDelaySeconds для медленно стартующих сервисов. Если проба стучится на порт 8080, а приложение слушает 80, под останется NotReady, а endpoints будут пустыми.
Если readinessProbe в манифесте не задана, под считается Ready сразу после запуска контейнера, ещё до того как приложение подняло слушающий сокет. Обратная ошибка тоже встречается: livenessProbe с агрессивным таймаутом перезапускает контейнер до завершения инициализации, из-за чего под постоянно перезапускается и не успевает стать Ready. Флаг --previous в команде kubectl logs покажет логи предыдущего запуска контейнера, если он уже перезапускался.
targetPort и порты контейнера: где чаще всего ошибаются
Три порта легко перепутать. port - порт самого Service, на него обращаются клиенты по ClusterIP. targetPort - порт, на который kube-proxy перенаправляет трафик внутри пода. containerPort - запись в спецификации контейнера, которая сама по себе ничего не открывает и служит документацией для читающего манифест. Если targetPort не совпадает с портом прослушивания, пакеты уходят в закрытый порт, а endpoints при этом выглядят корректно.
kubectl get svc <service-name> -o yaml
kubectl describe pod <pod-name>
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].ports}'
Пример ошибки: в Service указано targetPort: 8080, приложение слушает 80. Симптомы - мгновенный отказ соединения или таймаут при заполненных endpoints. Если targetPort в манифесте не указан вообще, kube-proxy использует значение port, и это тоже стоит проверить в выводе kubectl get svc -o yaml.
Named ports избавляют от ручной синхронизации номеров: если в контейнере порт назван, например http, то в Service можно указать targetPort: http. При смене номера порта в приложении правка Service не потребуется.
Как проверить, что приложение слушает нужный порт
kubectl exec <pod-name> -- ss -tlnp kubectl exec <pod-name> -- netstat -tlnp kubectl exec <pod-name> -- curl -v http://localhost:8080 kubectl port-forward pod/<pod-name> 8080:8080
Команда ss -tlnp показывает слушающие сокеты вместе с процессами; в минималистичных образах её может не быть, тогда пригодится netstat. Если curl изнутри пода не отвечает на localhost, проблема в приложении или в его конфигурации, и Service здесь ни при чём. Для образов без shell и утилит запустите отладочный контейнер в том же поде: kubectl debug -it <pod-name> --image=busybox --target=<container-name>.
Проверка через kubectl port-forward pod/<pod-name> 8080:8080 и curl localhost:8080 с рабочей машины подтверждает, что порт открыт и отвечает. Так вы исключаете порт и приложение и переходите к проверке самой маршрутизации Service.
NetworkPolicy и сетевые блокировки: когда трафик режется на уровне сети
NetworkPolicy работает как firewall на уровне подов. Как только хотя бы одна политика выбирает под по podSelector и включает тип Ingress, весь входящий трафик к этому поду, не разрешённый явно, отбрасывается. Service с готовыми endpoints при этом остаётся без ответа, а логи приложения будут пустыми: пакеты до него не доходят.
kubectl get networkpolicy -A kubectl describe networkpolicy <policy-name> -n <namespace>
В описании политики проверяйте podSelector (кого защищает), policyTypes (Ingress, Egress или оба), блоки from с podSelector, namespaceSelector и ipBlock, а также список ports. Типовая рабочая политика выглядит так:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-frontend
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Политики выполняет CNI-плагин: по документации Kubernetes для использования NetworkPolicy нужно сетевое решение, поддерживающее NetworkPolicy, а создание объекта NetworkPolicy без реализующего его контроллера не даёт эффекта. Пример такого плагина с инструкцией по установке - Antrea (документация Kubernetes). Конкретный список поддерживаемых плагинов сверяйте с документацией вашего сетевого решения: если плагин не реализует NetworkPolicy, объекты создаются, но ни на что не влияют, и диагноз придётся искать в другом месте. Как устроена цепочка доставки трафика и где в ней место политик, разбираем в статье про сетевые модели Docker и Kubernetes.
Как отличить блокировку NetworkPolicy от других проблем
- Endpoints заполнены, targetPort совпадает с портом прослушивания, приложение отвечает на localhost внутри пода.
- Запросы из другого namespace или от другого пода заканчиваются таймаутом, а не отказом соединения.
- В namespace есть хотя бы один объект NetworkPolicy, а непустой список из kubectl get networkpolicy -n <namespace> это подтверждает.
Проверка тестовым подом даёт быстрый ответ: сначала обращаемся к Service, потом к IP пода напрямую.
kubectl run nettest --rm -it --image=busybox -- sh wget -qO- --timeout=3 http://<service-name>.<namespace>.svc.cluster.local:<port> wget -qO- --timeout=3 http://<pod-ip>:<port>
Если блокирует политика, недоступны оба адреса. Если Service не отвечает, а прямой IP пода работает, ищите проблему в endpoints, targetPort или в правилах kube-proxy. Похожую картину даёт internalTrafficPolicy: Local, когда на узле-источнике нет локальных эндпоинтов: трафик к ClusterIP не доставляется, хотя поды на других узлах готовы. В dev-среде для подтверждения гипотезы можно временно удалить политику и повторить запрос, в проде вместо удаления правьте правила from и ports.
DNS и связь изнутри пода: проверка CoreDNS и resolv.conf
Если имя Service не разрешается в IP, приложение получит ошибку имени и до сети дело не дойдёт. Проверяйте из того же пода, который не может подключиться: у разных namespace и разных подов отличаются DNS-политики и содержимое resolv.conf.
kubectl exec <pod-name> -- cat /etc/resolv.conf kubectl exec <pod-name> -- nslookup kubernetes.default kubectl exec <pod-name> -- nslookup <service-name>.<namespace>.svc.cluster.local
В resolv.conf должно быть три вещи: nameserver с адресом ClusterIP сервиса kube-dns (часто 10.96.0.10, конкретное значение зависит от service CIDR), строка search с доменами вида <namespace>.svc.cluster.local svc.cluster.local cluster.local и параметр options ndots:5. Если nameserver указывает на посторонний адрес, резолвинг внутри пода работать не будет.
Пошаговая проверка DNS изнутри пода
- Посмотрите /etc/resolv.conf внутри пода и убедитесь, что nameserver указывает на kube-dns.
- Проверьте служебное имя: nslookup kubernetes.default. Ошибка на этом шаге означает проблему в CoreDNS, а не в конкретном Service.
- Проверьте полное имя своего сервиса с точкой на конце: nslookup <service-name>.<namespace>.svc.cluster.local. Точка отключает перебор search-доменов и убирает ложные срабатывания.
- Сравните результат для короткого имени и FQDN. Короткое имя может не разрешаться из-за другого namespace в search-домене, при этом сам Service полностью работоспособен.
kubectl run dns-test --rm -it --image=busybox -- nslookup <service-name>.<namespace>.svc.cluster.local kubectl get pods -n kube-system -l k8s-app=kube-dns kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
Поды CoreDNS должны быть в статусе Running с READY 1/1. В логах ищите строки про SERVFAIL, таймауты upstream-серверов и ошибки плагина forward. Сбои DNS бывают и на уровне узла: kubelet передаёт каждому контейнеру информацию о DNS-резолвере, а для настройки иного DNS-конфига для подов используется флаг kubelet --resolv-conf (документация Kubernetes). В KubeletConfiguration за это отвечает поле resolvConf - файл конфигурации резолвера, используемый как основа для DNS-разрешения контейнеров; если задать его пустой строкой, значение по умолчанию переопределяется и DNS-запросы фактически отключаются (документация Kubernetes). Проверить стоит и сам файл на узле:
cat /etc/resolv.conf kubeadm config print init-defaults --component-configs KubeletConfiguration
Как отличить сбой Service от ошибки приложения
Разделитель простой: смотрите на характер ответа. Полное отсутствие ответа или быстрый отказ соединения означают, что пакет не дошёл до приложения: пустые endpoints, неверный targetPort, NetworkPolicy, DNS или политика локального трафика. Любой HTTP-код в ответе означает, что трафик дошёл до обработчика, и искать нужно в приложении или в Ingress-контроллере.
| Симптом | Где искать причину |
|---|---|
| Таймаут, ответа нет | Пустые endpoints, NetworkPolicy, сбой DNS, internalTrafficPolicy: Local |
| Быстрый отказ соединения | Неверный targetPort, приложение не слушает порт, пустые endpoints |
| HTTP 404, 405, 500 | Приложение: маршрут отсутствует, ошибка в обработчике |
| HTTP 502, 503 | Ingress-контроллер или отсутствие готовых эндпоинтов за Service |
Код 503 от Ingress-контроллера часто означает ровно то, с чего началась статья: за Service нет готовых адресов. Такой ответ не говорит об ошибке в коде приложения.
Проверка через port-forward и логи приложения
kubectl port-forward svc/<service-name> 8080:<service-port> curl -v http://localhost:8080/healthz kubectl logs <pod-name> --tail=50 kubectl get events -A --sort-by=.metadata.creationTimestamp
kubectl port-forward перенаправляет один или несколько локальных портов в под, выбирая под автоматически, если подходящих несколько; сессия переадресации завершается при завершении выбранного пода, и команду нужно запустить заново (документация Kubernetes). Если ответ приходит, приложение и порт в порядке, и дальше стоит разбираться с маршрутизацией Service и сетевыми политиками. Учитывайте ограничение: официальная документация по команде описывает её синтаксис и поведение, но не детали внутреннего механизма туннелирования, поэтому не стоит делать вывод, что успешный port-forward полностью исключает блокировку NetworkPolicy для обычного трафика.
Дальше смотрим логи приложения: kubectl logs <pod-name> --tail=50. Запросы, которые вообще не дошли до контейнера, в логах не появятся, и это само по себе диагностический признак. События кластера по всем namespace удобно отсортировать по времени: kubectl get events -A --sort-by=.metadata.creationTimestamp. Если внутри кластера всё работает, а снаружи доступа нет, проверьте тип публикации и firewall: разбор с диапазоном портов и чек-листом собран в статье про NodePort в Kubernetes.
Чек-лист восстановления работоспособности Service
- Статус подов: kubectl get pods -o wide. Контейнер должен быть Running, а READY - 1/1.
- Адреса за Service: kubectl get endpoints <service-name> и kubectl get endpointslices -l kubernetes.io/service-name=<service-name>.
- Селектор и метки: kubectl get svc <service-name> -o jsonpath='{.spec.selector}' против kubectl get pods --show-labels. Проверьте и namespace.
- Порты: port в Service, targetPort, containerPort и реально слушающий порт из kubectl exec <pod> -- ss -tlnp.
- Readiness-проба: секция Conditions в kubectl describe pod <pod-name> и события пода.
- Сетевые политики: kubectl get networkpolicy -A и разбор правил ingress с podSelector и ports.
- DNS: kubectl exec <pod> -- nslookup <service-name>.<namespace>.svc.cluster.local. и состояние подов CoreDNS.
- Логи приложения: kubectl logs <pod-name> --tail=50, при перезапусках - с флагом --previous.
- События кластера: kubectl get events -A --sort-by=.metadata.creationTimestamp.
- Политика внутреннего трафика: kubectl get svc <service-name> -o jsonpath='{.spec.internalTrafficPolicy}'. Значение Local требует локальных эндпоинтов на узле-источнике.
Профилактика проще, чем разбор аварии. Держите метки в Deployment и Service синхронными, задавайте readinessProbe с реалистичным initialDelaySeconds, следите за показателем доступных адресов эндпоинтов в kube-state-metrics и документируйте каждую NetworkPolicy прямо в репозитории. Готовые манифесты Deployment, Service и Ingress с разбором типичных ошибок вы найдёте в руководстве по деплою сервисов в Kubernetes, а порядок проверки сетевых политик и kubelet описан в материале про аудит безопасности кластера.
Итог по алгоритму: пустые endpoints отправляют вас к селектору, namespace и readiness-пробам; заполненные endpoints при отсутствии ответа ведут к targetPort, NetworkPolicy, DNS и internalTrafficPolicy. Начните с kubectl get endpoints и kubectl describe pod: эти две команды в большинстве случаев сразу указывают, какой из десяти пунктов чек-листа нужно закрыть.