ClusterIP в Kubernetes: манифест, маршрутизация kube-proxy, DNS и headless-сервис | AdminWiki

ClusterIP в Kubernetes: манифест, маршрутизация kube-proxy, DNS и headless-сервис

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

Что такое ClusterIP и зачем он нужен

ClusterIP - тип Service в Kubernetes, который выдаёт группе подов один виртуальный IP и делает его стабильной точкой входа. Адрес берётся из диапазона service CIDR, который задаётся при создании кластера. В kubeadm подсеть сервисов задаётся флагом --service-cidr, например kubeadm init --service-cidr 10.96.0.0/12: виртуальные IP сервисов выделяются именно из этой подсети (документация kubeadm). Диапазон адресов для Service также описывают параметром --service-cluster-ip-range (PurpleSchool). Если в манифесте не указать поле type, Kubernetes создаст именно ClusterIP.

Поды пересоздаются и меняют IP, а селектор сервиса (spec.selector) каждый раз заново собирает список готовых бэкендов в объектах Endpoints и EndpointSlice. Клиент обращается к постоянному адресу и DNS-имени, а kube-proxy на каждой ноде держит правила, которые переводят трафик с виртуального IP на реальные адреса подов.

ClusterIP маршрутизируется только внутри кластера. Подключиться к нему напрямую с домашнего ноутбука или с хоста-ноды не получится: адрес живёт в сети подов и обслуживается правилами ядра на нодах. Для публикации наружу нужен NodePort, LoadBalancer или Ingress.

Минимальный рабочий манифест ClusterIP

apiVersion: v1
kind: Service
metadata:
name: web
namespace: default
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
port: 80
targetPort: http
protocol: TCP

Примените манифест командой kubectl apply -f service-web.yaml и проверьте результат: kubectl get svc web покажет ClusterIP и порт, а kubectl describe svc web добавит список endpoints и события. Если это ваш первый сервис в кластере, начните с гайда Kubernetes для начинающих: запуск сервисов с минимальной конфигурацией.

Разбор полей. selector: app=web должен совпадать с метками подов, иначе endpoints останутся пустыми. port: 80 - порт, который слушает виртуальный IP внутри кластера. targetPort: http - порт контейнера, куда уходит трафик; здесь указано имя порта контейнера, и Kubernetes подставит его номер. Можно задать число напрямую, например targetPort: 8080. protocol по умолчанию TCP, для DNS и части игровых серверов нужен UDP.

Если убрать selector, автоматические endpoints не создадутся. Такой сервис работает только с объектом Endpoints, созданным вручную, и применяется для внешних ресурсов вроде managed-базы данных.

ClusterIP среди других типов Service

ТипОбласть примененияДоступ
ClusterIPвзаимодействие подов внутри кластера, базы данных, кэши, очередитолько изнутри кластера
NodePortбыстрый доступ без балансировщика, тестовые стендыпорт из диапазона 30000-32767 на каждой ноде
LoadBalancerпубликация сервиса во внешней сетивнешний IP от облака или балансировщика на bare metal
ExternalNameпрокси к внешнему DNS-имениCNAME, трафик не проходит через кластер

NodePort и LoadBalancer надстраиваются над ClusterIP: у них тоже есть виртуальный IP для внутреннего трафика, а снаружи добавляются порты нод или адрес балансировщика. ExternalName ничего не балансирует и возвращает CNAME на внешнее имя. Критерии выбора с примерами собраны в статье Как выбрать тип Service в Kubernetes: ClusterIP, NodePort или LoadBalancer. Если внешний доступ нужен без облачного балансировщика, посмотрите разбор externalIPs в гайде по настройке публичного доступа к сервисам.

Как kube-proxy реализует маршрутизацию и балансировку

kube-proxy работает на каждой ноде как DaemonSet (при ручной установке - как systemd-юнит), следит за Service и EndpointSlice через API-сервер и программирует правила ядра. Прокси в привычном смысле здесь нет: после DNAT пакет уходит напрямую к поду, а обратный трафик отслеживает conntrack. Пропускная способность поэтому не упирается в один процесс.

Режим задаётся флагом --proxy-mode или полем mode в ConfigMap kube-proxy. На Linux-узлах доступны три режима: iptables, ipvs и nftables - каждый определяет свой способ настройки правил перенаправления пакетов (Virtual IPs and Service Proxies). Режим nftables появился как alpha-функция в Kubernetes 1.29, на момент публикации находится в beta и ожидается в статусе GA начиная с 1.33 (NFTables mode for kube-proxy). Посмотреть действующую настройку можно так: kubectl -n kube-system get cm kube-proxy -o yaml и kubectl -n kube-system logs daemonset/kube-proxy, в логах при старте печатается выбранный режим.

iptables: правила и балансировка

В режиме iptables kube-proxy создаёт в таблице nat цепочки KUBE-SERVICES, KUBE-SVC-<hash> и KUBE-SEP-<hash>. Первая сопоставляет ClusterIP с сервисом, вторая выбирает бэкенд, третья делает DNAT на конкретный под. Смотреть разбор удобно так: iptables -t nat -L KUBE-SERVICES -n и iptables -t nat -L KUBE-SVC-XXXX -n.

Балансировка вероятностная. Для трёх endpoints цепочка KUBE-SVC выглядит примерно так:

-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAA
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.5 -j KUBE-SEP-BBBB
-A KUBE-SVC-XXXX -j KUBE-SEP-CCCC

Каждый следующий переход получает пересчитанную вероятность, поэтому итоговое распределение равномерное, а последний endpoint указан без условия. Загрузку подов алгоритм не учитывает: выбор случайный, но по числу соединений трафик выравнивается.

Ограничение режима - рост числа правил. Цепочки читаются последовательно, и при тысячах сервисов и десятках тысяч endpoints время обработки первой таблицы растёт. Синхронизация тоже не мгновенная: kube-proxy обновляет правила циклами, а период синхронизации настраивается полями syncPeriod и minSyncPeriod в конфигурации kube-proxy (пример конфигурации kube-proxy). Конкретные значения по умолчанию зависят от версии и способа установки, поэтому перед изменением сверяйтесь с документацией для вашей версии.

IPVS: производительность и алгоритмы

IPVS - модуль ядра, который держит виртуальные сервисы и реальные серверы в хеш-таблицах. Виртуальным сервисом становится ClusterIP, реальными серверами - адреса подов вместе с портом. Поиск бэкенда выполняется за константное время, поэтому режим лучше держит тысячи сервисов.

Алгоритм выбирает планировщик: rr (round robin), wrr (взвешенный), lc (наименьшее число соединений), wlc, sh (хеш по адресу источника), dh (хеш по адресу назначения), sed, nq, mh. Для равномерных stateless-сервисов подходит rr, для тяжёлых сессий - sh или lc. Планировщик задаётся флагом --ipvs-scheduler, состояние видно в выводе ipvsadm -Ln: в блоке TCP перечислены ClusterIP и real servers.

Для IPVS нужны загруженные модули ядра: ip_vs, ip_vs_rr, ip_vs_wrr, ip_vs_sh, nf_conntrack. В managed-кластерах режим обычно выбирает провайдер, и вам остаётся проверить результат по логам kube-proxy.

nftables: новый режим kube-proxy

В режиме nftables kube-proxy настраивает правила перенаправления пакетов через nftables API подсистемы netfilter ядра. Для каждого endpoint устанавливаются правила, которые по умолчанию выбирают backend Pod случайным образом (Virtual IPs and Service Proxies). Режим введён как alpha в Kubernetes 1.29, сейчас находится в beta и ожидается GA начиная с 1.33 (NFTables mode for kube-proxy), поэтому в продакшене его стоит включать с оглядкой на версию кластера.

Если в кластере CNI на eBPF, например Cilium, kube-proxy может быть отключён совсем: маршрутизацию сервисов берут на себя eBPF-программы, а цепочек KUBE-* в iptables не остаётся. Тогда вместо команд выше используют cilium service list и cilium bpf lb list.

DNS-имя сервиса: как резолвится ClusterIP внутри кластера

За имена сервисов отвечает CoreDNS. Он развёрнут в namespace kube-system как Deployment, перед ним стоит Service с именем kube-dns и собственным ClusterIP. Каждый под получает этот адрес в /etc/resolv.conf: там прописаны nameserver, строка поиска вида default.svc.cluster.local svc.cluster.local cluster.local и опция ndots:5.

Полное имя сервиса строится по схеме <service>.<namespace>.svc.cluster.local. Внутри того же namespace хватает короткого имени, но резолвится оно медленнее: из-за ndots:5 резолвер сначала перебирает суффиксы и лишь потом запрашивает имя как есть. Для конкретного порта и протокола пригодится SRV-запись вида _http._tcp.web.default.svc.cluster.local.

Для сервиса с ClusterIP CoreDNS отдаёт одну A-запись с виртуальным адресом. Живые бэкенды в DNS не попадают: клиент всегда видит стабильный IP.

Запись создаётся сразу после появления сервиса. Если селектор не совпал с метками и endpoints пусты, имя всё равно резолвится, но соединение не установится: DNAT будет некуда делать.

Проверка DNS изнутри пода

Поднять временный под с утилитами проще всего так: kubectl run -it --rm debug --image=busybox:1.36 --restart=Never -- nslookup web.default.svc.cluster.local. В ответе для ClusterIP будет один Address, для headless-сервиса - несколько строк с адресами подов.

Более подробный вывод даёт dig: kubectl run -it --rm debug --image=tutum/dnsutils --restart=Never -- dig @kube-dns.kube-system.svc.cluster.local web.default.svc.cluster.local. Поле dnsPolicy в спецификации пода управляет тем, куда идут запросы: ClusterFirst стоит по умолчанию, ClusterFirstWithHostNet нужен подам с hostNetwork, None заставляет использовать только собственный dnsConfig. Менять ndots в dnsConfig имеет смысл, когда приложение часто обращается к внешним именам и лишние попытки с суффиксами добавляют задержку.

Диагностика пустых endpoints: типичные ошибки и команды проверки

Endpoints - список адресов подов, которые совпали с селектором и прошли readiness-проверки. EndpointSlice API имеет статус Stable начиная с Kubernetes v1.21, и control plane автоматически создаёт EndpointSlices для любого Service с указанным селектором. Эти срезы служат источником истины для маршрутизации внутреннего трафика (EndpointSlices), при этом kubectl get endpoints продолжает работать и показывает сводную картину.

Посмотреть состав: kubectl get endpoints web -o wide, срезы отдельно - kubectl get endpointslice -l kubernetes.io/service-name=web. Пустая колонка ENDPOINTS означает, что трафик отправлять некуда, и причина почти всегда в метках, готовности подов или портах.

Типичные причины:

  • селектор сервиса не совпадает с метками подов: другой ключ или опечатка в значении;
  • поды в статусе Running, но не Ready из-за падающей readiness-проверки;
  • targetPort указывает на порт, который контейнер не слушает;
  • сервис создан в одном namespace, а поды живут в другом;
  • targetPort задан именем, а такого имени нет в ports контейнера;
  • поды в состоянии Terminating или отсутствуют после неудачного деплоя.

Пошаговый чек-лист диагностики

  1. kubectl get svc web -o yaml. Ожидаемый результат: в spec.selector те же метки, что у подов, а в ports корректные port и targetPort.
  2. kubectl get pods --show-labels -n default. Хотя бы один под должен нести метку из селектора и быть в статусе Running.
  3. kubectl get pods -l app=web -o wide. Пустой вывод означает несовпадение меток, а не проблему сети.
  4. kubectl describe pod <pod>. Смотрите блок Conditions: Ready=True и отсутствие ошибок readiness-probe в Events.
  5. kubectl get endpoints web -o wide. Если после правки меток список пуст, поды не прошли проверку готовности.
  6. kubectl describe svc web. В колонке Port сверьте targetPort с портом контейнера; при именованном порте сравните имя с ports в спецификации пода.
  7. Проверьте NetworkPolicy в namespace: на состав endpoints политики не влияют, но блокировать трафик при живых адресах могут.

Исправление обычно в одну строку: поправить метку пода командой kubectl label pod <pod> app=web --overwrite или привести селектор сервиса к существующим меткам. После правки endpoints обновляются в течение нескольких секунд.

Headless-сервисы: clusterIP: None для StatefulSet и прямого доступа к подам

Headless-сервис создаётся с полем clusterIP: None. Виртуальный адрес не выделяется, kube-proxy не строит правил, а DNS возвращает адреса всех подов, попавших под селектор. Клиент сам решает, к какому адресу идти: балансировка переходит на его сторону.

Основной сценарий - StatefulSet, где каждому поду нужна стабильная сетевая идентичность. Имя пода предсказуемо, и DNS-запись для него выглядит так: <pod-name>.<service-name>.<namespace>.svc.cluster.local. Это позволяет собрать кластер базы данных, где реплики находят друг друга по именам, а не по изменчивым IP.

Манифест headless-сервиса и StatefulSet

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

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pg
spec:
serviceName: db
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres

Поле serviceName в StatefulSet указывает на headless-сервис. Именно оно включает персональные DNS-имена подов: pg-0.db.default.svc.cluster.local, pg-1.db.default.svc.cluster.local, pg-2.db.default.svc.cluster.local. Логику контроллеров разбираем отдельно в статье Контроллеры Kubernetes: как работает Control Loop, Deployment, StatefulSet, DaemonSet.

Проверка: kubectl get svc db -o yaml покажет clusterIP: None, а kubectl get endpoints db - адреса всех готовых подов. Чтобы в DNS попадали и ещё не готовые поды (типично для кластеров БД, которые поднимаются по цепочке), добавьте в сервис поле publishNotReadyAddresses: true.

Прямой доступ к подам через headless-сервис

Запрос nslookup db.default.svc.cluster.local вернёт несколько A-записей, по одной на под. Подключение к конкретному узлу идёт по персональному имени, например pg-0.db.default.svc.cluster.local, и трафик не проходит через виртуальный IP.

Автоматической балансировки в headless-режиме нет. Клиент получает список адресов и сам выбирает стратегию: round-robin, подключение к лидеру, разбиение шардов. Это ровно то, что нужно реплицируемым базам, брокерам сообщений и сервисам с собственным service discovery. Для stateless-приложений, где нужна простая раздача трафика, обычный ClusterIP удобнее.

Безопасность и ограничение доступа к ClusterIP

По умолчанию любой под в кластере может обратиться к любому ClusterIP. Кластер без сетевых политик - плоская сеть: скомпрометированный контейнер способен перебрать внутренние адреса и достучаться до базы данных, метрик или API. Считать непубличный адрес средством защиты нельзя.

Практичный минимум: разносить приложения по namespace, выдавать сервис-аккаунтам минимальные права, включать NetworkPolicy там, где CNI умеет их применять. NetworkPolicy - объект Kubernetes, но выполняет его именно CNI-плагин, поэтому нужен CNI с поддержкой сетевых политик: Calico, Cilium, Antrea, а также Weave (Основные концепции сетевой архитектуры Kubernetes). Простые CNI вроде Flannel NetworkPolicy не поддерживают и игнорируют, и правила просто не сработают (NetworkPolicy Zero Trust Kubernetes).

Пример NetworkPolicy для ClusterIP

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-frontend
namespace: default
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

Политика разрешает входящий трафик к подам app=web только от подов app=frontend на порт 8080. Применение и проверка: kubectl apply -f netpol.yaml, kubectl describe networkpolicy web-allow-frontend, kubectl get networkpolicy -A. Учтите: как только к поду применена хотя бы одна политика с policyTypes: Ingress, всё, что не разрешено явно, блокируется.

Дополнительные меры: не публиковать внутренние сервисы через NodePort без необходимости, ограничивать доступ к kube-apiserver, включить аудит, проверить, что в подах нет лишних привилегий. Если ваши сервисы собираются в микросервисную схему, готовые манифесты и связки с базой и брокером есть в гайде Практическое развёртывание микросервисов в Kubernetes.

Чек-лист проверки ClusterIP и headless-сервиса

  • kubectl get svc: сервис существует, тип ClusterIP, порт и адрес на месте.
  • kubectl describe svc <name>: селектор совпадает с метками подов, targetPort верный.
  • kubectl get endpoints <name> -o wide: есть адреса подов и порты; пустой список указывает на проблему с метками или readiness.
  • kubectl get pods --show-labels: поды Running и Ready, метки совпадают с селектором.
  • kubectl get endpointslice -l kubernetes.io/service-name=<name>: срез содержит те же адреса, что и Endpoints.
  • nslookup <service>.<namespace>.svc.cluster.local из пода: одна A-запись для ClusterIP, несколько для headless.
  • curl внутри пода по адресу сервиса и по персональному DNS-имени пода: приходит ожидаемый ответ приложения.
  • kubectl get networkpolicy -A: политики не блокируют нужный трафик.
  • kubectl get svc <name> -o yaml: для headless в выводе clusterIP: None.

Проверяйте сервис на том же уровне, где работает клиент: curl с рабочей станции ничего не скажет о доступности ClusterIP изнутри кластера. Команды и манифесты из статьи рассчитаны на Kubernetes 1.28 и новее, где apiVersion: v1 для Service и networking.k8s.io/v1 для NetworkPolicy остаются актуальными.

Поделиться: