🐳 Раздел 14 · Вопрос #12

Какие типы Service существуют (ClusterIP, NodePort, LoadBalancer)

В Kubernetes существует 4 основных типа Service, которые определяют уровень сетевой доступности приложения (будет ли оно видно только внутри кластера или доступно пользователям...


🟢 Junior Level

30-секундный ответ

В Kubernetes существует 4 основных типа Service, которые определяют уровень сетевой доступности приложения (будет ли оно видно только внутри кластера или доступно пользователям из интернета):

  1. ClusterIP (по умолчанию): сервис получает стабильный виртуальный IP, доступный строго внутри кластера. Внешний трафик из интернета сюда попасть не может. Используется для 95% микросервисов (связь бэкенда с базой данных, кэшем, очередями).
  2. NodePort: открывает выделенный статический порт (из диапазона 30000–32767) на каждом физическом сервере (Node) кластера. Позволяет достучаться до приложения извне по адресу http://<IP-любой-ноды>:NodePort.
  3. LoadBalancer: автоматически заказывает у внешнего облачного провайдера (AWS, GCP, Azure, Yandex Cloud) полноценный физический балансировщик нагрузки с публичным IP-адресом.
  4. ExternalName: специальный тип без селектора и без проксирования трафика. Возвращает DNS CNAME-запись, перенаправляя запросы на внешний домен (например, db.external-provider.com).

Наглядная аналогия «Матрёшка»

Архитектурно типы сервисов вложены друг в друга как русская матрёшка:

  • ClusterIP — самая маленькая внутренняя матрёшка (базовый маршрут к подам).
  • NodePort — средняя матрёшка: он оборачивает ClusterIP, пробрасывая порт на хосты.
  • LoadBalancer — самая большая внешняя матрёшка: он заказывает внешний балансировщик, направляет трафик на NodePort, который передаёт его на ClusterIP, а тот балансирует на Pod!

Сводная таблица для быстрого понимания

| Тип сервиса | Внешний IP | Доступен из интернета | Основное назначение | | :— | :—: | :—: | :— | | ClusterIP | ❌ Нет | ❌ Нет | Межсервисное общение (Back $\to$ DB) | | NodePort | ❌ Нет (порт ноды) | 🟡 Да (по IP нод) | Тестовые стенды, прямое подключение | | LoadBalancer | 🟢 Да (Public IP) | 🟢 Да | Продакшен-доступ для внешних клиентов | | ExternalName | ❌ Нет (DNS CNAME) | ❌ Нет | Псевдоним для внешних баз данных |


🟡 Middle Level

Детальная конфигурация и спецификация

1. ClusterIP (Внутрикластерный стандарт)

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  type: ClusterIP # Можно опустить, выставляется по умолчанию
  selector:
    app: order-service
  ports:
  - port: 80         # Порт сервиса
    targetPort: 8080 # Порт контейнера Java Spring Boot

2. NodePort (Доступ через порты хостов)

apiVersion: v1
kind: Service
metadata:
  name: debug-service
spec:
  type: NodePort
  selector:
    app: order-service
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 31234 # Диапазон: 30000–32767 (если опустить, K8s выберет случайный)

Запрос на адрес 192.168.1.10:31234 перенаправляется на сервис, даже если на этой конкретной ноде вообще не запущено ни одного пода order-service.

3. LoadBalancer (Интеграция с облачной инфраструктурой)

apiVersion: v1
kind: Service
metadata:
  name: public-gateway
spec:
  type: LoadBalancer
  selector:
    app: api-gateway
  ports:
  - port: 443
    targetPort: 8443

Cloud Controller Manager перехватывает событие создания такого сервиса и через облачный API поднимает AWS NLB или Google Cloud Network Load Balancer.

4. ExternalName (Внешний CNAME редирект)

apiVersion: v1
kind: Service
metadata:
  name: postgres-db
spec:
  type: ExternalName
  externalName: mydb-cluster.rds.amazonaws.com # CNAME запись в CoreDNS

Java-приложение подключается к jdbc:postgresql://postgres-db:5432/mydb, а CoreDNS возвращает CNAME на облачную AWS RDS.


Архитектурный выбор: LoadBalancer vs Ingress

В продакшене каждый облачный LoadBalancer стоит денег (от 15 до 30 долларов в месяц за каждый экземпляр в AWS/GCP):

  • Антипаттерн: делать 20 микросервисов с типом LoadBalancer ($20 \times $25 = $500$ ежемесячно только за сетевые балансировщики).
  • Best Practice (Паттерн Ingress):
    1. Создаётся ровно один сервис типа LoadBalancer, смотрящий в интернет.
    2. За ним работает Ingress Controller (Nginx, Traefik, Envoy).
    3. Все остальные микросервисы объявляются как дешёвые и безопасные ClusterIP.
    4. Ingress распределяет входящий HTTP/HTTPS трафик по путям (/api/orders, /api/users) на внутренние ClusterIP сервисы.

🔴 Senior Level

Политика маршрутизации: externalTrafficPolicy: Cluster vs Local

При использовании сервисов типов NodePort и LoadBalancer критически важно понимать поведение параметра spec.externalTrafficPolicy:

[ externalTrafficPolicy: Cluster ]                 [ externalTrafficPolicy: Local ]
          Клиент                                             Клиент
            │                                                  │
            ▼                                                  ▼
     [ Node 1 (без пода) ]                             [ Node 1 (без пода) ]
            │ (SNAT + Extra Hop)                               ❌ Трафик сброшен!
            ▼
     [ Node 2 (с подом) ]                              [ Node 2 (с подом) ]
                                                               🟢 Обработка без SNAT!

1. externalTrafficPolicy: Cluster (по умолчанию)

  • Трафик, пришедший на любую ноду кластера, может быть переслан через внутреннюю оверлейную сеть на совершенно другую ноду, где реально запущен под.
  • Плюсы: идеальная равномерная балансировка нагрузки между подами.
  • Минусы:
    1. Лишний сетевой переход (Extra Network Hop): повышает сетевую задержку.
    2. Потеря Client IP (SNAT): ядро выполняет подмену исходного адреса (Source NAT). Java-приложение видит IP-адрес внутренней ноды вместо реального публичного IP клиента, что ломает аудит безопасности и rate-limiting.

2. externalTrafficPolicy: Local

  • Трафик направляется только на поды, работающие на этой же самой ноде. Если подов на ноде нет, трафик отбрасывается.
  • Плюсы:
    1. Сохраняется реальный исходный IP клиента (Source IP Preservation) без SNAT.
    2. Отсутствует второй межсерверный hop (минимальные задержки).
  • Минусы: риск неравномерной загрузки подов при перекосе трафика между нодами. Облачный балансировщик обязан отправлять health-проверки на порт healthCheckNodePort.

Частные облачные сети (Internal Load Balancer)

По умолчанию сервис type: LoadBalancer заказывает публичный IP. Для создания балансировщика, доступного только внутри корпоративной VPC, используют аннотации облачного провайдера:

metadata:
  name: internal-service
  annotations:
    # Для AWS:
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"
    # Для GCP:
    networking.gke.io/load-balancer-type: "Internal"

On-Premise балансировка: MetalLB

В локальных датацентрах (Bare-Metal K8s) нет API облачного провайдера, и сервис LoadBalancer навсегда зависает в статусе <pending>. Для решения этой проблемы разворачивают MetalLB:

  1. Layer 2 Mode (ARP/NDP): один из серверов кластера отвечает на ARP-запросы для выделенного виртуального IP из пула. При падении сервера другой узел перехватывает IP через Gratuitous ARP.
  2. BGP Mode: узлы кластера устанавливают BGP-сессии с физическими датацентровыми маршрутизаторами (Cisco, Juniper) и анонсируют маршруты к сервисам с поддержкой Equal-Cost Multi-Path (ECMP) балансировки.

4 Tricky Questions

1. Почему при создании сервиса type: LoadBalancer Kubernetes всё равно выделяет для него NodePort и ClusterIP? Можно ли отключить выделение NodePort?

Ответ: Архитектурно облачный балансировщик (например, классический AWS ELB) не имеет прямой маршрутизации в оверлейную сеть подов (10.244.0.0/16). Он принимает внешний трафик и пересылает его на физические IP серверов кластера на выделенный NodePort. Далее kube-proxy через правила ClusterIP маршрутизирует пакет до пода. Отключение NodePort: начиная с Kubernetes 1.20, доступно свойство spec.allocateLoadBalancerNodePorts: false. Если облачный контроллер поддерживает прямую маршрутизацию пакетов в поды (например, AWS VPC CNI с TargetGroupBinding в режиме IP mode), выделение портов NodePort можно отключить, экономя пул портов кластера.


2. Почему обращение к внешнему HTTPS-сервису через type: ExternalName часто падает с ошибкой валидации SSL/TLS сертификата в Java (SSLHandshakeException)?

Ответ: Тип ExternalName работает исключительно на уровне DNS: он не проксирует TCP-соединение, а лишь возвращает клиенту CNAME-запись (например, mydb.internal $\to$ custom-service.eu-west-1.rds.amazonaws.com). Когда Java-клиент устанавливает защищённое TLS-соединение, он отправляет заголовок SNI (Server Name Indication) с исходным именем хоста, к которому подключался (mydb.internal). Удалённый сервер возвращает SSL-сертификат, выписанный на домен *.rds.amazonaws.com. JVM сравнивает запрошенное имя mydb.internal с маской сертификата, фиксирует несовпадение имён и выбрасывает фатальное исключение javax.net.ssl.SSLPeerUnverifiedException: Certificate for <mydb.internal> doesn't match any of the subject alternative names.


3. Как директива externalTrafficPolicy: Local защищает от утечки Client IP и почему она требует настройки healthCheckNodePort?

Ответ: В стандартном режиме Cluster сетевой пакет, пришедший на Node 1, пересылается на Node 2. Чтобы Node 2 знала, куда отправить ответный пакет, ядро на Node 1 выполняет Source NAT (SNAT), заменяя реальный IP клиента на IP-адрес Node 1 (клиентский IP теряется). В режиме Local ядро запрещает пересылку между нодами: пакет передаётся поду только локально, благодаря чему SNAT не требуется, и настоящий Client IP сохраняется. Но если на Node 1 нет подов этого сервиса, пакет будет отброшен. Чтобы облачный балансировщик знал, на какие именно ноды можно слать трафик, Kubernetes автоматически открывает служебный порт healthCheckNodePort. Балансировщик непрерывно опрашивает этот порт: ноды с подами отвечают HTTP 200, а ноды без подов — HTTP 503.


4. Можно ли внутри кластера обратиться к сервису NodePort по его внутреннему ClusterIP, игнорируя выделенный порт 30000–32767?

Ответ: Да, абсолютно. Каждый сервис типа NodePort одновременно является полноценным сервисом типа ClusterIP. Внутри кластера другие микросервисы могут обращаться к нему по его DNS-имени и стандартному внутреннему порту spec.ports.port (например, http://my-service:80), минуя громоздкие внешние порты nodePort (31234). Выделенный порт nodePort открывается только для входящего трафика с внешних физических интерфейсов нод.


🎯 Шпаргалка для интервью

Главное о типах Service

  • ClusterIP: дефолтный виртуальный IP, доступен только внутри кластера.
  • NodePort: открывает порт 30000–32767 на всех серверах; трафик идет через NodePort $\to$ ClusterIP $\to$ Pod.
  • LoadBalancer: заказывает внешний IP в облаке (AWS/GCP); в on-premise требует MetalLB.
  • ExternalName: чистый DNS CNAME алиас на внешнюю систему (без виртуального IP и без селектора).
  • Ingress vs LoadBalancer: 1 Ingress (1 LoadBalancer) обслуживает десятки сервисов ClusterIP, экономя бюджет.
  • externalTrafficPolicy: Local: сохраняет реальный Client IP и убирает лишний hop, но отключает межсерверную балансировку.

Сравнение свойств сетевого доступа

| Тип | Выделяет ClusterIP? | Выделяет NodePort? | Требует интеграции с облаком? | | :— | :—: | :—: | :—: | | ClusterIP | 🟢 Да | ❌ Нет | ❌ Нет | | NodePort | 🟢 Да | 🟢 Да | ❌ Нет | | LoadBalancer | 🟢 Да | 🟢 Да (по умолчанию)| 🟢 Да (Cloud Provider / MetalLB) | | ExternalName | ❌ Нет | ❌ Нет | ❌ Нет (только CoreDNS) |

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «В продакшене мы для каждого бэкенд-сервиса создаём LoadBalancer с публичным IP» — колоссальный перерасход денег и дыра в безопасности; нужен Ingress + ClusterIP.
  • ❌ «NodePort открывает порт только на том сервере, где физически запущен под» — NodePort открывается на ВСЕХ серверах кластера без исключения.
  • ❌ «ExternalName умеет балансировать TCP-пакеты на внешнюю БД» — ExternalName это только DNS-алиас CNAME, трафик идёт напрямую от клиента к внешнему хосту.

Связанные вопросы