Compose vs Kubernetes: когда переходить
Compose отлично работает для локальной разработки и небольших stateless-сервисов. Но у него есть жёсткий предел масштабирования. В какой-то момент команда сталкивается с вопросом: остаёмся на compose-стенде или переходим на Kubernetes?
Этот урок — про границу между ними. Когда compose достаточно, когда k8s неизбежен, и как мигрировать с минимальной болью.
Что compose делает хорошо
Compose — идеальный инструмент для:
- Local development: одна команда
docker compose upподнимает весь стек разработчика. - CI integration tests: в прошлом уроке мы видели, как compose +
--wait— стандарт для интеграционных тестов. - Small production: один сервер, 5-15 контейнеров, без HA-требований. Например, internal-инструмент для команды.
- POC и спринты: быстро попробовать новый сервис, поднять в compose, выкинуть.
Компонентов в compose — три: YAML-файл, Docker daemon на одной машине, docker compose CLI. Простота — его суперсила.
Анатомия Pod — базовая единица Kubernetes Deployment: декларативное управление репликами
Где compose ломается
Один хост = single point of failure
Если упал сервер с compose, вместе с ним упало всё. Нет автоматического failover на другой сервер.
В k8s: control plane следит за тем, что Pod’ы запущены на нужном количестве нод. Нода упала — Pod’ы перезапускаются на других нодах.
Нет autoscaling
# Compose: всегда 2 worker'а Spark, не больше не меньше
spark-worker:
scale: 2
В k8s: HorizontalPodAutoscaler масштабирует Pod’ы по CPU/memory или custom metrics:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spark-worker
spec:
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Нет rolling updates
# Compose: тебе нужно остановить и поднять
docker compose pull
docker compose up -d # downtime пока pulling
В k8s: Deployment делает rolling update без downtime:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # на 1 больше реплик во время update
maxUnavailable: 0 # 0 unavailable -- zero downtime
Нет declarative state
Compose — “process YAML, run containers”. Если ты руками удалил контейнер — compose не вернёт его, пока ты не сделаешь up.
В k8s — declarative. Удалил Pod — ReplicaSet создаст новый. Удалил Deployment — всё чисто. Контроллеры постоянно сверяют actual state с desired.
Нет nativ secrets management
В compose-проде секреты через .env — файл на диске. Кто угодно с доступом к серверу читает.
В k8s — Secrets, RBAC, opt-in encryption-at-rest, integration с Vault / AWS Secrets Manager / GCP Secret Manager.
Когда compose достаточно
Конкретные сценарии:
Internal dashboard для команды. 5 контейнеров: Grafana + Prometheus + cAdvisor + Loki + Tempo. Один сервер. 10 пользователей. Downtime 5 минут раз в неделю — ОК. Compose — правильный выбор. k8s — overkill (нужно потратить недели на setup, чтобы запустить то, что компоуз делает за час).
ETL для маленькой команды. Airflow + Postgres + один Spark worker. Делает ночные DAG’и. Если упал — джобы запустятся следующей ночью. Compose — OK.
Локальный dev. Всегда compose. k8s локально (через kind/minikube) для редких случаев, когда тестируешь Helm-charts.
CI integration tests. Всегда compose. Поднял, протестил, опустил.
Internal tool для одной команды разработки. Если работает 10 человек, downtime тебе не убьёт, и трафик предсказуем — compose.
Когда нужен k8s
Public-facing service. Если downtime стоит денег и пользователи ждут 99.9% uptime — нужен k8s c rolling updates и multi-node HA.
Большая команда (50+ engineers). k8s даёт namespace-isolation, RBAC, quotas. На compose-сервере у всех root.
Autoscaling по нагрузке. Spark-jobs, которые иногда требуют 5 workers, иногда 50. Compose не умеет; k8s — из коробки.
Микросервисы с service mesh. 30+ сервисов, каждый общается с 5 другими. mTLS, observability, traffic management — это Istio / Linkerd на k8s.
Compliance требования. SOC2, ISO27001 ожидают audit trails, RBAC, encryption — стандарт k8s.
Multi-region. Failover между регионами, traffic policies, replication. k8s + service mesh.
Общие концепции: маппинг compose -> k8s
# Compose
services:
api:
image: my-api:v1
ports:
- "8080:8080"
environment:
DB_URL: postgresql://postgres/api
deploy:
replicas: 3
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
Эквивалент в k8s — два объекта:
# Deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels: {app: api}
template:
metadata:
labels: {app: api}
spec:
containers:
- name: api
image: my-api:v1
ports:
- containerPort: 8080
env:
- name: DB_URL
value: postgresql://postgres/api
readinessProbe:
httpGet:
path: /health
port: 8080
---
# Service.yaml
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector: {app: api}
ports:
- port: 8080
targetPort: 8080
Сравнение:
| Concept | Compose | Kubernetes |
|---|---|---|
| Сервис | service | Deployment + Service (split) |
| Контейнер | image: в service | containers: в Pod |
| Группа контейнеров | — | Pod (1+ containers sharing network) |
| Реплики | deploy.replicas | Deployment.spec.replicas |
| ENV | environment: | env: |
| Healthcheck | healthcheck: | readinessProbe, livenessProbe |
| Volume | volumes: | PersistentVolumeClaim + mount |
| Network | networks: | автоматически (Pod-to-Pod через DNS) |
| Secret | .env | Secret resource + envFrom |
| ConfigMap | — | ConfigMap resource (для non-secret конфигов) |
| Healthcheck-startup | depends_on: condition: service_healthy | initContainers + readinessProbes |
В k8s Pod — это группа из 1+ контейнеров, которые шарят network namespace. Большинство DE-сервисов = 1 контейнер на Pod. Sidecar-pattern (logging-collector, secrets-fetcher) — больше.
kompose: автоматическая миграция
kompose — CLI, который генерирует k8s-манифесты из compose.yml.
brew install kompose
kompose convert -f compose.yml
# Создаёт api-deployment.yaml, api-service.yaml, postgres-deployment.yaml, ...
Это начальная точка, не финальный продукт. kompose не умеет:
- Конвертировать
profiles:. - Правильно ставить resource requests/limits (нужно по нагрузке настраивать).
- Создавать proper liveness/readiness probes из compose healthcheck (часто превращает в
command:). - Настраивать ingress (compose
ports:это NodePort, не Ingress). - Работать с
depends_on: condition:(k8s ожидает init-containers или job dependencies).
Реальный путь миграции:
kompose convert— получи skeleton.- Пройди руками: добавь resource requests, probes, ingress, secrets.
- Замени
hostPathvolumes наPersistentVolumeClaim(для stateful). - Замени
.envнаSecret/ConfigMap. - Деплой через Helm / Kustomize / ArgoCD.
ВНИМАНИЕ: stateful-сервисы (Postgres, Kafka) на k8s — сложнее, чем stateless. Нужен StatefulSet, PersistentVolume, headless Service. Для production обычно используют operators: kafka-operator (Strimzi), postgres-operator (Zalando / Crunchy). Junior’у на компоуз-стеке этого ещё не нужно знать — но впереди.
Путь миграции для DE-стека
Если у тебя compose-стек с Airflow + Postgres + Kafka:
-
Stateful сначала в managed services. Postgres -> RDS / Cloud SQL. Kafka -> MSK / Confluent Cloud. Это снимает 80% сложности k8s-stateful.
-
Stateless — в k8s. Airflow webserver/scheduler/workers — это stateless (state в Postgres). Деплой через official Helm chart
airflow-helm/airflow. -
Используй managed k8s. EKS / GKE / AKS. Не пытайся kubeadm-кластер на VM — это full-time-job.
-
Observability отдельно. Не пихай Prometheus / Grafana в свой кластер на старте. Cloud-managed (CloudWatch / Stackdriver) — быстрее.
-
CI/CD через ArgoCD или Flux. GitOps: ваш git-репо с k8s-манифестами — single source of truth.
Связь с нашим Kubernetes-курсом
Этот курс заканчивается на compose. Следующий шаг — наш [kubernetes-course] (отдельный курс на платформе). Там:
- Pods, Services, Deployments в деталях.
- StatefulSets и PersistentVolumes.
- Helm charts для DE-сервисов (Airflow, Spark Operator).
- Ingress, NetworkPolicies.
- RBAC и security.
- ArgoCD GitOps workflow.
После compose ты уже знаешь основы контейнеров, образов, сетей, volumes. В k8s эти концепции масштабируются. Не нужно учить с нуля.
Trade-offs: реальная цена k8s
K8s даёт многое, но требует:
- Кривая обучения: 200+ ресурсных типов, 50+ концепций. Месяцы для свободной работы.
- Operational overhead: etcd backups, version upgrades, security patches. Даже на managed.
- Compute overhead: control plane + kubelet + system pods = 1-2 cores на ноду. На маленьком стенде это огромная доля.
- Network complexity: CNI plugin, NetworkPolicy, Service mesh — много слоёв.
- Cost: managed k8s (EKS) платит 10/мес.
Если ты ещё junior, и команда поднимает k8s — скорее всего, рано. Если ты junior в команде, которая уже на k8s — учи. Это де-факто стандарт enterprise compute.
Попробуй сам
# 1. Установи kompose
brew install kompose # или snap install kompose
# 2. Возьми любой compose.yml из предыдущих уроков (например, Airflow-стек)
kompose convert -f compose.yml
# 3. Посмотри, что сгенерировалось
ls *.yaml
# airflow-webserver-deployment.yaml
# airflow-webserver-service.yaml
# postgres-deployment.yaml
# postgres-service.yaml
# pg-data-persistentvolumeclaim.yaml
# 4. Локально подними k3d / kind / minikube
brew install k3d
k3d cluster create de-test
# 5. Применяй манифесты
kubectl apply -f .
# 6. Проверь
kubectl get pods
kubectl get services
# 7. Forward webserver:
kubectl port-forward svc/airflow-webserver 8080:8080
# Открой http://localhost:8080
# 8. Cleanup
k3d cluster delete de-test
Это даст ощущение пути migration. Сравни сгенерированный YAML с compose — увидишь, сколько добавилось boilerplate (Deployment+Service+PVC вместо одного service).