XNOVA
← Все статьи

СТАТЬЯ / engineering

Kubernetes с нуля: Pod, Deployment, Service и Ingress

Что такое Kubernetes, как связаны Pod, Deployment, Service и Ingress, и как запустить Nginx в локальном кластере с проверкой HTTP-маршрута.

Автор: XNova

В 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.

Две связи: Deployment управляет ReplicaSet и двумя Pod; Ingress настраивает Traefik, Service предоставляет адреса через EndpointSlice, а Traefik направляет HTTP прямо на Pod IP

На схеме 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 и жизненный цикл выбранного контроллера.

Источники