Трендовые github проекты в нашем телеграм канале. Подпишись → Компактный кластер для сервисов с предсказуемым деплоем
K3s позволяет собрать рабочую Kubernetes-среду на одной виртуальной машине и получить привычные механизмы оркестратора: декларативные манифесты, self-healing для подов, rolling update, Service, Ingress и постоянные тома. Такой стенд полезен для небольшого сервиса, пет-проекта и обучения эксплуатационным практикам на реальной нагрузке.
Одноузловая схема имеет чёткую границу: кластер переживает сбой отдельного процесса или пода, но потеря самой виртуальной машины остановит все сервисы. Высокая доступность на уровне инфраструктуры требует нескольких control-plane и worker-узлов, репликации хранилища и независимых зон отказа. Это важно зафиксировать до начала работы, чтобы возможности кластера соответствовали ожиданиям.
Почему для старта подходит K3s
Полная установка Kubernetes требует заметного объёма ресурсов и внимания к отдельным компонентам control plane. K3s упаковывает лёгкую конфигурацию Kubernetes в один дистрибутив и сохраняет совместимость со стандартным API и манифестами. В базовой конфигурации доступны CoreDNS, Traefik в роли ingress-контроллера и Local Path Provisioner для PersistentVolumeClaim.
Для небольшого набора сервисов разумной отправной точкой будет виртуальная машина с 2 vCPU, 4 ГБ оперативной памяти и SSD-диском от 30 ГБ. Память понадобится самому кластеру, PostgreSQL, приложению и временным задачам CI. SSD снижает задержки для persistent volume и ускоряет работу с образами. Цифры остаются ориентиром: перед вводом в эксплуатацию стоит измерить потребление именно своих контейнеров.
У каждого контейнера следует описать requests и limits. Requests участвуют в планировании и резервируют минимум ресурсов; limits не дают одному процессу занять весь CPU или всю память узла. Например:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
При CPU limit приложение может отвечать медленнее под всплеском трафика, зато узел сохранит ресурс для DNS, ingress-контроллера, базы и остальных сервисов. Лимит памяти требует более внимательного расчёта: превышение обычно приводит к завершению контейнера по OOM.
Состояние PostgreSQL: StatefulSet, PVC и секреты
База данных требует постоянного хранилища и стабильной идентичности экземпляра. Для неё подходит StatefulSet с volumeClaimTemplates: контроллер создаёт PVC, а после пересоздания пода подключает прежний том. Удаление postgres-0 в таком сценарии не означает автоматическую потерю файлов данных — новый под получит связанный persistent volume.
Одна реплика PostgreSQL на одном PVC решает задачу сохранности данных при перезапуске пода. Она не создаёт кластер баз данных и не обеспечивает продолжение записи после сбоя узла. Для репликации и переключения роли понадобятся отдельная архитектура PostgreSQL, несколько узлов и продуманный механизм failover.
Параметры подключения и пароли храните в Kubernetes Secret. Имена ресурсов должны быть однозначными: Secret с учётными данными приложения и Secret типа kubernetes.io/dockerconfigjson для доступа к registry — разные объекты. Изменение типа уже созданного секрета Kubernetes не разрешает, поэтому ошибка конфигурации исправляется удалением или созданием нового объекта с корректным именем.
Для асинхронного Python-приложения строку подключения можно собрать из переменных окружения:
- name: DATABASE_URL
value: "postgresql+asyncpg://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)"
Префикс postgresql+asyncpg выбирает диалект PostgreSQL и асинхронный драйвер для SQLAlchemy. Передавать секреты в репозиторий или образ контейнера нельзя: используйте Secret, ограничения доступа RBAC и отдельные значения для окружений.
Маршрутизация, TLS и готовность приложений
Внешний трафик удобно завести через Ingress: путь / направляется во frontend-service, а /api — во backend-service. Статический фронтенд раздаёт Nginx, API-запросы идут к сервису бэкенда напрямую через ingress-контроллер. Такая схема исключает лишний прокси-переход внутри фронтенд-пода.
Если FastAPI ожидает маршруты без префикса /api, Traefik может удалить его Middleware StripPrefix. При этом особенно важны namespace и имя провайдера в аннотации Ingress. Для CRD Traefik используется суффикс @kubernetescrd; неверный провайдер оставит маршрут недоступным при исправных подах и Service.
TLS-сертификаты можно выпускать cert-manager через ACME HTTP-01 challenge. Для успешной проверки необходимы корректный DNS, адрес электронной почты в ClusterIssuer и доступ робота центра сертификации к портам 80 и 443. Диагностику удобно вести по цепочке ресурсов:
kubectl get certificate
kubectl describe order
kubectl describe certificaterequest
Готовность сервиса к трафику определяет readinessProbe. Пока endpoint не вернул ожидаемый ответ, pod не входит в endpoints Service. livenessProbe контролирует зависшие процессы и инициирует перезапуск контейнера. Пробы должны проверять лёгкий endpoint без обращения к медленным внешним системам, иначе они сами станут причиной нестабильности.
Деплой из CI/CD внутри кластера
GitLab Runner можно запускать в отдельном namespace и выдать ему ServiceAccount с минимально необходимыми правами. Широкая роль cluster-admin упрощает первые эксперименты, однако для постоянного контура безопаснее выделить Role или ClusterRole только на нужные namespace и типы ресурсов.
Сборка образов Docker-in-Docker часто требует privileged-режима и добавляет сетевые сложности. Kaniko собирает контейнерные образы внутри Kubernetes без Docker daemon, используя контекст репозитория и конфигурацию авторизации registry. После публикации образа задача deploy применяет манифесты обычной командой:
kubectl apply -f k3s/ --recursive
Такой pipeline напрямую обращается к Kubernetes API, а состояние окружения остаётся описанным в Git-репозитории. Версии образов лучше фиксировать тегом коммита или релиза: тег latest усложняет откат и расследование инцидентов.
Что проверять после настройки
Манифесты приобретают ценность после проверки отказов. Для rolling update задайте maxSurge: 1, maxUnavailable: 0 и readiness probe. Во время обновления Kubernetes создаст новую реплику, дождётся её готовности и только затем завершит старую. Параллельный цикл запросов к сервису покажет, были ли ошибки в момент выкладки.
Отдельно удалите один stateless pod: ReplicaSet должен восстановить его, а Service направит трафик на оставшиеся готовые реплики. Затем проверьте PostgreSQL: удаление pod StatefulSet должно привести к повторному подключению PVC и сохранению данных. Нагрузочный тест поможет увидеть, как CPU limits влияют на задержки и не лишают ли соседние сервисы ресурсов.
Эти проверки стоит дополнить резервным копированием базы, мониторингом метрик, логированием и тестом восстановления на отдельном окружении. Кластер становится надёжнее благодаря регулярно подтверждаемым сценариям восстановления, а не только набору YAML-файлов.