В этом материале мы развернём простое веб-приложение на кластере Kubernetes и разберём, как взаимодействуют его ключевые абстракции — pod, deployment, service и ingress. Мы покажем, как эти компоненты обеспечивают контролируемый доступ, отказоустойчивость и масштабируемость приложения, а также как организовать подобное развертывание на VPS.
Как взаимодействуют компоненты Kubernetes
Компоненты Kubernetes работают по модели, построенной на принципе декларативного управления: вы описываете желаемое состояние в YAML-манифестах (например, «должно работать 3 копии пода с образом Nginx»), а система непрерывно стремится привести реальное состояние к этому описанию.
Взаимодействие ключевых объектов, которые мы будем использовать, можно представить в виде слоёв, каждый из которых решает свою конкретную задачу:
- Pod (под). Объединяет один или несколько контейнеров с общим сетевым пространством (IP-адрес) и ресурсами хранилища. Однако поды в кластере смертны, их IP-адрес меняется при пересоздании, они не самовосстанавливаются и не масштабируются.
- Deployment (развёртывание). Это следующий уровень абстракции, контроллер, который управляет жизненным циклом подов. Вы описываете ему желаемое состояние: какой образ использовать, сколько реплик пода должно работать постоянно, стратегию обновления (например, rolling-update). Deployment'ы непрерывно следят за состоянием подов через API-сервер. Если под умирает, deployment создаёт новый. Если вы меняете образ в конфигурации, deployment плавно разворачивает новую версию. Именно он обеспечивает отказоустойчивость и возможность легкого обновления приложения.
- Service (сервис). Так как поды динамичны, нужно обеспечить стабильный сетевой доступ к ним — для этого используется service: он определяет логический набор подов (обычно через селектор меток) и правила доступа к ним. Сервис получает статический IP-адрес и DNS-имя, которые не меняются на протяжении всего его жизненного цикла. Его основная роль — быть постоянным сетевым эндпоинтом и балансировщиком нагрузки для всех подов, в данный момент входящих в этот набор.
- Ingress (вход). Ingress управляет приходящим извне HTTP/HTTPS-трафиком. Он не является типом сервиса, а действует как маршрутизатор на прикладном уровне модели OSI. Вы определяете правила, например, трафик на домен my-app.test должен перенаправляться на сервис web, эти правила выполняет ingress controller — приложение (например, на основе Nginx или Traefik), работающее в кластере и постоянно следящее за изменениями объектов ingress.
Поток запроса выглядит так:
- Внешний пользователь делает запрос по адресу my-app.test.
- DNS направляет запрос на IP ingress controller'а.
- Ingress controller, проверяя правила всех ingress-ресурсов, определяет, что запрос для my-app.test нужно направить на определённый service, например, web.
- Service web, в свою очередь, принимает запрос и, действуя как внутренний балансировщик нагрузки, направляет его на один из здоровых подов, входящих в селектор.
Создание деплоймента для управления подами
Cоздавать под напрямую — табу в продакшене (единичный под не будет перезапущен при сбое и его невозможно масштабировать). Для этого используется деплоймент, который непрерывно следит за подами, поддерживает их количество и конфигурацию соответствующими описанному состоянию.
Создадим манифест deployment.yaml, который развернёт три реплики Nginx:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: nginx-container
image: nginx:1.25
ports:
- containerPort: 80
Разберём основные параметры:
- apiVersion: apps/v1 — указывает на версию API Kubernetes для работы с деплойментом;
- kind: Deployment — определяет тип ресурса;
- replicas: 3 — количество идентичных копий нашего приложения;
- selector.matchLabels — метки, по ним деплоймент находит поды, которыми он должен управлять;
- spec.template — шаблон по которому будут создаваться поды; именно здесь описываются сами контейнеры;
- image: nginx:1.25 — указывается конкретная версия образа для стабильности; не указывайте образ без тега (например, nginx без 1.25) и не используйте тег latest — это может привести к незапланированному обновлению.
- containerPort: 80 — порт контейнера.
Применим и проверим результат:
kubectl apply -f deployment.yaml
kubectl get deployment web-app
kubectl get pods -l app=web-app -o wide
Последняя команда покажет, как поды распределены по worker-нодам, демонстрируя работу scheduler'а (компонента, отвечающего за распределение подов по узлам кластера).
Создание service для стабильного доступа
Поды, управляемые деплойментом, могут быть убиты и пересозданы при обновлении или сбое, и их IP изменятся. Для предоставления стабильной точки доступа к динамически меняющимся подам используется service. Он предоставляет постоянный виртуальный IP и DNS-имя внутри кластера, направляя запросы на здоровые поды, соответствующие его селекторам.
Создадим манифест service.yaml:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
Объяснение директив:
- app: web-app — параметр, связывающий этот сервис с подами, у которых есть указанная метка;
- port: 80 — порт сервиса внутри кластера;
- targetPort: 80 — порт контейнера, куда будет перенаправлен трафик.
Развернём сервис и протестируем его:
kubectl apply -f service.yaml
kubectl get svc web
Теперь мы можем получить доступ к приложению изнутри кластера по внутреннему IP сервиса или по его DNS-имени web.default.svc.cluster.local. Протестировать это можно, запустив временный под с утилитой curl:
kubectl run curl-test --image=curlimages/curl --rm -it -- curl -s http://web.default.svc.cluster.local
Эта команда создаст под, выполнит запрос к сервису и выведет HTML-код главной страницы Nginx, после чего удалит под (--rm).
Обеспечение внешнего доступа через ingress
Созданный сервис не доступен извне кластера. Для организации контролируемого входящего HTTP/HTTPS-трафика используется ресурс ingress. Однако для его работы требуется ingress controller — приложение, которое развёрнуто в кластере и отслеживает создание объектов ingress для настройки балансировщика нагрузки.
Установим популярный Nginx Ingress Controller через Helm (менеджер пакетов Kubernetes; предполагается, что он установлен).
Добавление репозитория и установка контроллера:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --set controller.publishService.enabled=true
Дождёмся, пока статус пода контроллера не станет Running, проверка:
kubectl get pods -n default -l app.kubernetes.io/name=ingress-nginx --watch
Создадим ingress.yaml, который будет перенаправлять трафик с любого доменного имени (в нашем тестовом окружении) на сервис web:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Применим и узнаем внешний IP, полученный ingress controller'ом:
kubectl apply -f ingress.yaml
kubectl get ingress web-ingress
Если контроллер работает в среде без поддержки LoadBalancer (например, в локальном кластере), статус может быть <pending>. В этом случае можно использовать NodePort, который открывает порт на всех нодах. Однако способ с NodePort — это доступ в обход Ingress Controller, который лишает всех его преимуществ (роутинг по доменам, SSL и т. д.). Порт можно указать (из диапазона 30000–32767), но если не указывать, NodePort выберет случайный:
kubectl get svc ingress-nginx-controller -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}'
Теперь приложение доступно по адресу любой ноды и этому порту, например:
http://k8s-worker01:30752
Чтобы использовать доменное имя для теста, добавим запись в локальный файл hosts. Выполните на вашей рабочей машине (не в кластере), подставив актуальный IP ноды:
echo "192.168.1.101 my-app.test" | sudo tee -a /etc/hosts
После этого приложение будет доступно в браузере по адресу http://my-app.test.
Основные операции управления и проверка работоспособности
Масштабирование. Увеличим количество реплик приложения до пяти командой, не редактируя исходный манифест:
kubectl scale deployment/web-app --replicas=5
Сразу же проверим, как Kubernetes запускает новые поды:
kubectl get pods -l app=web-app --watch
Обновление. Сымитируем процесс деплоя новой версии приложения. Обновим версию образа Nginx в деплойменте:
kubectl set image deployment/web-app nginx-container=nginx:1.26
Начнётся плавное rolling-update: Kubernetes поочерёдно создаст поды с новой версией и, после их успешного запуска, удалит старые. Это гарантирует нулевое время простоя. Процесс можно наблюдать:
kubectl rollout status deployment/web-app
Отказоустойчивость. Симулируем сбой, просто удалив один под:
kubectl delete pod/web-app-<RANDOM-ID>
Деплоймент немедленно зафиксирует, что текущее состояние (4 пода) не соответствует желаемому (5 подов), и создаст новый под взамен удалённого. Это также можно наблюдать в реальном времени:
kubectl get pods -l app=web-app
Что делать, если ingress не работает
Если Ingress возвращает ошибки (например, 504 Gateway Timeout или 404):
Проверьте логи ingress controller'а. В них содержится подробная информация о том, куда и почему маршрутизируется (или не маршрутизируется) трафик. Найдите под с контроллером и посмотрите его логи:
kubectl get pods -l app.kubernetes.io/name=ingress-nginx
kubectl logs <ingress-controller-pod-name>
Проверьте аннотации ingress. Для расширенной конфигурации (например, настройки SSL-редиректа или кастомных размеров буфера) используются аннотации. Если вы ими пользуетесь, то убедитесь, что в ingress-ресурсе они указаны корректно:
kubectl describe ingress web-ingress
Проверьте эндпоинты сервиса и убедитесь, что он правильно находит свои поды. Колонка ENDPOINTS должна содержать IP-адреса работающих подов:
kubectl describe svc web
Или:
kubectl get ep web
Если эта колонка пуста, значит, селектор сервиса не совпадает с метками на подах, или поды не готовы.
Заключение
Теперь приложение развёрнуто и готово к работе, но для продакшена этого недостаточно. Что ещё нужно сделать:
- Установите приложения для наблюдения за состоянием кластера. Можно начать с kube-prometheus-stack — это готовое решение, включающее в себя Prometheus (сбор метрик) и Grafana (их визуализация). Для агрегации логов рассмотрите связку Loki и Promtail.
- Настройте Liveness и Readiness Probes для ваших контейнеров в манифесте деплоймента. Они укажут Kubernetes, как проверить, здоров ли контейнер и готов ли он принимать трафик. Без них работа механизмов самовосстановления и обновлений не будет полноценной.
- Освойте работу с объектами ConfigMap и Secret для хранения конфигурации и чувствительных данных отдельно от образов приложений.
- Для stateful-приложений (базы данных, кеши) изучите механизмы PersistentVolume (PV) и PersistentVolumeClaim (PVC). Настройте динамическое выделение дискового пространства (provisioning), чтобы поды могли автоматически запрашивать нужный объем памяти.
- Изучите концепции ServiceAccount, Role и RoleBinding для настройки ролевой модели доступа (RBAC) внутри кластера, и ограничьте права служб и пользователей в соответствии с принципом наименьших привилегий.
- Настройте автоматизированный пайплайн развертывания приложений в кластер. Для этого можно использовать связку GitLab CI + Argo CD или Jenkins + Spinnaker, которые позволяют реализовать практики GitOps и непрерывной доставки.
Читайте в блоге:
- Интеграция Telegram-бота с веб-приложением на Flask: деплой и безопасность
- Автоматический деплой через Jenkins: установка и CI/CD пайплайн
- Деплой на VPS без проблем: как автоматизировать процесс и забыть о рутине