Docker Compose — отличный инструмент для локальной разработки и небольших проектов. Однако по мере роста системы возникает необходимость в оркестрации контейнеров на продакшн-уровне. Kubernetes предоставляет автоматическое масштабирование, самовосстановление, rolling updates и множество других возможностей, которых нет в Docker Compose. В этой статье мы пошагово разберём процесс миграции реального проекта.
Почему стоит мигрировать
Docker Compose управляет контейнерами на одном хосте. Это означает, что при падении сервера вся система становится недоступной. Kubernetes решает эту проблему, распределяя нагрузку между несколькими узлами кластера. Помимо отказоустойчивости, Kubernetes предоставляет декларативное управление конфигурацией, встроенный service discovery, управление секретами и автоматическое горизонтальное масштабирование.
Типичные причины миграции: необходимость масштабирования на несколько серверов, требования к высокой доступности (HA), потребность в rolling updates без простоя, желание использовать экосистему Kubernetes (Helm, Operators, Service Mesh) и стандартизация деплоя в компании.
Подготовка: анализ текущего docker-compose.yml
Начнём с типичного docker-compose.yml, который содержит веб-приложение, API-сервер, базу данных PostgreSQL и Redis для кеширования. Для каждого сервиса нам нужно определить: образ и тег, переменные окружения, тома (volumes), порты, зависимости между сервисами и ресурсные лимиты.
version: '3.8'
services:
web:
build: ./frontend
ports:
- "3000:3000"
environment:
- API_URL=http://api:8080
depends_on:
- api
api:
build: ./backend
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
cache:
image: redis:7-alpine
volumes:
pgdata:
Шаг 1: Создание Docker-образов
В Docker Compose часто используется директива build для сборки образов из Dockerfile. В Kubernetes образы должны быть предварительно собраны и размещены в container registry. Можно использовать Docker Hub, GitHub Container Registry (ghcr.io), GitLab Container Registry или собственный registry.
# Сборка и публикация образов
docker build -t registry.example.com/myapp/frontend:v1.0.0 ./frontend
docker build -t registry.example.com/myapp/backend:v1.0.0 ./backend
docker push registry.example.com/myapp/frontend:v1.0.0
docker push registry.example.com/myapp/backend:v1.0.0
Важно использовать конкретные теги версий (например, v1.0.0), а не latest. Это обеспечивает воспроизводимость деплоев и возможность отката к предыдущей версии.
Шаг 2: Namespace и секреты
Первым делом создадим namespace для изоляции ресурсов нашего приложения и Kubernetes Secret для хранения чувствительных данных (пароли, строки подключения).
apiVersion: v1
kind: Namespace
metadata:
name: myapp
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: myapp
type: Opaque
stringData:
database-url: "postgres://user:pass@db-svc:5432/app"
redis-url: "redis://cache-svc:6379"
Шаг 3: Преобразование сервисов в Deployments
Каждый сервис из docker-compose.yml превращается в пару Deployment + Service в Kubernetes. Deployment управляет подами (replicas), а Service обеспечивает сетевой доступ к ним. Рассмотрим на примере API-сервера.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: myapp
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/myapp/backend:v1.0.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: redis-url
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: api-svc
namespace: myapp
spec:
selector:
app: api
ports:
- port: 8080
targetPort: 8080
Обратите внимание на несколько ключевых отличий от Docker Compose. Мы явно указываем replicas: 3 для запуска трёх экземпляров API-сервера. Переменные окружения берутся из Secret, а не задаются напрямую. Добавлены readinessProbe и livenessProbe для проверки здоровья контейнера. Указаны resource requests и limits для планирования ресурсов кластера.
Шаг 4: StatefulSet для базы данных
Для stateful-сервисов, таких как PostgreSQL, используется StatefulSet вместо Deployment. StatefulSet обеспечивает стабильные сетевые идентификаторы, упорядоченный запуск и завершение подов, а также привязку к Persistent Volumes. Каждый под получает свой PersistentVolumeClaim, который сохраняется при рестарте.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
namespace: myapp
spec:
serviceName: db-svc
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: postgres-password
- name: POSTGRES_DB
value: app
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Шаг 5: Ingress для внешнего доступа
В Docker Compose мы просто пробрасывали порты на хост. В Kubernetes для маршрутизации внешнего трафика используется Ingress. Ingress позволяет настроить маршрутизацию по hostname и path, терминацию TLS (HTTPS), балансировку нагрузки и rate limiting.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: myapp
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 3000
Шаг 6: Деплой и проверка
После подготовки всех манифестов применяем их к кластеру. Рекомендуется использовать Kustomize или Helm для управления конфигурацией, но для начала можно применить манифесты напрямую через kubectl.
# Применение манифестов
kubectl apply -f namespace.yaml
kubectl apply -f secrets.yaml
kubectl apply -f db-statefulset.yaml
kubectl apply -f cache-deployment.yaml
kubectl apply -f api-deployment.yaml
kubectl apply -f web-deployment.yaml
kubectl apply -f ingress.yaml
# Проверка статуса
kubectl get pods -n myapp
kubectl get svc -n myapp
kubectl logs -f deployment/api -n myapp
Типичные ошибки при миграции
Первая и самая частая ошибка — использование тега latest для образов. В Kubernetes это приводит к непредсказуемым деплоям и невозможности отката. Всегда используйте семантическое версионирование. Вторая ошибка — отсутствие resource limits. Без лимитов один контейнер может потребить все ресурсы узла и вызвать OOM-kill других подов. Третья ошибка — хранение секретов в открытом виде в манифестах. Используйте Kubernetes Secrets, а лучше — внешние системы управления секретами (Vault, AWS Secrets Manager). Четвёртая ошибка — отсутствие health checks (probes). Без них Kubernetes не может определить, работает ли приложение корректно, и не будет автоматически перезапускать зависшие поды.
Заключение
Миграция с Docker Compose на Kubernetes — это не просто перенос контейнеров в другую среду. Это переход к cloud-native подходу, который требует изменения мышления. Kubernetes предоставляет мощные возможности для масштабирования, отказоустойчивости и автоматизации, но требует инвестиций в изучение экосистемы. Начните с малого: перенесите один некритичный сервис, освойте основные концепции, а затем постепенно мигрируйте остальные компоненты. Используйте Helm или Kustomize для управления конфигурациями окружений (dev, staging, prod) и автоматизируйте деплой через CI/CD-пайплайн.