Сеть в Kubernetes часто кажется мистикой: сервис есть, а не отвечает; адрес резолвится, но соединение зависает; после деплоя соседняя команда ловит 503. Всё это объясняется четырьмя механизмами: Service, DNS, Ingress, NetworkPolicy. Разберём их по очереди.
Почему IP пода нельзя использовать напрямую
В Kubernetes у каждого пода свой IP-адрес, и все поды видят друг друга в одной плоской сети — без NAT. Казалось бы, просто обращайся по IP и живи.
Проблема: поды недолговечны. Перезапуск, обновление, сбой ноды — и под поднимается с новым IP. Жёстко прописать адрес в конфиге — значит сломать систему при первом же пересоздании.
Именно для этого и существует Service: абстракция, которая даёт постоянное сетевое имя изменчивому множеству подов.
Типы Service: что выбрать и когда
Service описывается в YAML и указывает, какие поды он объединяет (через selector с метками) и как они доступны снаружи.
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: payments
spec:
selector:
app: order # объединяет все поды с меткой app=order
ports:
- port: 80
targetPort: 8080
Существует четыре типа:
ClusterIP — тип по умолчанию. Даёт виртуальный IP, доступный только внутри кластера. Для связи одного микросервиса с другим — это он, и почти всегда только он.
NodePort — открывает порт на каждой ноде кластера (30000–32767). Технический примитив; в продакшне напрямую не используется, но на нём строится тип LoadBalancer.
LoadBalancer — заказывает внешний балансировщик у облачного провайдера и подключает его к сервису. Дорого вешать на каждый сервис: обычно один LoadBalancer стоит перед Ingress-контроллером, а сервисы публикуются через Ingress (об этом ниже).
Headless (clusterIP: None) — без виртуального IP. DNS-имя резолвится сразу в список IP конкретных подов. Нужен, когда клиенту важно знать каждый экземпляр отдельно: кластеры баз данных, Kafka-клиенты, StatefulSet-ы.
DNS: как сервисы находят друг друга по имени
Каждый Service автоматически получает DNS-имя:
имя-сервиса.namespace.svc.cluster.local
Внутри того же namespace достаточно короткого имени:
http://order-service
Из другого namespace нужен namespace-префикс:
http://order-service.payments
Это и есть правильный способ обращаться к соседнему сервису — никогда не IP, всегда DNS-имя. И всегда в паре с таймаутами: DNS-имя не делает соседа надёжным.
app:
clients:
order:
base-url: http://order-service.payments
connect-timeout: 1s
read-timeout: 3s
Endpoints: где сеть встречается с readiness
За каждым Service Kubernetes поддерживает список endpoints — это реальные IP подов, которые прошли две проверки: совпадение по selector-меткам и успешный readiness probe.
Это ключевая связка:
- Под запустился, но ещё прогревается → readiness не проходит → его нет в endpoints → трафик не идёт к нему.
- Под перегружен, readiness провалилась → он выпадает из endpoints до восстановления.
- При rolling update старые поды выводятся из endpoints до остановки — поэтому правильный graceful shutdown не теряет запросы.
Первое, что нужно проверить, когда «сервис есть, но не отвечает»:
kubectl -n payments get endpoints order-service
Пустой список при живых подах означает: все реплики не проходят readiness probe. Это уже конкретный вопрос к probes, а не «что-то с сетью».
Ingress: один вход для всех сервисов
Выпускать каждый сервис наружу через LoadBalancer — расточительно: отдельный балансировщик и отдельный IP для каждого. Решение — один LoadBalancer перед Ingress-контроллером, а маршрутизация описывается в Ingress-ресурсах.
Ingress — это декларация «какой host/path куда направлять»:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
spec:
tls:
- hosts: [api.example.com]
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port: { number: 80 }
- path: /payments
pathType: Prefix
backend:
service:
name: payment-service
port: { number: 80 }
TLS терминируется на уровне Ingress; внутрь кластера трафик идёт как HTTP (или через mTLS, если есть service mesh).
Ingress-контроллер — отдельный компонент, который нужно установить (nginx, Traefik, облачные варианты). Сам по себе Ingress-ресурс без контроллера ничего не делает.
Gateway API — преемник Ingress
Gateway API — более новый стандарт для той же задачи. Главные отличия:
- чёткое разделение ролей: платформа управляет
Gateway(точкой входа), команды — своимиHTTPRoute; - поддержка TCP, gRPC и WebSocket из коробки;
- нет зоопарка vendor-аннотаций, в который упирается Ingress при сложных сценариях.
Новые платформы строят на Gateway API; существующие Ingress-ресурсы продолжают работать.
NetworkPolicy: кто с кем может говорить
По умолчанию в Kubernetes любой под может обратиться к любому. Для среды с платёжными сервисами это неприемлемо.
NetworkPolicy — декларативный файрвол на уровне подов. Описывает, какой трафик разрешён, всё остальное — запрещено:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: limits-ingress
namespace: risk
spec:
podSelector:
matchLabels: { app: limits }
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: { team: payments }
ports:
- port: 8080
Этот манифест говорит: к подам app=limits в namespace risk разрешён входящий трафик только из namespace с меткой team=payments, и только на порт 8080.
Хорошая практика — deny-by-default на namespace, затем явные разрешения по потребителям. Если не можете достучаться до соседнего сервиса — kubectl describe networkpolicy проверяется раньше, чем пишется обращение к платформенной команде.
Service mesh: когда нужно больше
Service mesh (Istio, Linkerd) — дополнительный уровень: рядом с каждым подом запускается прокси-контейнер, который перехватывает весь трафик. Это позволяет получить без изменения кода:
- mTLS между всеми сервисами автоматически;
- ретраи и таймауты на уровне сети;
- канареечное распределение трафика по весам;
- единую телеметрию всех вызовов.
Mesh оправдан при десятках сервисов и регулярной практике канареечных выкаток. При небольшом числе сервисов он добавляет заметную сложность в отладку — в каждом запросе появляется ещё один прокси.
Важное правило при использовании mesh: ретраи настраиваются в одном месте — либо в mesh, либо в приложении, не в обоих. Ретрай в приложении плюс ретрай в mesh — это лавина повторов при деградации соседнего сервиса.
Частые ошибки
| Ошибка | Что происходит | Как правильно |
|---|---|---|
| Обращение по IP пода | Ломается при первом пересоздании | DNS-имя Service |
LoadBalancer на каждый сервис | Лишние расходы, зоопарк точек входа | Один LB перед Ingress-контроллером |
NodePort в продакшне | Нестандартные порты, обход TLS | Ingress / Gateway API |
Жёсткий FQDN с cluster.local в коде | Ломается при смене кластера | Короткое имя + namespace в конфиге |
| Клиент без таймаутов «он же рядом» | Зависший сосед вешает вызывающего | Таймаут на каждый клиент |
| Ретраи и в mesh, и в приложении | Лавина запросов при деградации | Одно место для retry-политики |
Коротко
- У каждого пода свой IP, но IP поды меняют — поэтому нужен
Serviceкак постоянное имя. ClusterIP— для связи сервисов внутри кластера (основной тип).LoadBalancer— для входа снаружи, лучше один перед Ingress.Headless— когда клиенту важен каждый экземпляр отдельно.- DNS-имя сервиса:
имя.namespace.svc.cluster.local. Внутри namespace — простоимя. - Endpoints — реальные IP подов за Service. Под попадает туда только если прошёл readiness probe. Пустой endpoints при живых подах — смотрите на probes.
- Ingress маршрутизирует внешний трафик по host/path к нужным Service. Gateway API — более новый стандарт с разделением ролей.
- NetworkPolicy — файрвол между подами. По умолчанию всё открыто; deny-by-default + явные разрешения — хорошая практика.
- Service mesh переносит ретраи, таймауты и mTLS в инфраструктуру. Ретраи — либо в mesh, либо в приложении, не в обоих.
Что почитать дальше
- K8s Fundamentals — pod, Deployment, Service в общей модели кластера.
- Spring Boot в Kubernetes — readiness probe, управляющая endpoints.
- Эксплуатация и отладка — диагностика цепочки Ingress → Service → endpoints → pod.
- Паттерны отказоустойчивости — таймауты и ретраи, которые DNS-имя не заменяет.