Выбор типа сервиса Kubernetes: дерево решений и практические сценарии | AdminWiki

Выбор типа сервиса Kubernetes: дерево решений и практические сценарии

30 сентября 2026 9 мин. чтения

Зачем выбирать тип сервиса осознанно

Короткий ответ: внутренним сервисам нужен ClusterIP, публичным HTTP/HTTPS-сервисам - Ingress или Gateway API, TCP/UDP или статический IP - LoadBalancer, кластеру без облачного контроллера - NodePort или MetalLB, базам данных и StatefulSet - headless-сервис, внешним зависимостям - ExternalName. Дальше объясняем, почему выбор стоит делать осознанно, а не по привычке.

Тип Service определяет сетевую доступность pod'ов, уровень обработки трафика, стоимость облачных ресурсов и поверхность атаки. kube-proxy на каждой ноде транслирует правила Service в iptables или IPVS, поэтому выбранный тип меняет маршрут пакетов, точку терминации TLS и то, что видят внешние сканеры портов.

Цена ошибки видна по ресурсам, которые создаёт каждый тип. ClusterIP заводит виртуальный IP, доступный только внутри кластера, и не порождает внешних сущностей. NodePort открывает порт из диапазона 30000-32767 на каждой ноде, и обращение к любой ноде кластера доходит до сервиса. LoadBalancer заказывает внешний балансировщик у облачного провайдера, который тарифицируется по часам работы и за обработанный трафик. Ingress без установленного контроллера (Ingress NGINX, Traefik, Envoy Gateway) не публикует ничего: ресурс просто лежит в кластере.

Перед правками посмотрите, что уже открыто наружу:

kubectl get svc -A

Колонка TYPE показывает, где уже оплачиваются балансировщики и какие NodePort слушают ноды. Типовые последствия неверного выбора: счёт за облако растёт из-за лишних LoadBalancer, а сервисы, которым нужен только внутренний доступ, оказываются видны из интернета. Разницу между ClusterIP, NodePort и LoadBalancer с готовыми манифестами мы разбирали в руководстве по выбору типа Service.

Дерево решений: от вопроса к типу сервиса

Алгоритм покрывает большинство рабочих задач. Идите по вопросам сверху вниз и останавливайтесь на первом подходящем типе.

  1. Нужен доступ извне кластера? Нет - ClusterIP.
  2. Нужна L7-маршрутизация по HTTP/HTTPS (host, path, терминация TLS)? Да - Ingress или Gateway API.
  3. Есть облачный контроллер балансировщика? Да - LoadBalancer. Нет - NodePort или MetalLB.
  4. Нужна стабильная идентичность pod'ов? Да - headless-сервис с clusterIP: None.
  5. Нужен доступ к внешнему ресурсу по DNS-имени? Да - ExternalName или Service без селектора с EndpointSlice.

Вопрос 1: нужен ли доступ извне

ClusterIP - тип по умолчанию: его получает любой Service без явного поля spec.type. Виртуальный IP живёт только внутри кластера, наружу не публикуется ничего, счёт за облако не меняется. Проверить текущий тип, адрес и селектор:

kubectl get svc my-service
kubectl describe svc my-service

Внутренние микросервисы, Redis и другие кэши, брокеры сообщений, внутренние API и админки без внешнего входа закрываются ClusterIP. Имя формируется по схеме my-service.default.svc.cluster.local, короткая форма my-service работает внутри того же namespace. Балансировка идёт на уровне соединений: в режиме iptables pod выбирается один раз на соединение, а IPVS поддерживает более сложные алгоритмы балансировки нагрузки, чем iptables (наименьшая нагрузка, наименьшее число соединений, локальность, взвешенная и т.д.), что заметно при большом числе коротких подключений. Подробнее про виртуальные IP и режимы проксирования - в документации Kubernetes по Virtual IPs and Service Proxies, а про различия алгоритмов IPVS - в README модуля kube-proxy в режиме IPVS.

Вопрос 2: нужна ли L7-маршрутизация

LoadBalancer работает на L4: распределяет TCP/UDP-соединения и не читает заголовок Host или путь. Ingress и Gateway API работают на L7: маршрутизируют HTTP/HTTPS по host и path, терминируют TLS, перенаправляют HTTP на HTTPS. Если нужно api.example.com и admin.example.com на одном IP или /v1 и /v2 на разные бэкенды, берите Ingress либо Gateway API. Для TCP, MQTT, произвольного UDP и другого не-HTTP трафика остаётся LoadBalancer. Механику обоих уровней, манифесты и сравнение контроллеров (Ingress NGINX, Traefik, Envoy Gateway) мы разбирали в статье про маршрутизацию Service на L4 и Ingress/Gateway API на L7.

Gateway API - официальный проект Kubernetes, ориентированный на маршрутизацию L4 и L7, и семейство API-видов с роле-ориентированным дизайном: инфраструктурный администратор описывает GatewayClass и Gateway, разработчик - маршруты вроде HTTPRoute. Такой подход убирает зоопарк аннотаций, который различается у каждого контроллера. Ingress остаётся совместимым минимумом, поэтому для схемы «один домен, несколько путей» его часто хватает; при этом покрытие Ingress у конкретных managed-провайдеров стоит проверять отдельно. Описание семейства API-видов и ролевой модели приведено в документации Gateway API и на сайте проекта Gateway API.

Вопрос 3: есть ли облачный балансировщик

Service типа LoadBalancer перекладывает создание внешнего балансировщика на облачный контроллер: AWS (CLB, NLB), GCP, Azure, Yandex Managed Kubernetes и другие платформы делают это автоматически. Балансировщик тарифицируется по часам работы и за обработанный трафик, а ставки отличаются по провайдеру и региону, поэтому перед публикацией десятка сервисов сверьтесь с прайс-листом облака.

В bare-metal и on-prem кластере облачного контроллера нет: Service типа LoadBalancer останется в статусе Pending и внешнего адреса не получит. Варианты: NodePort с портом из диапазона 30000-32767 на каждой ноде или MetalLB в режиме L2/BGP, который выдаёт адреса из вашего пула. Диапазон портов NodePort по умолчанию (30000-32767) зафиксирован в документации Service и подтверждается в документации Cilium про режим без kube-proxy. Для статического IP у облачного балансировщика используйте аннотации провайдера; конкретные значения аннотаций сверяйте с документацией вашего облачного контроллера, так как они меняются между версиями.

Сравнительная таблица типов сервисов

Тип или ресурсУровеньДоступНужно облакоСтоимостьБезопасностьТипичный сценарий
ClusterIPL4Внутри кластераНетНетПериметр не расширяетсяВнутренний микросервис, кэш, БД
Headless, clusterIP: NoneL4Внутри кластераНетНетПериметр не расширяетсяStatefulSet, PostgreSQL, Kafka
NodePortL4Снаружи через любую нодуНетПрямых затрат нетОткрытые порты, TLS отсутствуетОтладка, bare-metal
LoadBalancerL4Снаружи по внешнему IPДа, либо MetalLBОплата за час и трафикЗависит от TLS и политикTCP/UDP, gRPC, статический IP
ExternalNameDNSCNAME на внешнее имяНетНетПроксирования нетВнешняя БД, внешний API
IngressL7Снаружи через контроллерНет, нужен контроллерЗависит от точки входаTLS, HTTP-маршрутизацияПубличный HTTP API, сайт
Gateway APIL7Снаружи через GatewayНет, нужны CRDЗависит от реализацииTLS, разделение ролейМультитенантный HTTP

Ingress и Gateway API не входят в список типов Service: это отдельные ресурсы, которые направляют трафик на сервисы. ExternalName тоже особый случай: DNS-запись CNAME, а не прокси, пакеты идут напрямую к внешнему адресу.

Практические сценарии с рекомендациями

Для каждого сценария ниже: рекомендуемый тип, манифест и команда проверки.

Внутренний микросервис: ClusterIP

Рекомендация - ClusterIP. Указывайте тип и порты явно, не полагайтесь на значения по умолчанию в проде.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
    - name: http
      port: 80
      targetPort: 8080

Поле port это порт сервиса, targetPort это порт контейнера. Приложение слушает 8080, а соседние сервисы обращаются по адресу http://my-service:80. Проверка:

kubectl get endpoints my-service
kubectl run tmp-curl --rm -it --image=curlimages/curl -- curl -sS http://my-service.default.svc.cluster.local

Если список endpoints пуст, сверьте селектор с метками pod'ов (kubectl get pods --show-labels). Readiness probe обязателен, иначе трафик уйдёт на неготовый pod.

Публичный API: Ingress или LoadBalancer

HTTP/HTTPS API с несколькими доменами или путями закрывает Ingress. Один контроллер и один балансировщик обслуживают десятки сервисов, поэтому счёт за облако не растёт с каждым новым эндпоинтом.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

Секрет api-tls должен существовать заранее: его выдаёт cert-manager или вы создаёте его вручную. Варианты терминации TLS (Ingress, service mesh, внешний балансировщик) и их влияние на безопасность и производительность разобраны в материале про TLS-терминацию в Kubernetes.

LoadBalancer выбирайте, когда API говорит по TCP или нужен статический IP для whitelisting у клиентов:

apiVersion: v1
kind: Service
metadata:
  name: tcp-api
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  selector:
    app: tcp-api
  ports:
    - port: 443
      targetPort: 8443

Режим externalTrafficPolicy: Local сохраняет IP клиента, но сужает выбор pod'ов до нод, где эти pod'ы запущены. Проверка: kubectl get svc tcp-api до появления EXTERNAL-IP и curl -I https://api.example.com после настройки DNS.

База данных: headless service

Для StatefulSet нужен headless-сервис с clusterIP: None. Виртуального IP он не даёт, зато выдаёт каждому pod'у стабильное DNS-имя.

apiVersion: v1
kind: Service
metadata:
  name: my-db
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - name: postgres
      port: 5432
      targetPort: 5432

Поды получают имена вида my-db-0.my-db.default.svc.cluster.local, дальше my-db-1 и my-db-2. Репликация MySQL, кластер PostgreSQL и кворум etcd адресуют участников по этим именам, а не по случайным IP. Проверка изнутри pod'а:

nslookup my-db-0.my-db.default.svc.cluster.local

DNS возвращает адреса всех подов, поэтому приложение само выбирает реплику. Для записи направляйте трафик на конкретный pod или поднимите отдельный ClusterIP поверх роли leader.

Доступ к внешнему ресурсу: ExternalName

Обращение к внешней БД или API можно абстрагировать через ExternalName.

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com

ExternalName - это внешняя ссылка, которую механизмы обнаружения возвращают как псевдоним для сервиса (например, DNS CNAME-запись), без проксирования. Клиент внутри кластера обращается к external-db, DNS возвращает CNAME на db.example.com. Трафик идёт напрямую, поэтому ExternalName не скрывает IP, не проверяет доступность и не терминирует TLS. Зато смена внешнего адреса делается правкой одного поля. Нужны health checks или фильтрация по IP, создайте Service без селектора и привяжите EndpointSlice с конкретными адресами вручную. Формулировка про внешнюю ссылку и отсутствие проксирования приведена в справочнике Service v1.

Частые ошибки выбора и их последствия

  1. NodePort для продакшн-трафика. Порт из диапазона 30000-32767 слушает на каждой ноде, TLS нет, правила Ingress и WAF не применяются. Внешние сканеры находят такие порты быстро. Решение: Ingress или LoadBalancer, а NodePort оставьте для отладки и bare-metal за MetalLB.
  2. LoadBalancer для каждого сервиса. Каждый объект тарифицируется отдельно, десять микросервисов дают десять счетов и десять внешних IP. Решение: один Ingress или Gateway с маршрутизацией по host и path.
  3. Ingress без TLS. Токены и cookies уходят открытым текстом, HSTS не включён. Решение: терминация TLS на контроллере, редирект с HTTP и заголовок Strict-Transport-Security.
  4. ExternalName для внутренних сервисов. CNAME на имя, которого нет в DNS кластера, даёт ошибку резолва, а endpoints у такого сервиса не появляются. Решение: ClusterIP для внутренних обращений.
  5. Отсутствие NetworkPolicy. По умолчанию все pod'ы видят друг друга, поэтому компрометация одного контейнера открывает путь к базе. Решение: default deny и явные разрешения по меткам.
  6. Селектор не совпал с метками. Сервис создан, endpoints пусты, клиенты получают 503 без внятной причины. Решение: kubectl get endpoints и kubectl describe svc как первые шаги диагностики.
  7. Нет readiness probe. Неготовый pod получает трафик сразу после старта. Решение: readinessProbe в манифесте, тогда pod попадёт в endpoints только после успешной проверки.

Чек-лист выбора типа сервиса

  1. Нужен доступ извне? Нет - ClusterIP.
  2. Нужна L7-маршрутизация по host и path? Да - Ingress или Gateway API.
  3. Есть облачный контроллер балансировщика? Да - LoadBalancer. Нет - NodePort или MetalLB.
  4. Трафик не HTTP (TCP, UDP, MQTT)? Да - LoadBalancer или NodePort.
  5. Нужна стабильная идентичность pod'ов? Да - headless-сервис с clusterIP: None.
  6. Нужно обратиться к внешнему ресурсу? Да - ExternalName или Service без селектора с EndpointSlice.
  7. TLS терминируется на входе в кластер? Нет - добавьте секрет и редирект до публикации.
  8. NetworkPolicy описывает, кто к кому ходит? Нет - начните с default deny в namespace.
  9. Endpoints непустые? Проверьте перед релизом, иначе получите 503.

Команды для проверки:

kubectl get svc -A
kubectl describe svc my-service
kubectl get endpointslices -l kubernetes.io/service-name=my-service
kubectl run tmp-curl --rm -it --image=curlimages/curl -- curl -sS http://my-service
curl -I https://api.example.com

Начните с аудита: выполните kubectl get svc -A, отметьте все NodePort и LoadBalancer, для каждого сервиса ответьте на три вопроса дерева. Если сервис не должен быть виден из интернета, а его тип не ClusterIP, исправьте манифест до следующего релиза.

Поделиться: