В Docker можно запустить один контейнер и открыть его порт. В Kubernetes нужно решить больше задач: создать нужное число экземпляров приложения, заменить исчезнувший экземпляр и направить запросы к работающим контейнерам. Эти обязанности разделены между несколькими объектами.
Разберём, как связаны Pod, Deployment, Service и Ingress, а затем пройдём один HTTP-запрос от локального компьютера до Nginx в Kubernetes. В конце вы сможете отличить проблему запуска контейнера от проблемы сетевого маршрута и воспроизвести ошибку селектора Service.
Для практики понадобятся работающий Docker, утилиты k3d и kubectl, curl и свободный порт 8081. Предполагается знакомство с контейнерами, HTTP и терминалом. Используйте только одноразовый локальный кластер; не направляйте команды на чужой или рабочий Kubernetes.
Что такое Kubernetes
Kubernetes — платформа для оркестрации контейнерных приложений. Вы описываете желаемое состояние системы: какие контейнеры нужны, в каком количестве и как их обнаружить по сети. Управляющие компоненты сверяют фактическое состояние с заданным и предпринимают действия для их согласования.
Кластер включает control plane (компоненты управления) и узлы (nodes), на которых размещаются рабочие нагрузки. Команда kubectl обращается к Kubernetes API, а не запускает контейнер сама. API принимает описания объектов; контроллеры отвечают за изменение состояния.
Четыре объекта в этой статье решают разные задачи. Pod объединяет контейнеры, запускаемые вместе. Deployment управляет числом Pod и обновлениями через ReplicaSet. Service предоставляет постоянную точку обращения к выбранным Pod. Ingress описывает HTTP-маршрут к Service; исполняет его отдельный контроллер.
Здесь действуют две связанные цепочки: управление экземплярами (Deployment → ReplicaSet → Pod) и логическая маршрутизация (Ingress-контроллер → Service → Pod). Подробности об управляющих компонентах приведены в обзоре Kubernetes.
На схеме xnova.local — условное значение заголовка Host. В манифесте ниже поле host не задано, поэтому запрос по адресу 127.0.0.1:8081 также подходит под правило Ingress.
Стрелки через Service обозначают логическую связь объектов, а не обязательный сетевой переход через ClusterIP. По умолчанию Traefik направляет запросы непосредственно на IP-адреса Pod, выбранных через Service. Это объясняется в документации Traefik; при другой конфигурации контроллер может обращаться к ClusterIP.
Pod: где работает контейнер
Pod — минимальная развёртываемая единица Kubernetes, содержащая один или несколько контейнеров. Контейнеры одного Pod размещаются вместе и совместно используют сетевое пространство; они могут обращаться друг к другу через localhost. При необходимости они разделяют подключённые к Pod тома данных.
В простом примере Pod содержит один Nginx, слушающий порт 80. Два Pod — это две отдельные копии приложения со своими сетевыми адресами, а не два контейнера в одном Pod.
Pod имеет жизненный цикл. При замене приложения контроллер может создать новый Pod с другим именем и IP-адресом. Поэтому жёстко записанный IP отдельного Pod не подходит в качестве адреса сервиса. Для сетевого обнаружения используют Service, который выбирает подходящие Pod по меткам.
Создавать Pod вручную полезно для эксперимента, но поддерживать работающее приложение удобнее через Deployment. Официальная документация Pod подробно описывает совместные ресурсы и правила размещения контейнеров.
Deployment: как поддерживать число реплик
Deployment управляет ReplicaSet и поддерживает заданное количество Pod. В определении Deployment указывают шаблон Pod, образ контейнера, число реплик и селектор меток.
В примере ниже replicas: 2 означает, что контроллер должен поддерживать два Pod с Nginx. Поле spec.selector.matchLabels у Deployment должно соответствовать spec.template.metadata.labels. У уже созданного Deployment этот селектор нельзя произвольно менять.
При исчезновении Pod ReplicaSet стремится создать замену. При изменении шаблона Pod Deployment выполняет развёртывание новой версии через ReplicaSet; в зависимости от стратегии некоторое время могут существовать Pod разных версий.
Команда kubectl rollout status deployment/xnova-web отслеживает процесс обновления, а kubectl get deployment,replicaset,pods показывает объекты разных уровней. При этом наличие двух Pod ещё не доказывает, что извне доступен HTTP-сервис: проверка запросом выполняется отдельно.
Механизм описан в документации Deployment.
Service: постоянная точка обращения
IP-адреса Pod могут меняться при замене или обновлении приложения. Service объединяет подходящие конечные адреса и даёт клиентам стабильную точку обращения.
Service с типом ClusterIP даёт стабильную точку обращения внутри кластера, даже когда отдельные Pod меняются. Для обычного Service с селектором Kubernetes обнаруживает Pod по меткам и представляет их адреса через объекты EndpointSlice. EndpointSlice позволяет увидеть адреса и условия готовности конечных точек, которые относятся к Service.
Селектор Service app: xnova-web совпадает с меткой шаблона Pod. Если изменить его на app: missing, Service останется в кластере, но не найдёт подходящие экземпляры приложения. Исправность Deployment и наличие Service ещё не означают, что трафик действительно попадает в контейнер.
Различайте три значения в манифестах:
name: httpвместе сcontainerPort: 80задаёт именованный порт контейнера Nginx.port: 80задаёт порт, на котором Service принимает обращения.targetPort: httpуказывает на порт контейнера с именемhttp.
Наличие containerPort само по себе не публикует порт за пределы кластера. Обычный ClusterIP также не означает, что HTTP доступен из браузера на компьютере. Для этого понадобится дополнительный внешний маршрут.
Читайте определения и ограничения в документации Service и EndpointSlice. При диагностике смотрите не только наличие EndpointSlice, но и его endpoints и условия готовности.
Ingress: как направить HTTP-запрос к Service
Ingress задаёт правила HTTP/HTTPS-маршрутизации к Service; без Ingress-контроллера эти правила не обеспечивают доставку запросов. Объект Ingress содержит имя Service, его порт, а также правила выбора маршрута по хосту и пути. Контроллер наблюдает за этими объектами и настраивает HTTP-маршрутизацию.
В локальном примере правило с путём / направляет запросы на Service xnova-web. Поле ingressClassName: traefik указывает, какой класс контроллера должен обрабатывать это правило. Одного Ingress-объекта в Kubernetes API недостаточно: контроллер должен быть установлен, а его входной порт — доступен клиенту.
В K3s Traefik устанавливается по умолчанию, если его явно не отключили. Через k3d мы запускаем K3s в контейнерах Docker. В K3s у Traefik собственный Service типа LoadBalancer, а у нашего Nginx остаётся обычный ClusterIP. Эти Service не следует путать. Подробнее о стандартном Traefik и ServiceLB рассказано в документации K3s.
API Ingress стабилен, но его развитие заморожено: Kubernetes рекомендует Gateway API для новых сценариев маршрутизации. Отдельно стоит учитывать контроллеры. Ingress NGINX завершил сопровождение 24 марта 2026 года. Это относится к конкретному community-проекту, а не к работоспособности Kubernetes Ingress API. См. описание Ingress-контроллеров и сообщение Kubernetes о завершении сопровождения.
Для локальной практики достаточно HTTP: TLS здесь намеренно не настраиваем. Публикация реального приложения потребует отдельного решения о сертификатах, сети и доступе.
Практика: запускаем три объекта и проверяем HTTP
Будем использовать один локальный кластер xnova-article, пространство имён xnova-k8s-article и Nginx с двумя репликами. Сначала убедитесь, что Docker работает, а нужные команды доступны:
docker version
k3d version
kubectl version --client
curl --version
Создайте кластер с отображением порта 80 входного маршрутизатора на 127.0.0.1:8081 компьютера:
k3d cluster create xnova-article -p '127.0.0.1:8081:80@loadbalancer'
kubectl --context k3d-xnova-article get nodes
kubectl --context k3d-xnova-article get ingressclass
kubectl --context k3d-xnova-article create namespace xnova-k8s-article
Синтаксис отображения порта документирован в руководстве k3d. В списке IngressClass должен присутствовать traefik. Если его нет, проверьте конфигурацию и запуск контроллера, прежде чем продолжать. Параметр --context указан явно, чтобы команды не затронули другой кластер.
Создайте файл stack.yaml со всеми тремя объектами:
apiVersion: apps/v1
kind: Deployment
metadata:
name: xnova-web
namespace: xnova-k8s-article
spec:
replicas: 2
selector:
matchLabels:
app: xnova-web
template:
metadata:
labels:
app: xnova-web
spec:
containers:
- name: web
image: nginx:stable-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: http
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: xnova-web
namespace: xnova-k8s-article
spec:
type: ClusterIP
selector:
app: xnova-web
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: xnova-web
namespace: xnova-k8s-article
spec:
ingressClassName: traefik
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: xnova-web
port:
name: http
В Deployment используется Nginx с закреплённым SHA-256 образа. Такое закрепление позволяет не зависеть от изменений тега. Доступность образа на вашей архитектуре и фактический запуск контейнера нужно проверить в выбранном кластере; собственный Dockerfile не требуется.
Проверка readinessProbe отправляет HTTP-запрос к / на именованный порт http. Пока она не проходит, Pod не получает трафик через обычный Service, хотя контейнер может уже работать. Это поведение описано в документации Kubernetes.
Примените манифест и проверьте результат:
kubectl --context k3d-xnova-article apply -f stack.yaml
kubectl --context k3d-xnova-article -n xnova-k8s-article rollout status deployment/xnova-web --timeout=180s
kubectl --context k3d-xnova-article -n xnova-k8s-article get deployment,replicaset,pods -o wide
kubectl --context k3d-xnova-article -n xnova-k8s-article get service,ingress
kubectl --context k3d-xnova-article -n xnova-k8s-article get endpointslices -l kubernetes.io/service-name=xnova-web -o yaml
Сверьте количество готовых Pod с replicas: 2. У Service должен быть ClusterIP, а EndpointSlice должен содержать адреса готовых Pod. При первом запуске Traefik может потребоваться время на загрузку; проверьте его состояние в kube-system.
Теперь обратитесь к внешнему порту:
curl -i --max-time 10 http://127.0.0.1:8081/
При исправной конфигурации ожидается HTTP 200 и стандартная страница Nginx. В локальном запуске с k3d v5.9.0, K3s v1.35.5+k3s1 и Traefik 3.6.13 наблюдались две готовые реплики, два адреса с ready: true в EndpointSlice и ответ HTTP 200.
Манифесты и команды для повторения примера собраны в репозитории XNova на зафиксированной версии. Там используется тот же stack.yaml, который приведён выше.
Логическая цепочка запроса: клиент → порт k3d → Traefik → Service xnova-web:80 → Pod → Nginx:80. Service определяет получателя, но не всегда служит отдельным сетевым переходом через ClusterIP. Deployment не обрабатывает HTTP-трафик: он создаёт и поддерживает Pod.
После выполнения удалите только созданный кластер:
k3d cluster list
k3d cluster delete xnova-article
Если запрос не проходит: последовательная диагностика
1. Проверьте запуск и готовность Pod. Ошибки загрузки образа, запуска контейнера и readiness probe проявляются до сетевого маршрута:
kubectl --context k3d-xnova-article -n xnova-k8s-article get pods -o wide
kubectl --context k3d-xnova-article -n xnova-k8s-article describe deployment xnova-web
kubectl --context k3d-xnova-article -n xnova-k8s-article logs deployment/xnova-web --tail=30
Если видите ImagePullBackOff, изучите события получения образа. Если Pod постоянно перезапускается, проверьте причину завершения контейнера и журнал. При 0/2 готовых реплик сначала разберитесь с приложением, а уже потом с Ingress.
2. Проверьте селектор Service. В одноразовом кластере временно замените его на несуществующую метку:
kubectl --context k3d-xnova-article -n xnova-k8s-article set selector service/xnova-web app=missing
kubectl --context k3d-xnova-article -n xnova-k8s-article get endpointslices -l kubernetes.io/service-name=xnova-web -o yaml
curl -i --max-time 10 http://127.0.0.1:8081/
Pod продолжат работать с меткой app: xnova-web, но после согласования состояния Service не должен иметь готовых адресов для запроса. Отсутствие адресов в EndpointSlice — главное свидетельство ошибки. Точный HTTP-код ошибки на входе зависит от контроллера, поэтому не привязывайте диагностику к единственному значению 503.
В локальном запуске после изменения селектора на app=missing оба Pod остались Ready, EndpointSlice потерял адреса, а Traefik вернул HTTP 503. После восстановления селектора снова появились два готовых адреса, и запрос получил HTTP 200.
Исправьте селектор и проверьте восстановление:
kubectl --context k3d-xnova-article -n xnova-k8s-article set selector service/xnova-web app=xnova-web
kubectl --context k3d-xnova-article -n xnova-k8s-article get endpointslices -l kubernetes.io/service-name=xnova-web -o yaml
curl -i --max-time 10 http://127.0.0.1:8081/
3. Если EndpointSlice содержит готовые адреса, проверьте Ingress. Сопоставьте класс контроллера, имя Service, порт и отображение 8081:80:
kubectl --context k3d-xnova-article -n xnova-k8s-article describe ingress xnova-web
kubectl --context k3d-xnova-article get ingressclass
kubectl --context k3d-xnova-article -n kube-system get pods,service
k3d cluster list
При необходимости выполните kubectl --context k3d-xnova-article -n xnova-k8s-article port-forward service/xnova-web 8082:80 в отдельном терминале и затем curl -i http://127.0.0.1:8082/. Если этот запрос проходит, приложение и Service достижимы через туннель kubectl, а внешний путь через Ingress ещё предстоит проверить.
Разделяйте сетевой отказ (нет TCP-соединения с 8081) и HTTP-ответ, который уже сформировал работающий прокси. Это значительно быстрее, чем удалять Pod или пересоздавать весь кластер при каждом сбое.
Итог: четыре разные обязанности
Pod объединяет контейнеры и предоставляет им общее окружение. Deployment через ReplicaSet поддерживает заданное число Pod и управляет обновлениями. Service связывает меняющиеся экземпляры с постоянным сетевым именем и портом. Ingress описывает входящий HTTP-маршрут к Service, а контроллер реализует его в сети.
В исправном локальном примере ожидаются две готовые реплики Nginx, адреса Pod в EndpointSlice и HTTP-ответ через Traefik. Отрицательный пример с неверным селектором показывает, что запущенные Pod ещё не гарантируют работу Service. Для диагностики проверяйте Pod, метки и EndpointSlice, затем IngressClass и опубликованный порт.
Этот пример не покрывает TLS, постоянное хранилище, сетевые политики, управление правами и отказоустойчивость кластера. Для рабочего внедрения нужны отдельные решения по этим вопросам, а для новых внешних маршрутов стоит рассмотреть Gateway API и жизненный цикл выбранного контроллера.
Источники
- Компоненты кластера и модель Kubernetes
- Состав Pod, совместные сеть и жизненный цикл контейнеров
- Deployment, ReplicaSet, число реплик и процесс обновления
- Service, ClusterIP, labels/selectors и обращение к Pod
- Связь Service с EndpointSlice и набором backend адресов
- Ingress API и правила маршрутизации HTTP/HTTPS
- Почему для исполнения правил Ingress нужен отдельный контроллер и статус API
- Официальная дата завершения сопровождения community Ingress NGINX
- Стандартная установка Traefik в K3s и ServiceLB
- Официальный локальный пример k3d с пробросом порта и Ingress
- Проверка состояния развёртывания kubectl rollout status
- Изменение селектора Service для отрицательного контроля
- Прямая маршрутизация Traefik к адресам Pod и настройка nativeLB
- Проверка readiness и исключение неготового Pod из Service
- Публичный пример Deployment, Service, Ingress и k3d smoke
- Точный проверенный commit Kubernetes-примера