Почему Kubernetes вместо Docker Compose? Анализ целесообразности миграции
Переход с Docker Compose на Kubernetes для production-среды решает фундаментальные проблемы управления жизненным циклом приложений. Docker Compose работает как инструмент оркестрации на уровне одной ноды, а Kubernetes создан для распределенных кластеров. Основное различие лежит в парадигме управления: Compose использует императивный подход, где вы явно указываете команды для запуска, а Kubernetes оперирует декларативным состоянием. Вы описываете желаемое состояние системы в YAML-манифестах, а контроллеры кластера непрерывно приводят реальное состояние к этому описанию.
Миграция становится необходимостью при росте нагрузки, требованиях к высокой доступности, работе в multi-host окружениях или необходимости внедрения продвинутых стратегий развертывания, таких как canary-релизы.
Ограничения Docker Compose в production-средах
Docker Compose не имеет встроенного оркестратора для развертывания на нескольких физических хостах или виртуальных машинах. Все сервисы запускаются на одной ноде, что создает единую точку отказа. Механизмы самовосстановления ограничены примитивными политиками перезапуска, которые не учитывают состояние здоровья приложения.
Масштабирование выполняется вручную через команду docker-compose up --scale и не является автоматическим. Сетевые возможности Compose обеспечивают базовую изоляцию, но не предлагают аналогов Kubernetes Network Policies для тонкого контроля трафика между сервисами. Управление конфиденциальными данными, такими как пароли и токены, через переменные окружения или файлы небезопасно при работе на нескольких нодах.
В Compose отсутствуют встроенные механизмы балансировки нагрузки между репликами одного сервиса. Проброс портов на хост-машину работает, но не обеспечивает отказоустойчивости при сбое контейнера.
Что Kubernetes дает для масштабирования и отказоустойчивости
Kubernetes реализует декларативное управление состоянием. Вы определяете, сколько реплик приложения должно работать, а ReplicaSet и Deployment контроллеры гарантируют это количество, автоматически пересоздавая упавшие поды.
Liveness и Readiness Probes обеспечивают self-healing. Liveness Probe определяет, жив ли контейнер, и перезапускает его при сбое. Readiness Probe проверяет, готов ли контейнер принимать трафик, что исключает отправку запросов на нерабочие экземпляры.
Horizontal Pod Autoscaler автоматически увеличивает или уменьшает количество реплик на основе метрик потребления CPU, памяти или пользовательских метрик из Prometheus. Встроенный Service Discovery и балансировка нагрузки работают через kube-proxy и объекты Service. ClusterIP предоставляет внутренний стабильный IP-адрес для доступа к группе подов, NodePort открывает порт на каждой ноде кластера, а LoadBalancer интегрируется с облачными провайдерами для создания внешних балансировщиков.
Ingress-контроллеры, такие как Nginx или Traefik, управляют маршрутизацией HTTP/HTTPS трафика на основе домена и пути, заменяя традиционные reverse proxy.
Подготовка к миграции: анализ приложения и планирование
Системный подход к миграции начинается с детального аудита текущей конфигурации. Не пытайтесь перенести все приложение сразу. Разбейте процесс на этапы, начиная с наименее критичных stateless-компонентов. Это снижает риски и позволяет команде набраться опыта.
Первый шаг - полный анализ файла docker-compose.yml и всех его зависимостей: файлов окружения, volume, пользовательских сетей.
Аудит docker-compose.yml: выявление stateless и stateful компонентов
Классификация компонентов определяет выбор ресурсов Kubernetes. Stateless-сервисы (веб-серверы, API, фоновые workers) не хранят уникальных данных внутри пода и являются кандидатами на использование Deployment. Stateful-сервисы (PostgreSQL, Redis, Kafka) хранят данные или требуют стабильных сетевых идентификаторов и используют StatefulSet.
Проанализируйте секцию volumes: в Compose. Локальные маппинги (./data:/var/lib/postgresql/data) указывают на необходимость PersistentVolume в Kubernetes. Зависимости, описанные в depends_on, в Kubernetes заменяются комбинацией initContainers и правильно настроенных readinessProbe, чтобы сервис БД был готов до запуска приложения.
Планирование архитектуры в Kubernetes: namespaces, сеть, ресурсы
Используйте namespaces для логического разделения окружений. Например, создайте prod, staging, monitoring. Это упрощает управление правами доступа и ресурсами.
Спроектируйте сетевую модель. Внутренняя коммуникация между микросервисами должна идти через Service типа ClusterIP. Внешний доступ к API или веб-интерфейсу организуется через Ingress. Прямой доступ по специфическим протоколам может потребовать Service типа NodePort или LoadBalancer.
Заранее рассчитайте requests и limits для CPU и памяти в манифестах. Requests - гарантированные ресурсы, limits - жесткий лимит. Неправильная настройка ведет к eviction подов при нехватке памяти на ноде. Для хранения данных выберите подходящий storage class: local для тестов, NFS для shared-доступа или cloud-provider volumes (например, AWS EBS, GCE PD).
Выбор стратегии миграции: Big Bang (полный перенос за один раз) или поэтапный (сервис за сервисом). Для большинства проектов рекомендуем поэтапную миграцию, которая позволяет тестировать каждый компонент отдельно. Во время перехода можно временно использовать bridge-сети для связи между контейнерами в Compose и подами в Kubernetes.
Ключевые изменения: от Docker Compose к Kubernetes-манифестам
Трансляция конструкций Docker Compose в объекты Kubernetes - это ядро технической работы. Рассмотрим прямое сопоставление основных элементов.
Сервис в Compose разделяется на два объекта в Kubernetes: Deployment (или StatefulSet) для управления жизненным циклом подов и Service для обеспечения сетевого доступа к ним. Переменные окружения переезжают в ConfigMap и Secret. Тома данных становятся PersistentVolumeClaim. Проброс портов реализуется через Service и containerPort.
Трансляция сервисов: Deployment, Service и Pod
Deployment - основной объект для управления stateless-приложениями. Его манифест содержит метаданные (имя, namespace), spec с количеством реплик, селектором для выбора подов и шаблоном пода.
Внутри шаблона пода определяется один или несколько контейнеров с указанием образа, портов, переменных окружения и точек монтирования томов. Service типа ClusterIP создает стабильную внутреннюю DNS-запись вида <service-name>.<namespace>.svc.cluster.local и балансирует трафик между всеми подами, соответствующими селектору.
# Пример docker-compose.yml для nginx
services:
web:
image: nginx:alpine
ports:
- "8080:80"
environment:
NGINX_HOST: example.com
# Эквивалент в Kubernetes: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
env:
- name: NGINX_HOST
value: "example.com"
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
Управление конфигурацией: ConfigMaps и Secrets вместо environment и env_file
ConfigMap хранит конфигурационные данные в виде пар ключ-значение или целых файлов. Создать его можно из файла: kubectl create configmap app-config --from-file=config.properties, или из литералов: kubectl create configmap app-config --from-literal=log_level=INFO.
ConfigMap подключается к поду как переменные окружения через spec.containers.env.valueFrom.configMapKeyRef или как volume, что позволяет монтировать конфигурационные файлы. Для хранения чувствительных данных используйте Secret. Данные в Secret хранятся в base64-кодировке. Создайте его из файла: kubectl create secret generic db-password --from-literal=password=MyP@ssw0rd.
Никогда не храните Secret в Git-репозитории. Используйте специализированные системы, такие как HashiCorp Vault, или механизмы secret management от облачных провайдеров (AWS Secrets Manager, GCP Secret Manager), интегрируя их с Kubernetes через CSI драйверы.
Сетевой доступ: Services (ClusterIP, NodePort, LoadBalancer) и Ingress
Service в Kubernetes - это абстракция над группой подов. ClusterIP предоставляет внутренний IP-адрес, доступный только внутри кластера. NodePort открывает статический порт на IP-адресе каждой ноды кластера, перенаправляя трафик на сервис. LoadBalancer создает внешний балансировщик нагрузки у облачного провайдера.
Ingress не является типом Service. Это отдельный объект API, который определяет правила маршрутизации HTTP/HTTPS трафика. Для его работы в кластере должен быть развернут Ingress-контроллер, например, ingress-nginx.
# Пример Ingress для маршрутизации по доменам
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
Эта конфигурация направляет трафик с app.example.com на web-service, а с api.example.com - на api-service.
Сложные случаи: миграция stateful-приложений (БД, кеши)
Перенос stateful-приложений - самый ответственный этап миграции. Основная задача - обеспечить сохранность данных и бесперебойную работу службы. Для stateful-нагрузок в Kubernetes используется StatefulSet, а не Deployment.
Перед началом миграции создайте полную резервную копию данных. Для PostgreSQL используйте pg_dumpall, для Redis - SAVE или BGSAVE, для файловых данных - rsync или инструменты бэкапа соответствующего storage-провайдера.
Использование StatefulSet для баз данных
StatefulSet гарантирует предсказуемые и устойчивые идентификаторы для подов. Поды получают имена по шаблону <statefulset-name>-<ordinal>, например, postgres-statefulset-0, postgres-statefulset-1. Эти имена сохраняются при пересоздании пода.
Каждому поду в StatefulSet соответствует свой PersistentVolumeClaim, созданный на основе volumeClaimTemplates. Это обеспечивает уникальное постоянное хранилище для каждой реплики. Для внутреннего discovery реплик БД используется Headless Service (с полем clusterIP: None). Он создает DNS-записи для каждого пода, позволяя репликам находить друг друга по стабильным доменным именам: postgres-statefulset-0.postgres-headless.default.svc.cluster.local.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-statefulset
spec:
serviceName: "postgres-headless"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
Перенос данных и обеспечение persistence
Существует два основных сценария переноса данных. Первый: остановить контейнер БД в Docker Compose, создать дамп данных, развернуть StatefulSet в Kubernetes и импортировать дамп в новую БД. Этот способ подходит для сред, где допустим краткий простой.
Второй сценарий предполагает использование общего хранилища, доступного с любой ноды кластера, например, NFS или cloud storage (AWS EFS, GCP Filestore). В этом случае PersistentVolume в Kubernetes монтирует ту же самую файловую систему, что использовалась Docker volume. Данные остаются на месте, и миграция сводится к переключению endpoint'а приложения с контейнера Compose на под Kubernetes. Проверьте права доступа (fsGroup, runAsUser) в SecurityContext пода, чтобы контейнер мог писать в смонтированный том.
После миграции обязательно протестируйте отказоустойчивость: удалите один из подов StatefulSet командой kubectl delete pod и убедитесь, что контроллер пересоздает его с теми же сетевым идентификатором и томом данных.
Упаковка и управление: Helm-чарты vs Kustomize
После создания набора raw YAML-манифестов встает вопрос управления ими для разных окружений. Два основных инструмента - Helm и Kustomize - предлагают разные парадигмы.
Helm - это пакетный менеджер для Kubernetes, использующий шаблонизацию на основе Go templates. Он подходит для сложных приложений с множеством опциональных компонентов и является стандартом для публикации чартов в репозиториях. Kustomize - это инструмент декларативного наложения патчей, встроенный в kubectl через флаг -k. Он работает по принципу наследования базовой конфигурации и применяет к ней специфичные для окружения изменения.
Выбор зависит от задачи. Для внутренних проектов с необходимостью быстрой настройки под dev/staging/prod часто удобнее Kustomize. Для публикации приложения или управления сложными зависимостями (как в случае с миграцией с использованием Velero) может подойти Helm.
Создание базового Helm-чарта для мигрированного приложения
Инициализируйте структуру чарта командой helm create myapp. Внутри каталога myapp/templates/ разместите ваши YAML-манифесты, преобразовав статические значения в переменные шаблонов Helm вида {{ .Values.image.tag }}.
Параметры, которые могут меняться (версия образа, количество реплик, ресурсы), выносятся в файл values.yaml. Для установки чарта в кластер используйте команду helm install myapp ./myapp, а для обновления - helm upgrade myapp ./myapp.
# Пример фрагмента шаблона deployment.yaml в Helm
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
requests:
memory: {{ .Values.resources.requests.memory }}
cpu: {{ .Values.resources.requests.cpu }}
Настройка Kustomize для разных окружений (dev, prod)
Структура проекта Kustomize обычно выглядит так:
base/
kustomization.yaml # Общие ресурсы (deployment.yaml, service.yaml)
deployment.yaml
service.yaml
overlays/
dev/
kustomization.yaml # Наследует base, добавляет патчи для dev
patch_replicas.yaml
prod/
kustomization.yaml # Наследует base, добавляет патчи для prod
patch_resources.yaml
ingress.yaml
В overlays/dev/kustomization.yaml вы можете уменьшить количество реплик, использовать образ с тегом latest для разработки и задать низкие requests/limits. В overlays/prod/ увеличиваете реплики, указываете стабильный тег образа, настраиваете лимиты ресурсов и добавляете манифест Ingress с TLS. Применить конфигурацию для prod можно командой kubectl apply -k overlays/prod.
Интеграция в CI/CD: адаптация пайплайнов под Kubernetes
После миграции приложения необходимо обновить пайплайны непрерывной интеграции и доставки. Основные этапы CI/CD остаются прежними: сборка образа, тестирование, push в registry. Изменяется этап деплоя.
Вместо команды docker-compose up -d пайплайн должен обновить манифесты в Git (например, подставить новый тег образа в kustomization.yaml или values.yaml Helm) и применить их к кластеру с помощью kubectl apply, helm upgrade или kubectl apply -k.
Безопасная настройка доступа CI-раннера к кластеру
Никогда не используйте админский kubeconfig файл в CI/CD. Создайте отдельный ServiceAccount в namespace, куда происходит деплой.
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-cd-sa
namespace: prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: prod
name: deploy-role
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-cd-rolebinding
namespace: prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: deploy-role
subjects:
- kind: ServiceAccount
name: ci-cd-sa
namespace: prod
Получите токен ServiceAccount и сформируйте kubeconfig, который будет храниться в секретных переменных CI-системы (GitLab CI/CD Variables, GitHub Actions Secrets).
Реализация стратегий развертывания: blue-green и canary в CI/CD
Blue-green деплой в Kubernetes реализуется с помощью двух Deployment'ов (blue и green) и одного Service. Service selector указывает на labels текущей активной версии (например, version: blue). Пайплайн разворачивает новую версию в green Deployment, выполняет smoke-тесты, а затем обновляет selector Service на version: green. Это мгновенно переключает весь трафик.
Canary-деплой предполагает постепенное наращивание трафика на новую версию. Простой способ - использовать аннотации Ingress-контроллера nginx. В манифесте Ingress для нового canary Deployment можно указать аннотации nginx.ingress.kubernetes.io/canary: "true" и nginx.ingress.kubernetes.io/canary-weight: "10", чтобы направить 10% трафика. После проверки метрик пайплайн увеличивает вес до 100% и удаляет старый Deployment.
Для более сложных сценариев, включая маршрутизацию на основе заголовков, используйте Service Mesh, например, Istio. В пайплайн можно включить этап проверки метрик здоровья (успешность HTTP-запросов, latency) из Prometheus и автоматический откат при превышении пороговых значений.
Подробнее о стратегиях развертывания и их автоматизации можно прочитать в руководстве по Docker в production.
Чек-лист миграции и частые ошибки
Используйте этот список для самопроверки перед финальным запуском в production.
- Аудит и планирование: Выделили stateless и stateful компоненты. Рассчитали requests/limits для CPU и памяти. Выбрали стратегию миграции (поэтапная).
- Безопасность данных: Создали полные бэкапы всех stateful-сервисов. Проверили процедуру восстановления из бэкапа.
- Создание манифестов: Stateless-сервисы описаны в Deployment, stateful - в StatefulSet. Конфигурации вынесены в ConfigMap, секреты - в Secret (или внешний Vault). Настроены Services для внутреннего доступа и Ingress для внешнего.
- Проверки здоровья: Для каждого контейнера настроены осмысленные livenessProbe и readinessProbe. Это критически важно для работы self-healing и service discovery.
- Хранение данных: Для stateful-приложений использованы volumeClaimTemplates в StatefulSet. Проверена работа PersistentVolume при перемещении пода на другую ноду.
- Безопасность: Задан SecurityContext для подов (runAsNonRoot, readOnlyRootFilesystem). Используются образы со стабильными тегами, а не latest. Настроены RBAC для CI/CD ServiceAccount.
- CI/CD: Пайплайн обновляет манифесты и применяет их с помощью kubectl/helm/kustomize. Реализована стратегия деплоя (например, blue-green) и есть этап отката.
- Мониторинг и логи: Настроен сбор метрик приложения и кластера в Prometheus. Настроено логирование через Loki или EFK-стек. Определены ключевые алерты.
Частые ошибки:
- Забыть про readinessProbe, из-за чего трафик начинает поступать на еще не готовые к работе поды.
- Не настроить requests/limits, что приводит к eviction подов при нехватке памяти на ноде или к невозможности запланировать под из-за нехватки CPU.
- Использовать тег образа
latestв production, что делает деплой непредсказуемым. - Хранить секреты в Git, даже в base64.
- Не тестировать поведение PersistentVolume при смене ноды. Локальные тома (local) привязаны к конкретной ноде, и под не может переехать.
- Игнорировать настройку Pod Disruption Budget для защиты от случайного eviction'а при обслуживании нод.
После успешной миграции рассмотрите возможность дальнейшей оптимизации инфраструктуры, например, автоматизацию развертывания кластеров с помощью инструментов, описанных в статье об автоматизации переноса инфраструктуры. Для управления уже развернутыми приложениями обратитесь к полному руководству по Kubernetes Deployment.