← Kubernetes: сеть
6 мин · средне · Урок 5 из 8

Headless Service: clusterIP None

Что меняется, когда Service отдаёт клиенту список адресов Pod вместо одного виртуального IP.

Обычный ClusterIP vs headless

Обычный Service:

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

Кластер выделяет ему один виртуальный IP (spec.clusterIP). DNS резолвит web.<ns>.svc.cluster.local в этот один IP. Дальше kube-proxy на ноде через iptables/IPVS заворачивает трафик на один из живых Pod.

Headless отличается одной строкой:

spec:
  clusterIP: None
  selector:
    app: web
  ports:
    - port: 80

clusterIP: None означает «не выделяй виртуальный IP». Теперь CoreDNS возвращает не один IP, а список A-записей по числу endpoint-ов. kube-proxy в эту схему не лезет вообще: трафик идёт прямо на Pod.

Обычный ClusterIPHeadless (clusterIP: None)
Виртуальный IPвыдаётсянет
Что отдаёт DNSодну A-запись на ClusterIPпо A-записи на каждый Ready-под
kube-proxy в путиестьнет
Кто выбирает подkube-proxyклиент
Per-pod именанеттолько через serviceName у StatefulSet

Что отдаёт DNS

На запрос web-hl.demo.svc.cluster.local CoreDNS отдаст что-то вроде:

Name:      web-hl.demo.svc.cluster.local
Address 1: 10.42.0.12
Address 2: 10.42.0.13
Address 3: 10.42.0.14

Это IP каждого живого Pod. Клиент сам выбирает, к кому подключаться: обычно первый в списке. Порядок DNS-резолвер обычно меняет на каждом запросе (round-robin), но это не балансировка в честном смысле, а эффект DNS-кеша.

Через 10 секунд один Pod исчез. На следующем resolve CoreDNS отдаст уже два адреса. Никаких health-check-ов: endpoint-контроллер выкидывает не-Ready Pod из списка, DNS видит только Ready.

SRV-записи

У Headless Service есть кое-что, чего обычный Service не умеет: SRV-записи на порт.

_<port-name>._<protocol>.<service>.<ns>.svc.cluster.local

Например: _http._tcp.web-hl.demo.svc.cluster.local. SRV вернёт имена Pod и порт. Нужно, когда клиенту важен не сам адрес, а кто на каком порту слушает (kafka brokers, например).

Per-pod DNS только для StatefulSet

Это место путает. Headless Service сам по себе не даёт DNS вида web-0.web-hl.demo.svc.cluster.local. Такие имена появляются, только когда:

  1. Headless Service есть.
  2. StatefulSet ссылается на него через spec.serviceName. StatefulSet - контроллер для приложений с состоянием, где у каждого Pod стабильное имя и адрес.

Под Deployment имена web-<hash> не попадают в DNS как отдельные записи. Headless над Deployment отдаёт только общий A-record со списком IP Pod, без per-pod имён.

Зачем это нужно

Три типичных сценария:

Обычным веб-приложениям headless не нужен. Если нужна ClusterIP-балансировка, остаётся стандартный Service.

Endpoints vs EndpointSlice

Headless Service пишет endpoints как обычно: контроллер endpoints-controller создаёт объекты Endpoints и EndpointSlice с IP-адресами Pod. CoreDNS читает их и формирует DNS-ответ.

Посмотреть, кого видит Service:

kubectl get endpoints web-hl
# NAME     ENDPOINTS                                        AGE
# web-hl   10.42.0.12:8080,10.42.0.13:8080,10.42.0.14:8080  30s

kubectl get endpointslices -l kubernetes.io/service-name=web-hl

Объект v1 Endpoints deprecated с 1.33, и kubectl get endpoints печатает про это предупреждение. Вторая команда показывает то же самое через актуальный EndpointSlice.

Если endpoints пустые - значит selector не совпадает с labels Pod, или Pod не Ready.

Как посмотреть резолв

Со стороны клиента нужен Pod, в котором есть nslookup. Удобнее всего busybox. Короткое имя web-hl резолвится в том namespace, где запущен Pod.

kubectl run dns-test --rm -it --image=busybox:1.36 --restart=Never -- \
  nslookup web-hl

--rm удалит Pod после выхода. Резолв делают именно изнутри кластера: снаружи CoreDNS недоступен.

Заодно стоит глянуть саму запись Service, у headless в колонке CLUSTER-IP стоит None:

kubectl get svc web-hl

Домашка

Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.

./run.sh start k8s-headless-service     # развернуть стартовое состояние
./run.sh verify k8s-headless-service    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.