← назад к разделу

Сетевые вопросы — главный источник «мистики» для разработчика в 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-имя не отменяет.