Сетевые вопросы — главный источник «мистики» для разработчика в Kubernetes: сервис работает, но недоступен; адрес резолвится, но соединение зависает; после деплоя соседняя команда ловит 503. Вся эта мистика разбирается на четыре механизма: типы Service, DNS, Ingress и endpoints. Плюс два инструмента контроля — NetworkPolicy и service mesh.
Базовая модель
В Kubernetes у каждого пода — собственный IP, и все поды видят друг друга напрямую, без NAT, в плоской сети (как именно — забота CNI-плагина, разработчику это знание не нужно). Проблема не в достижимости, а в изменчивости: поды и их IP живут минуты-дни. Все механизмы ниже решают одну задачу — дать изменчивому множеству подов постоянные имена.
Типы Service
- ClusterIP (дефолт) — виртуальный IP, доступный только внутри кластера. Для межсервисного общения — он, и почти всегда только он.
- NodePort — открывает порт на каждой ноде кластера. Технический примитив, на котором строится LoadBalancer; в проде напрямую почти не используется.
- LoadBalancer — заказывает внешний балансировщик у облака. Дорого и расточительно вешать на каждый сервис — обычно один LoadBalancer стоит перед Ingress-контроллером, а сервисы наружу публикуются через Ingress.
- Headless (
clusterIP: None) — без виртуального IP: DNS-имя резолвится сразу в список IP подов. Нужен, когда клиент хочет знать каждый экземпляр: клиенты Kafka, кластеры БД, peer-to-peer протоколы, StatefulSet-ы.
DNS: как сервисы зовут друг друга
Каждый Service получает DNS-имя имя.namespace.svc.cluster.local. Внутри своего namespace достаточно короткого http://order-service, через namespace — http://order-service.payments.
app:
clients:
limits:
base-url: http://limits.risk
connect-timeout: 1s
read-timeout: 2s
Тот самый http://limits/api/v1/limits/check из разбора батч-процессинга — это оно: имя Service вместо адресов. Правила гигиены: адреса соседей — всегда DNS-имена Service, никогда не IP подов; и каждое имя в конфиге идёт в паре с таймаутами — DNS-имя не делает соседа надёжным.
Endpoints: где сеть встречается с readiness
За каждым Service живёт список endpoints — IP подов, прошедших selector и readiness probe. Это центральная связка сети и жизненного цикла приложения:
- Под стартует и ещё прогревается → readiness красная → его нет в endpoints → трафик не идёт.
- Под перегружен и readiness замигала → под выпадает из endpoints до восстановления.
- При rolling update старые поды выводятся из endpoints до остановки — поэтому корректный shutdown не теряет запросы (механика).
Первая команда при «сервис есть, но не отвечает»:
kubectl -n payments get endpoints order-service
Пустой список endpoints при живых подах = все реплики не readiness — и это уже конкретный вопрос к probes, а не «сеть глючит».
Ingress и Gateway API
Ingress — декларация «какой host/path на какой Service», исполняемая контроллером (nginx, traefik, облачные):
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 }
TLS терминируется здесь; внутрь кластера трафик идёт уже HTTP (или mTLS, если есть mesh). Что нужно знать про Gateway API: это преемник Ingress — те же задачи, но с ролями (инфраструктура владеет Gateway, команды — своими HTTPRoute), поддержкой TCP/gRPC и без зоопарка vendor-аннотаций, в которые упирается Ingress. Новые платформы строят на нём; существующие Ingress-ы продолжают работать.
NetworkPolicy: кто с кем может говорить
По умолчанию в кластере можно всё: любой под — к любому. Для среды с платёжными сервисами это неприемлемо, и ответ — 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
Практика: deny-by-default на namespace + явные разрешения по потребителям. Для разработчика это означает, что «не могу достучаться до соседа» — иногда не баг, а политика: проверяется kubectl describe networkpolicy раньше, чем пишется тикет платформе.
Service mesh: когда сети нужно больше
Mesh (Istio, Linkerd) добавляет sidecar-прокси к каждому поду и переносит в инфраструктуру то, что иначе живёт в коде: mTLS между всеми сервисами, ретраи и таймауты на уровне сети, канареечные веса трафика, единая телеметрия вызовов.
Когда оправдан: десятки сервисов, требование mTLS everywhere, канарейки как регулярная практика. Когда нет: однажды установленный «на вырост» mesh — заметный налог на сложность каждой отладки (ещё один прокси в каждом запросе). И принципиальное правило при его наличии: ретраи конфигурируются в одном месте — либо mesh, либо приложение. Ретрай в приложении × ретрай в mesh = лавина повторов на деградирующего соседа, ровно то, от чего resilience-паттерны защищают.
Антипаттерны
| Антипаттерн | Чем кончается | Что взамен |
|---|---|---|
| Обращение по IP пода | Ломается при первом пересоздании | DNS-имя Service |
| LoadBalancer на каждый сервис | Счёт от облака и зоопарк точек входа | Один LB перед Ingress-контроллером |
| NodePort в проде «потому что быстро» | Нестандартные порты, обход TLS и маршрутизации | Ingress / Gateway API |
| Жёсткий FQDN с cluster.local в коде | Не переживает смену кластера/домена | Короткие имена + namespace в конфиге |
| Сервис без таймаутов «он же рядом» | Зависший сосед вешает вызывающего | Таймауты на каждый клиент |
| Ретраи и в mesh, и в приложении | Усиление нагрузки при деградации | Одно место для retry-политики |
Что почитать дальше
- Fundamentals — Service и путь запроса в общей модели.
- Spring Boot в Kubernetes — readiness, которая управляет endpoints.
- Эксплуатация и отладка — диагностика цепочки Ingress → Service → endpoints.
- Паттерны отказоустойчивости — таймауты и ретраи, которые DNS-имя не отменяет.