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

Сеть в 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 в продакшнеНестандартные порты, обход TLSIngress / 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-имя не заменяет.