Зачем выбирать тип сервиса осознанно
Короткий ответ: внутренним сервисам нужен 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.
Дерево решений: от вопроса к типу сервиса
Алгоритм покрывает большинство рабочих задач. Идите по вопросам сверху вниз и останавливайтесь на первом подходящем типе.
- Нужен доступ извне кластера? Нет - ClusterIP.
- Нужна L7-маршрутизация по HTTP/HTTPS (host, path, терминация TLS)? Да - Ingress или Gateway API.
- Есть облачный контроллер балансировщика? Да - LoadBalancer. Нет - NodePort или MetalLB.
- Нужна стабильная идентичность pod'ов? Да - headless-сервис с clusterIP: None.
- Нужен доступ к внешнему ресурсу по 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 у облачного балансировщика используйте аннотации провайдера; конкретные значения аннотаций сверяйте с документацией вашего облачного контроллера, так как они меняются между версиями.
Сравнительная таблица типов сервисов
| Тип или ресурс | Уровень | Доступ | Нужно облако | Стоимость | Безопасность | Типичный сценарий |
|---|---|---|---|---|---|---|
| ClusterIP | L4 | Внутри кластера | Нет | Нет | Периметр не расширяется | Внутренний микросервис, кэш, БД |
| Headless, clusterIP: None | L4 | Внутри кластера | Нет | Нет | Периметр не расширяется | StatefulSet, PostgreSQL, Kafka |
| NodePort | L4 | Снаружи через любую ноду | Нет | Прямых затрат нет | Открытые порты, TLS отсутствует | Отладка, bare-metal |
| LoadBalancer | L4 | Снаружи по внешнему IP | Да, либо MetalLB | Оплата за час и трафик | Зависит от TLS и политик | TCP/UDP, gRPC, статический IP |
| ExternalName | DNS | CNAME на внешнее имя | Нет | Нет | Проксирования нет | Внешняя БД, внешний API |
| Ingress | L7 | Снаружи через контроллер | Нет, нужен контроллер | Зависит от точки входа | TLS, HTTP-маршрутизация | Публичный HTTP API, сайт |
| Gateway API | L7 | Снаружи через 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.
Частые ошибки выбора и их последствия
- NodePort для продакшн-трафика. Порт из диапазона 30000-32767 слушает на каждой ноде, TLS нет, правила Ingress и WAF не применяются. Внешние сканеры находят такие порты быстро. Решение: Ingress или LoadBalancer, а NodePort оставьте для отладки и bare-metal за MetalLB.
- LoadBalancer для каждого сервиса. Каждый объект тарифицируется отдельно, десять микросервисов дают десять счетов и десять внешних IP. Решение: один Ingress или Gateway с маршрутизацией по host и path.
- Ingress без TLS. Токены и cookies уходят открытым текстом, HSTS не включён. Решение: терминация TLS на контроллере, редирект с HTTP и заголовок Strict-Transport-Security.
- ExternalName для внутренних сервисов. CNAME на имя, которого нет в DNS кластера, даёт ошибку резолва, а endpoints у такого сервиса не появляются. Решение: ClusterIP для внутренних обращений.
- Отсутствие NetworkPolicy. По умолчанию все pod'ы видят друг друга, поэтому компрометация одного контейнера открывает путь к базе. Решение: default deny и явные разрешения по меткам.
- Селектор не совпал с метками. Сервис создан, endpoints пусты, клиенты получают 503 без внятной причины. Решение: kubectl get endpoints и kubectl describe svc как первые шаги диагностики.
- Нет readiness probe. Неготовый pod получает трафик сразу после старта. Решение: readinessProbe в манифесте, тогда pod попадёт в endpoints только после успешной проверки.
Чек-лист выбора типа сервиса
- Нужен доступ извне? Нет - ClusterIP.
- Нужна L7-маршрутизация по host и path? Да - Ingress или Gateway API.
- Есть облачный контроллер балансировщика? Да - LoadBalancer. Нет - NodePort или MetalLB.
- Трафик не HTTP (TCP, UDP, MQTT)? Да - LoadBalancer или NodePort.
- Нужна стабильная идентичность pod'ов? Да - headless-сервис с clusterIP: None.
- Нужно обратиться к внешнему ресурсу? Да - ExternalName или Service без селектора с EndpointSlice.
- TLS терминируется на входе в кластер? Нет - добавьте секрет и редирект до публикации.
- NetworkPolicy описывает, кто к кому ходит? Нет - начните с default deny в namespace.
- 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, исправьте манифест до следующего релиза.