NodePort: порт на каждой ноде
Самый прямолинейный способ пустить трафик снаружи в кластер и его цена.
Когда ClusterIP мало
ClusterIP даёт Service внутренний адрес в кластере. Pod и другие сервисы видят его, снаружи кластера он недоступен. Для админ-задач или быстрой проверки приложения нужен внешний доступ. NodePort решает эту задачу самым прямолинейным способом: открыть порт на каждой ноде.
Что такое NodePort
Когда создаётся Service с type: NodePort, Kubernetes делает три
вещи:
- Создаёт ClusterIP (как у обычного Service): внутренний адрес.
- Открывает дополнительный порт на каждой ноде кластера в
диапазоне
30000-32767. Этот порт kube-proxy форвардит на ClusterIP, а оттуда на endpoints. kube-proxy - компонент на каждой ноде, который настраивает правила сети для Service. - Любой запрос на
<любая-нода>:<NodePort>попадает в один из Pod Service.
Минимальный манифест
apiVersion: v1
kind: Service
metadata:
name: nodeapp
spec:
type: NodePort
selector:
app: nodeapp
ports:
- port: 80 # внутренний порт ClusterIP
targetPort: 8080 # порт контейнера
nodePort: 30090 # внешний порт на каждой ноде
Поля
type: NodePort: обязательно. Без него получится обычный ClusterIP.selector: какие Pod принимают трафик. Должен совпадать сlabelsу Pod template в Deployment.port: внутренний порт ClusterIP. Прочие Service в кластере ходят сюда.targetPort: порт контейнера, куда Service форвардит. Должен совпадать сcontainerPortв Pod.nodePort: внешний порт. Если не указать, Kubernetes сам выберет свободный из диапазона. Полезно, когда нужен предсказуемый адрес.
Диапазон портов
По умолчанию 30000-32767. Это компромисс: достаточно высокий, чтобы
не конфликтовать с системными портами, и достаточно широкий, чтобы
один кластер выдерживал сотни сервисов.
Если попытаться задать nodePort: 80, API-сервер ответит:
provided port is not in the valid range. The range of valid ports is 30000-32767
Диапазон расширяется флагом --service-node-port-range у
kube-apiserver, но это пересборка кластера ради порта: не делают.
Как проверить
Сначала смотрят, что Service создан правильно и нашёл Pod:
kubectl get svc nodeapp
# колонка PORT(S): 80:30090/TCP
kubectl get endpoints nodeapp
# непустой список IP = Service реально указывает на под
Объект v1 Endpoints deprecated с 1.33, и kubectl печатает про это
предупреждение. Тот же список адресов через актуальный объект:
kubectl get endpointslice -l kubernetes.io/service-name=nodeapp
Дёрнуть NodePort запросом можно изнутри кластера: временный pod шлёт
запрос на IP-ноды:30090.
NODE_IP=$(kubectl get nodes \
-o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
kubectl run np-check --rm -it --restart=Never \
--image=busybox:1.36 -- wget -qO- http://$NODE_IP:30090
# должна прийти nginx welcome page
В виртуализованных учебных кластерах NodePort не всегда маршрутизируется
через InternalIP ноды, и проба может не достучаться. Главный критерий,
что NodePort настроен верно: Service с type: NodePort, нужный
nodePort и непустые endpoints на живой под.
Что важно помнить
Один порт на сервис на всю ноду
Если два разных Service попросят nodePort: 30090, второй упадёт
с port is already allocated. На одну ноду каждый NodePort уникален.
Все ноды слушают этот порт
Даже те, где нет ни одного Pod этого Service. kube-proxy всё равно форвардит трафик на ClusterIP. Это упрощает балансировщики снаружи: любой адрес ноды подходит.
externalTrafficPolicy: Cluster vs Local
По умолчанию Cluster: запрос с ноды A может попасть на под на ноде B
(через ClusterIP). Это балансирует нагрузку, но теряет client IP.
Local: трафик идёт только в Pod на той же ноде, куда пришёл запрос.
Сохраняет client IP, но если на ноде нет Pod: Connection refused.
| Значение | Куда попадёт запрос | Client IP | Если на ноде нет Pod |
|---|---|---|---|
Cluster (по умолчанию) | в любой Pod Service, хоть на другой ноде | теряется | запрос всё равно дойдёт |
Local | только в Pod на этой же ноде | сохраняется | Connection refused |
spec:
type: NodePort
externalTrafficPolicy: Local
Когда использовать NodePort
- Локальная разработка и быстрая проверка.
- Минимальный кластер без LoadBalancer-провайдера.
- За балансировщиком на L4 (HAProxy, AWS NLB), который шлёт на
<любая-нода>:30090.
Когда не использовать
- В проде с большим количеством сервисов. Фиксированные высокие
порты неудобны для DNS, не сразу понятно
:30090что это. - Когда нужно HTTPS termination. NodePort это L4, сертификат надо ставить на саму Pod или на Ingress.
- Когда нужны host-based / path-based правила. Это Ingress или Gateway API, они разбираются дальше в треке.
NodePort открывает порт на всех нодах кластера, включая те, где нет ни одного Pod этого Service. Без firewall перед нодами этот порт виден из интернета. В проде ноды прячут за балансировщиком или ограничивают сетевыми правилами.
Быстрый путь без YAML
kubectl create deployment nodeapp --image=nginxinc/nginx-unprivileged:1.25
kubectl expose deployment nodeapp --port=80 --target-port=8080 --type=NodePort
# какой порт выдал кластер
kubectl get svc nodeapp -o jsonpath='{.spec.ports[0].nodePort}'
kubectl expose без явного nodePort берёт случайный порт из
диапазона. Для предсказуемого адреса порт дописывают в манифест руками.
Коротко
type: NodePortоткрывает один и тот же порт из диапазона30000-32767на всех нодах кластера.- Поверх NodePort всегда остаётся ClusterIP, NodePort просто добавляет внешний вход.
- Порт уникален в пределах кластера: два Service на один
nodePortне уживутся. externalTrafficPolicy: Localсохраняет client IP, но ломает запрос, если на ноде нет Pod.- Для прода NodePort годится только за балансировщиком, HTTPS и правила по хостам делают Ingress или Gateway API.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс