Предыдущие статьи:
https://habr.com/ru/articles/1084866/ - Kubernetes просто, часть 3: зачем нужен Service и как трафик доходит до конкретного Pod
https://habr.com/ru/articles/1083100/ - Kubernetes просто, часть 2: что происходит после kubectl apply
https://habr.com/ru/articles/1082088/ - Kubernetes просто, часть 1: зачем Kubernetes, устройство кластера
В прошлой части мы разобрали для чего в Kubernetes нужен Service.
У нас закрепилась такая модель:
Client Pod │ │ backend-service ↓ Kubernetes DNS │ ↓ Service ClusterIP :80 │ ↓ ready backend endpoints │ ↓ Pods :8080
Идея проста:
Клиенту не нужно знать IP конкретных Pods. Он обращается к Service, а набор backend Pods может меняться независимо от него.
Это отлично работает, пока мы работаем внутри кластера, к примеру:
Frontend Pod │ ↓ Backend Service │ ↓ Backend Pods
Но настоящий пользователь пользователь обычно находится в другом месте, к примеру, он приходит из Интернета.
Здесь возникает следующий вопрос:
Как предоставить доступ к приложению снаружи кластера?
Для того, чтобы ответить на него, сегодня разберем два типа Service: NodePort и LoadBalancer
А заодно сравним его с уже известным нам ClusterIP.

Вспомним ClusterIP
В прошлой части мы создавали Service:
apiVersion: v1 kind: Service metadata: name: backend-service spec: selector: app: backend ports: - port: 80 targetPort: 8080
И как мы уже выяснили: если поле type не указано, как в примере выше, Kubernetes автоматически использует
type: ClusterIP
То есть наш Service можно представлять, как:
apiVersion: v1 kind: Service metadata: name: backend-service spec: type: ClusterIP selector: app: backend ports: - port: 80 targetPort: 8080
Service получает ClusterIP, например:
backend-service – 10.1.20.10:80
Другой Pod внутри кластера может обращаться так:
Frontend Pod │ │ backend-service:80 ↓ ClusterIP Service │ ↓ Backend Pod :8080
Это прекрасный вариант для внутреннего взаимодействия.
Например:
Frontend → Backend Backend → Redis Application → Internal API
Однако же, обычный ClusterIP сам по себе не является публичной точкой входа в Интернет.
Если нашему приложению нужен внешний доступ, требуется другой механизм.
NodePort
Начнем с достаточно простого варианта – NodePort.
Изменим Service-манифест так:
apiVersion: v1 kind: Service metadata: name: backend-service spec: type: NodePort selector: app: backend ports: - port: 80 targetPort: 8080 nodePort: 30080
Здесь мы прописали поле type самостоятельно и присвоили ему значение NodePort.
Также мы добавили новое поле – nodePort в раздел ports.
Что же это значит?
Упрощенно,
NodePort делает Service доступным через определенный порт на нодах кластера.
К примеру, у нас есть Node с адресом:
192.168.1.10
И
nodePort: 30080
Тогда при подходящей сетевой доступности к Node клиент может обратиться:
192.168.1.10:30080
Дальше запрос будет перенаправлен к backend Service и одному из подходящих backend endpoints соответственно.
Схематично:

Три порта - не путаемся
В NodePort Service-манифесте можно увидеть сразу три значения
ports: - port: 80
targetPort: 8080
nodePort: 30080
На первый взгляд выглядит так, будто Kubernetes специально пытается проверить нас на внимательность. Но здесь у каждого порта есть своя роль. Начнём с уже знакомых нам:
port:80
Это порт Service.
targetPort: 8080
Это порт backend, куда должен попадать трафик.
Теперь добавляется:
nodePort: 30080
Это порт, через который Service становится доступным из других нод.

NodePort не означает «Pod на этой Node»
Здесь частая ошибка.
Представим две Worker-ноды:
Node 1 192.168.1.10 Node 2 192.168.1.11
Backend под был определен планировщиком на вторую из них:
Node 2 └── Pod B
А клиент обращается
192.168.1.10:30080
То есть запрос пришел на Node 1.
Можно подумать:
Раз запрос пришёл на Node 1, значит backend Pod тоже должен находиться на Node 1.
Но это не обязательное условие.
Упрощённо возможна ситуация:
Client │ ↓ Node 1 :30080 │ │ └─────────────────────┐ ↓ Node 2 │ ↓ Pod B
Service работает с подходящими backend endpoints, а они могут находиться на других Nodes.
То есть:
NodePort определяет точку входа в кластер, но не означает, что запрос обязательно должен обслуживаться Pod на той же Node.

Это особенно важно для правильной ментальной модели.
Service работает с группой backend-подов, а не с одним конкретным подом на одной конкретной машине.
Нужно ли задавать nodePort вручную
В примере мы прописали:
nodePort: 30080
Но это поле не обязательно указывать вручную.
Можно создать:
apiVersion: v1 kind: Service metadata: name: backend-service spec: type: NodePort selector: app: backend ports: - port: 80 targetPort: 8080
В таком случае Kubernetes может выделить подходящий NodePort автоматически из настроенного для кластера диапазона.
В стандартной конфигурации часто используется диапазон:
30000–32767
Но лучше воспринимать его именно как типичное значение по умолчанию, а не как универсальный закон для любого возможного кластера: диапазон может быть настроен иначе.
Значит, NodePort можно использовать для публикации приложения?
Технически – да.
Если Node доступна клиенту по сети и нужный порт разрешён через firewall и политику доступа, можно обращаться:
<NodeIP>:<NodePort>
Например:
203.0.113.10:30080
Но для пользователей веб-приложений такая схема обычно не является конечной архитектурой.
Представьте адрес магазина:
http://203.0.113.10:30080
Не удобно - не так ли?
Кроме того возникают закономерные вопросы:
1) Какой Node IP использовать?
2) Что произойдёт при проблеме с этой Node?
3) Кто предоставляет удобный публичный IP?
4) Где выполнять внешнюю балансировку?
5) Как организовать нормальный входной трафик?
Итак, NodePort предоставляет для нас механизм доступа с других нод, но сам по себе не решает задачи публикации production-сервиса.
И здесь появляется следующий тип Service.
LoadBalancer
В инфраструктуре, которая поддерживает такую интеграцию, можно создать Service:
apiVersion: v1 kind: Service metadata: name: frontend-service spec: type: LoadBalancer selector: app: frontend ports: - port: 80 targetPort: 8080
Главное изменение:
type: LoadBalancer
Концептуально:
Internet │ ↓ External Load Balancer │ ↓ Kubernetes Service │ ↓ Frontend Pods
Инфраструктурная интеграция может предоставлять внешний load balancer (балансировщик нагрузки) и внешний адрес, через которые пользователи могут обращаться к приложению.

Но откуда берётся этот Load Balancer?
Сначала разберёмся, что это вообще такое. Представим, что у нас есть 3 Worker-ноды:
Node 1 Node 2 Node 3
На них работают Pods нашего приложения.
C NodePort пользователь мог обращаться напрямую к одной из нод:
User │ ↓ Node 1:30080 │ ↓ Service │ ↓ Pods
Но хотелось бы дать пользователю один нормальный внешний адрес, а уже перед кластером поставить компонент, который будет передавать их дальше.
Вот эту роль и выполняет внешний балансировщик нагрузки.
Упрощённо:
Internet │ ↓ Public IP │ ↓ Load Balancer / | \ ↓ ↓ ↓ Node 1 Node 2 Node 3 │ ↓ Service │ ↓ Pods
Теперь пользователю не нужно думать, к какой именно Node обращаться. У него есть одна внешняя точка доступа – Public IP.
А дальше инфраструктура сама доставляет запрос в кластер.

Но здесь есть важный нюанс.
Когда мы пишем:
type: LoadBalancer
Мы не создаём балаансировщик буквально самим Kubernetes.
Kubernetes скорее говорит:
Мне нужна внешняя точка входа для этого Service.
Если кластер работает, к примеру, в облаке и там настроена соответствующая интеграция, инфраструктура может создать внешний балансировщик и выдать ему публичный адрес.
Получается что-то такое:
Мы создаём:
Service type: LoadBalancer │ ↓ Kubernetes видит запрос │ ↓ Инфраструктура предоставляет внешний Load Balancer │ ↓ Появляется внешний адрес
То есть LoadBalancer - это не какой-то специальный Pod, который Kubernetes обязательно запускает внутри кластера.
Это способ сказать:
Я хочу предоставить этот Service наружу через внешний балансировщик
Именно поэтому поведение зависит от того, где запущен Kubernetes.
В облачном Kubernetes такой Service часто приводит к появлению внешнего адреса.
А если мы просто запустили Kubernetes локально на своём компьютере, облачного провайдера рядом нет – значит, некому автоматически выдать нам настоящий публичный адрес для Load Balancer. Для локальных кластеров эта задача может быть решена другими способами, но сейчас важна сама идея:
ClusterIP ↓ доступ внутри кластера ---------------------------- NodePort ↓ доступ через IP и порт Node ---------------------------- LoadBalancer ↓ внешняя точка входа через балансировщик
Service и Load Balancer – не одно и то же
Здесь легко запутаться из-за названия:
Здесь легко запутаться из-за названия:
type: LoadBalancer
Service у нас всё ещё остаётся Service.
Он по-прежнему знает, какие поды подходят по селекторам, предоставляет стабильную точку доступа к меняющемуся набору backend-подов.
Просто теперь перед ним появляется внешний способ попасть в кластер по публичному IP-адресу.
Internet │ ↓ Load Balancer │ ↓ Service │ ↓ ready Pods
То есть:
Service решает, куда внутри Kubernetes должен идти трафик.
Внешний Load Balancer помогает этому трафику попасть в кластер снаружи.
Для нашей текущей модели этого вполне достаточно.
Сравнение 3 типов
Теперь можно собрать всё, что мы уже разобрали.
Cluster IP
Клиент уже находится внутри Kubernetes.
Frontend Pod ↓ ClusterIP Service ↓ Backend Pods
Например:
Frontend - Backend
NodePort
Клиент приходит снаружи через IP одной из Nodes:
Client ↓ NodeIP:30080 ↓ Service ↓ Pods
LoadBalancerr
Перед кластером появляется единая внешняя точка входа:
Internet ↓ Public IP ↓ Load Balancer ↓ Service ↓ Pods

Открывать наружу нужно не всё!
Представим Интернет-магазин:
Frontend ↓ Backend ↓ Database
Пользователь должен иметь возможность открыть frontend, но ему совершенно не обязательно напрямую обращаться к backend и уж тем более он не должен иметь возможность обращаться напрямую к базе данных. Поэтому архитектура может выглядеть так:
INTERNET ↓ Load Balancer ↓ Frontend Service ↓ Frontend Pods ↓ Backend Service ↓ Backend Pods ↓ Database
При этом наружу опубликован только frontend. Он в свою очередь обращается к бэку через внутренний Cluster IP.
Frontend ↓ backend-service ↓ Backend Pods
Получается довольно логичная картина:

Запомнить просто:
Не публикуйте в Интернет то, к чему внешний доступ не требуется.
Что нужно запомнить из этой части?
У нас есть три основных типа Service:
ClusterIP NodePort LoadBalancer
Различать их проще всего по вопросу:
Если клиент находится внутри Kubernetes:
Client Pod ↓ ClusterIP ↓ Pods
Если мы хотим попасть в Service через Node:
Client ↓ NodeIP:NodePort ↓ Service ↓ Pods
Если приложению нужна нормальная внешняя точка входа:
Internet ↓ Load Balancer ↓ Service ↓ Pods
При этом фундаментальная идея не меняется:
Service скрывает от клиента постоянно меняющиеся Pods и предоставляет стабильную точку доступа к приложению.
Проверьте себя
Попробуйте ответить своими словами, не возвращаясь к тексту статьи.
1. Почему обычного ClusterIP недостаточно, если пользователь находится за пределами Kubernetes-кластера?
2. Что меняется, когда вместо:
type: ClusterIP
мы создаём:
type: NodePort
3. В Service указано:
port: 80 targetPort: 8080 nodePort: 30080
За что отвечает каждый из этих трёх портов?
4. Есть две Nodes:
Node 1 192.168.1.10 Node 2 192.168.1.11
Backend Pod работает на Node 2, а пользователь отправляет запрос:
192.168.1.10:30080
Может ли этот запрос попасть в Pod на Node 2? Почему?
5. Обязательно ли вручную указывать:
nodePort: 30080
при создании Service типа NodePort?
6. Какую проблему решает LoadBalancer по сравнению с прямым обращением к NodeIP:NodePort?
7. Что мы на самом деле просим сделать, когда создаём:
type: LoadBalancer
И почему в локальном Kubernetes-кластере после этого не обязательно автоматически появится публичный IP?
8. Есть приложение:
Internet ↓ Frontend ↓ Backend ↓ Database
Какие компоненты имеет смысл сделать доступными снаружи, а какие оставить внутри кластера? Какие типы Service здесь можно использовать?

