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.
| Обычный ClusterIP | Headless (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. Такие имена появляются,
только когда:
- Headless Service есть.
- StatefulSet ссылается на него через
spec.serviceName. StatefulSet - контроллер для приложений с состоянием, где у каждого Pod стабильное имя и адрес.
Под Deployment имена web-<hash> не попадают в DNS как отдельные
записи. Headless над Deployment отдаёт только общий A-record со списком
IP Pod, без per-pod имён.
Зачем это нужно
Три типичных сценария:
- gRPC-клиент с client-side load balancing резолвит хост, получает все IP и сам распределяет коннекты по пулу. kube-proxy балансит на уровне TCP-коннекта, а gRPC мультиплексирует много стримов в одном коннекте, поэтому стандартный Service балансит плохо. Headless даёт клиенту полный список.
- Kafka, RabbitMQ, Elasticsearch стартуют и хотят узнать всех соседей по DNS. Headless отдаёт список, нода понимает, что peers есть, присоединяется к кластеру.
- Иногда NAT через kube-proxy ломает sticky sessions или метрики (source IP меняется). Headless убирает proxy из пути.
Обычным веб-приложениям 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 # проверить текущий шаг
Коротко
- Headless Service - это
clusterIP: None, виртуальный IP не выделяется. - DNS возвращает список A-записей с адресами Ready-подов, kube-proxy из пути исчезает.
- Порядок адресов в ответе не балансировка: распределяет нагрузку сам клиент.
- Per-pod имена вида
web-0.web-hl...даёт только StatefulSet черезspec.serviceName, не сам headless. - Нужен для gRPC с клиентской балансировкой и для кластерных СУБД, которые ищут соседей через DNS.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс