Service и порты
Что нужно знать перед уроком
- M1.02 — Deployment и ReplicaSet — нужно понимать label selector
Что нужно знать перед уроком
M1 урок 2 — Deployment создаёт и заменяет Pod’ы, у каждого свой IP-адрес, который меняется при пересоздании. Как же клиенту находить актуальный набор Pod’ов? Это и есть задача Service.
Теория
Зачем нужен Service
Pod’ы — эфемерны: пересоздаются при обновлении, масштабировании, сбое узла, каждый раз получая новый IP-адрес. Если клиент запомнит конкретный IP Pod’а — он рано или поздно обратится в никуда. Service — объект, который даёт стабильную точку доступа (виртуальный IP и/или DNS-имя) к динамически меняющемуся набору Pod’ов, определяемому через selector (по тому же принципу label selector, что и у Deployment/ReplicaSet, но независимо от них — Service не знает о существовании Deployment, он видит только Pod’ы, подходящие под свой selector).
Endpoints/EndpointSlice — механизм связи Service↔Pod
Service не хранит список Pod’ов напрямую — он декларирует selector, а отдельный контроллер непрерывно синхронизирует список подходящих IP:port в объекты Endpoints (устаревающий формат) или EndpointSlice (современный, масштабируемый на большие наборы Pod’ов за счёт разбиения на несколько объектов). Именно EndpointSlice/Endpoints, а не сам Service, содержит актуальный список адресов — Service — это, по сути, «указатель» плюс правило балансировки на уровне kube-proxy.
| |
Четыре типа Service — каждый следующий технически расширяет предыдущий
| Тип | Что делает | Расширяет |
|---|---|---|
ClusterIP (по умолчанию) | стабильный виртуальный IP, доступный только внутри кластера | — |
NodePort | то же, что ClusterIP, плюс статический порт (диапазон 30000–32767 по умолчанию), открытый на каждом узле кластера | ClusterIP |
LoadBalancer | то же, что NodePort, плюс запрос внешнего балансировщика у облачного провайдера/платформы, который направляет трафик на этот NodePort | NodePort |
ExternalName | не создаёт виртуальный IP вообще — просто CNAME-запись, перенаправляющая DNS-имя сервиса на внешний DNS-адрес | отдельный случай, без Endpoints |
| |
port — порт, на котором Service доступен внутри кластера; targetPort — порт, который реально слушает контейнер. Они могут (и часто должны) отличаться.
trafficDistribution — предпочтение локальных Pod’ов
Начиная с Kubernetes 1.31 (beta), а с Kubernetes 1.35 — стабильно (GA), у Service есть поле spec.trafficDistribution, которое подсказывает kube-proxy, каким Pod’ам отдавать предпочтение при балансировке:
| Значение | Поведение |
|---|---|
| (не задано) | балансировка без явных предпочтений локальности — трафик может уйти на Pod в любой зоне/узле |
PreferSameZone | предпочитать Pod’ы в той же availability zone, что и узел-отправитель, с fallback на любые остальные (в версиях до 1.35 это же поведение называлось PreferClose — имя сохранено для обратной совместимости, но PreferSameZone — актуальное явное название) |
PreferSameNode | появилось в Kubernetes 1.35 — строго предпочитать Pod’ы на том же узле, что и узел-отправитель, и только при их отсутствии переключаться на удалённые |
Это не замена topologyKeys (устаревшего механизма, удалённого ранее) и не гарантия, а именно предпочтение — если подходящих локальных Pod’ов нет, трафик всё равно уйдёт на удалённые.
Headless Service: clusterIP: None
Обычный Service выдаёт один виртуальный IP, скрывающий за собой множество Pod’ов (балансировка на уровне kube-proxy, клиент не видит отдельные Pod’ы). Headless Service (spec.clusterIP: None) убирает этот виртуальный IP — вместо него DNS-запрос к имени сервиса возвращает A-записи сразу на все IP подходящих Pod’ов. Это нужно, когда клиенту важно самому видеть и выбирать конкретный Pod (классический случай — клиентские библиотеки БД, работающие со StatefulSet, где важно различать реплики).
Если у Pod’ов заданы subdomain и hostname — headless Service дополнительно создаёт именованные SRV-записи по каждому Pod’у отдельно (<hostname>.<subdomain>.<service>.<namespace>.svc.cluster.local), что используется StatefulSet для предсказуемого сетевого имени каждой реплики.
DNS и FQDN сервиса
Любой Service (кроме ExternalName, где это CNAME) получает полное доменное имя вида:
<service-name>.<namespace>.svc.cluster.local
Из того же namespace достаточно короткого имени (linkding), из другого namespace обязательно указывать хотя бы <service-name>.<namespace> — это разбирается подробно в разделе Linux/Сети ниже.
spec.externalIPs — список произвольных внешних IP, на которые Kubernetes принимает трафик для этого Service, минуя LoadBalancer/NodePort. Начиная с Kubernetes 1.36, это поле официально deprecated (полное удаление запланировано на v1.43) — оно годами было источником проблем с безопасностью (в частности, CVE-2020-8554, MITM-атаки через перехват трафика на произвольный IP). Если нужен внешний доступ — используйте LoadBalancer (облачный/платформенный балансировщик), NodePort или Gateway API/Ingress, а не externalIPs.Частые ошибки и подводные камни
- Путать
portиtargetPort. Ошибка «Service отвечает, но всегда Connection Refused» почти всегда означает, чтоtargetPortне совпадает с портом, который реально слушает контейнер. - Ожидать, что headless Service балансирует трафик как обычный. Headless Service не балансирует — клиент получает список всех IP и сам решает, к какому обращаться (или использует round-robin на уровне DNS-резолвера, что не то же самое, что
kube-proxy). - Забыть про
EndpointSlice/Endpointsпри диагностике «Service не работает». Еслиselectorне совпадает ни с одним Pod’ом (опечатка в лейбле) — Service будет существовать, ноEndpointsокажется пустым; сам Service при этом не покажет явную ошибку.
Linux / Сети
Как Pod резолвит DNS
Внутри каждого Pod’а kubelet прописывает /etc/resolv.conf, указывающий на DNS-сервер кластера (обычно CoreDNS) и search-домены:
nameserver 10.96.0.10
search course-m1-service.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
Search-домены — причина, почему внутри своего namespace достаточно написать просто linkding (резолвер попробует linkding.course-m1-service.svc.cluster.local первым из списка search-доменов и найдёт запись), а обращаясь из другого namespace, нужно писать хотя бы linkding.<namespace> — короткое имя linkding там просто не резолвится ни в одну из ваших записей.
| |
Практика в кластере
Linkding — минималистичный self-hosted менеджер закладок на Python (Django) + SQLite внутри контейнера — чуть сложнее Homer за счёт того, что реально отвечает на HTTP-запросы динамическим контентом, удобно для проверки Service end-to-end.
| |
- Проверьте Service и его Endpoints:
| |
- Проверьте DNS-резолвинг и доступ изнутри кластера:
| |
- Смоделируйте headless Service и посмотрите на разницу в DNS-ответе:
| |
- Сломайте
selectorнамеренно и убедитесь, что Endpoints опустели, а сам Service «выглядит» рабочим:
| |
Практика разработки
Используется официальный образ sissbruecker/linkding, собственная сборка появится в M3.
Шпаргалка команд урока
| |
Вопросы для самопроверки
Service декларирует правило (selector), а актуальный список адресов поддерживается в EndpointSlice/Endpoints.
Источник: Kubernetes — service
NodePort — это ClusterIP плюс открытый порт на каждом узле; LoadBalancer, в свою очередь, расширяет NodePort внешним балансировщиком.
Источник: Kubernetes — service
Headless Service убирает единый виртуальный IP, отдавая клиенту список всех адресов Pod'ов напрямую — используется, когда клиенту важно различать конкретные реплики.
Источник: Kubernetes — service
Search-домены в /etc/resolv.conf Pod'а покрывают только собственный namespace; из другого namespace нужно хотя бы <service>.<namespace>.
Источник: Kubernetes — dns pod service
Самая частая причина «немого» Service — опечатка в selector, из-за которой EndpointSlice остаётся пустым при формально валидном Service.
Источник: Рекомендуемая литература
Рекомендуемая литература
Официальная документация
- kubernetes.io — concepts/services-networking/service
- kubernetes.io — concepts/services-networking/dns-pod-service
- kubernetes.io — Service.
- kubernetes.io — Kubernetes v1.35: Timbernetes (
trafficDistribution: PreferSameNode/PreferSameZone— GA). - kubernetes.io — Kubernetes v1.36: Haru (
spec.externalIPs— deprecated, KEP-5707).
Статьи и блоги
- Kubernetes для начинающих: что такое Pod и как с ним работать — Flant на Habr.
- Основные концепции сетевой архитектуры Kubernetes — Habr.
- Миграция с ingress-nginx: выбор нового контроллера — Habr.
Книги
- Marko Lukša. Kubernetes in Action. 2nd ed., 2022. ISBN 978-1617297618.
- Kelsey Hightower, Brendan Burns, Joe Beda. Kubernetes: Up and Running. 3rd ed., 2022. ISBN 978-1098110201.
- Michael Hausenblas, Stefan Schimanski. Programming Kubernetes. 2019. ISBN 978-1492047107.
Связанные материалы
- Предыдущий урок: M1.02 — Deployment и ReplicaSet.
- Следующий урок: M1.04 — ConfigMap/Secret.
- Глоссарий: Service.
✓ Урок пройден — все вопросы самопроверки отвечены верно