API держал около 5000 RPS в пик. Compose крутил восемь копий на трёх серверах. Деплой: SSH на каждый, git pull, docker-compose up -d --build, молитва. Обычно падало.
Rolling update руками, 30-60 секунд простоя на сервер. Ночью умерла машина, балансировщик продолжал слать туда трафик, узнали утром от клиентов. Автоскейлинга нет: поднял сервер, час спустя он простаивает, платишь за воздух. Health check смотрел, жив ли контейнер, не живо ли приложение.
Дальше масштабировать Compose значило кормить ад поддержки. Нужен был оркестратор. Не обязательно Kubernetes.
Compose хорош локально и на одном-трёх серверах до примерно 500 RPS. Дальше простой начинает стоить денег.
Что Compose не закрывает
С Compose даже docker-compose up -d --no-deps --build service оставляет щель в несколько секунд. Пользователь её видит.
В Kubernetes новые поды стартуют, проходят readiness, начинают принимать трафик, старые уходят. Откат: kubectl rollout undo.
# Деплой новой версии
kubectl set image deployment/myapp myapp=myapp:v2
# Откат, если что-то пошло не так
kubectl rollout undo deployment/myappКонтейнер умер: restart: unless-stopped его поднимет. Умер сервер: SSH на новый и поднимай стек. В k8s под падает, scheduler поднимает другой. Нода падает, поды уезжают на живые.
В Compose состояние смотришь по SSH. В k8s пишешь replicas: 5 и kubectl apply. Git источник правды. ArgoCD или Flux применяют сами. Откат: git revert.
# Commit изменений в манифестах
git commit -m "Scale app to 10 replicas"
git push
# ArgoCD или Flux автоматически применят изменения
# Rollback? Просто git revert и pushhealthcheck в Compose: контейнер ответил на /health. База лежит, контейнер жив, пользователи получают 500. В k8s два зонда. Liveness: убить и поднять заново. Readiness: пока не готов, трафик не слать.
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Автоскейлинг в Compose: руками или cron. Horizontal Pod Autoscaler смотрит CPU, память или свою метрику из Prometheus.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Compose оставляю для локальной разработки, монолита на одном-двух серверах при 100-200 RPS, MVP и команды без DevOps. Там проще Render или Fly.io, чем кластер.
Kubernetes решает сложные задачи. Если задачи простые, он создаёт новые.
Минимум понятий
Говорите кластеру: пять копий, 2 CPU, 4 GB. Он решает куда поставить, следит и перезапускает.
Pod: один или несколько контейнеров на одной ноде.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080Deployment: сколько реплик, какой образ, как обновлять.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1.0.0
ports:
- containerPort: 8080Service: стабильный IP и DNS. ClusterIP внутри, NodePort снаружи, LoadBalancer в облаке.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancerIngress: HTTP снаружи внутрь.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80ConfigMap и Secret вместо переменных в compose.
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
DATABASE_URL: "postgresql://user@db:5432/mydb"
LOG_LEVEL: "info"
---
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # base64 encodedk8s ставит поды, масштабирует, лечит, даёт DNS, rolling update, секреты. Не ставит мониторинг, CI, бэкапы и безопасность. Это Prometheus, ArgoCD, Velero, RBAC отдельно.
Helm
Одно приложение: Deployment, Service, Ingress, ConfigMap, Secret. Двести строк. Три окружения с разными репликами и доменами. Без Helm: копипаста и Find & Replace.
myapp/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
Было:
version: "3.8"
services:
app:
image: myapp:latest
ports:
- "8080:8080"
environment:
DATABASE_URL: postgresql://user:pass@db:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secretpass
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
db_data:Стало, values.yaml:
replicaCount: 3
image:
repository: myapp
tag: "v1.0.0"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
targetPort: 8080
ingress:
enabled: true
host: myapp.example.com
env:
DATABASE_URL: postgresql://user@postgres:5432/mydb
REDIS_URL: redis://redis:6379
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70apiVersion: apps/v1
kind: Deployment
metadata:
name: { { include "myapp.fullname" . } }
spec:
replicas: { { .Values.replicaCount } }
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: { { .Values.service.targetPort } }
env:
- name: DATABASE_URL
value: { { .Values.env.DATABASE_URL } }
- name: REDIS_URL
value: { { .Values.env.REDIS_URL } }
resources: { { - toYaml .Values.resources | nindent 10 } }
livenessProbe:
httpGet:
path: /health
port: { { .Values.service.targetPort } }
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /health/ready
port: { { .Values.service.targetPort } }
initialDelaySeconds: 5# Установка chart
helm install myapp ./myapp
# Обновление с новыми параметрами
helm upgrade myapp ./myapp --set image.tag=v1.1.0
# Откат к предыдущей версии
helm rollback myapp
# Удаление
helm uninstall myapp# Dev (1 реплика, маленькие ресурсы)
helm install myapp-dev ./myapp -f values-dev.yaml
# Staging (3 реплики)
helm install myapp-staging ./myapp -f values-staging.yaml
# Production (10 реплик, HPA)
helm install myapp-prod ./myapp -f values-prod.yamlГотовые чарты на Artifact Hub.
# PostgreSQL
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install postgres bitnami/postgresql
# Redis
helm install redis bitnami/redis
# Nginx Ingress Controller
helm install nginx-ingress ingress-nginx/ingress-nginx
# Prometheus + Grafana (мониторинг)
helm install monitoring prometheus-community/kube-prometheus-stackЛокально
Полный кластер ноутбуку тяжёл. kind, minikube, k3d.
| Критерий | kind | minikube | k3d |
|---|---|---|---|
| Основа | Docker контейнеры | VM (VirtualBox/Docker) | k3s в Docker |
| Скорость запуска | Очень быстро (10-20 сек) | Медленно (1-2 мин) | Быстро (20-30 сек) |
| RAM | Мало (~2GB) | Много (~4-8GB) | Мало (~1-2GB) |
| Multi-node кластер | Да | Да (сложнее) | Да |
| LoadBalancer support | Через MetalLB | Из коробки | Из коробки |
| Близость к prod | Очень близко | Близко | k3s не полный k8s |
| Лучше для | CI/CD, тестирование | Обучение, dev | Быстрый dev |
kind для CI и близости к прод-k8s. minikube для учёбы, если RAM не жалко. k3d на каждый день.
brew install kind kubectl
# Или через binary
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-darwin-arm64
chmod +x ./kind
mv ./kind /usr/local/bin/kind# Базовый single-node кластер
kind create cluster --name dev
# Multi-node кластер (1 control-plane + 2 workers)
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOFkubectl cluster-info
kubectl get nodes# Загрузка локального образа в kind
kind load docker-image myapp:latest --name dev
# Деплой
kubectl apply -f deployment.yaml
kubectl get pods
kubectl logs -f <pod-name>
# Проброс порта для доступа
kubectl port-forward deployment/myapp 8080:8080
# Теперь доступно на http://localhost:8080kind delete cluster --name devbrew install k3d
# Или curl
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash# Кластер с 3 worker нодами и LoadBalancer на порту 8080
k3d cluster create dev \
--agents 3 \
--port "8080:80@loadbalancer"kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Если Service type: LoadBalancer, доступен на localhost:8080
curl http://localhost:8080k3d cluster stop dev
k3d cluster start devМинимум 8 GB RAM и 4 ядра, лучше 16 и 6. Docker Desktop режу до 6 GB и 4 CPU. В манифестах лимиты. Не поднимаю фронт, если тестирую API. После тестов kubectl delete: остановленный под память всё равно ест.
Если ноутбук задыхается, дешевле облачный dev-кластер за $10-20 в месяц, чем день на перезагрузки.
Как смотреть, что сломалось
# Получить список подов
kubectl get pods
# Подробная информация о поде
kubectl describe pod <pod-name>
# Логи пода
kubectl logs <pod-name>
kubectl logs <pod-name> -f # follow (real-time)
kubectl logs <pod-name> --previous # логи предыдущего упавшего контейнера
# Логи всех подов deployment
kubectl logs -l app=myapp --all-containers=true
# Exec в под (как docker exec)
kubectl exec -it <pod-name> -- /bin/bash
# Проброс порта
kubectl port-forward pod/<pod-name> 8080:8080
# События кластера
kubectl get events --sort-by='.lastTimestamp'
# Top (CPU/Memory usage)
kubectl top nodes
kubectl top podsPending: describe, обычно не хватает CPU/памяти или нод. CrashLoopBackOff: logs --previous. Running и молчит: readiness. Service пустой: kubectl get endpoints, поды не готовы.
# Добавляем репозиторий
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Устанавливаем kube-prometheus-stack (Prometheus + Grafana + Alertmanager)
helm install monitoring prometheus-community/kube-prometheus-stack
# Получаем пароль Grafana
kubectl get secret monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 --decode
# Проброс порта для доступа
kubectl port-forward svc/monitoring-grafana 3000:80
# Grafana доступна на http://localhost:3000 (admin / <password>)helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack \
--set grafana.enabled=false \
--set promtail.enabled=true
# Добавьте Loki как data source в Grafana
# URL: http://loki:3100В Explore: {app="myapp"} и {app="myapp"} |= "ERROR".
Если не Kubernetes
Nomad: один бинарь, 50 MB RAM, Docker и не только Docker, Consul и Vault рядом. Экосистема меньше, Ingress нет, managed от облаков нет. Имеет смысл команде до десяти человек на стеке HashiCorp.
job "myapp" {
datacenters = ["dc1"]
group "app" {
count = 3
task "web" {
driver = "docker"
config {
image = "myapp:v1.0.0"
ports = ["http"]
}
resources {
cpu = 500
memory = 512
}
service {
name = "myapp"
port = "http"
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "2s"
}
}
}
}
}ECS: если уже весь AWS. Fargate без серверов. Lock-in и цена, особенно Fargate.
Fly.io: fly deploy, как Heroku с Docker, точки по миру, три бесплатные VM. Контроля меньше, счёт растёт вместе с нагрузкой.
# Установка CLI
brew install flyctl
# Вход
fly auth login
# Инициализация приложения
fly launch
# Деплой
fly deploy
# Масштабирование
fly scale count 5
fly scale vm shared-cpu-2xDocker Swarm: если знаете Compose, освоите за день. docker stack deploy -c docker-compose.yml. Docker Inc его почти не развивает.
# Инициализация Swarm
docker swarm init
# Деплой compose файла
docker stack deploy -c docker-compose.yml myapp
# Масштабирование
docker service scale myapp_web=5| Критерий | Kubernetes | Nomad | AWS ECS | Fly.io | Docker Swarm |
|---|---|---|---|---|---|
| Сложность | Высокая | Средняя | Низкая | Очень низкая | Низкая |
| Экосистема | Огромная | Средняя | AWS-only | Маленькая | Мёртвая |
| Гибкость | Максимум | Высокая | Средняя | Низкая | Средняя |
| Vendor lock-in | Нет | Нет | AWS | Fly.io | Нет |
| Стоимость | DIY дёшево, managed дорого | DIY дёшево | Дорого | Растёт | Дёшево |
| Для кого | Средние/крупные команды | Малые команды, HashiCorp стек | AWS-native проекты | Стартапы | Малые проекты |
Compose хватает: остаюсь. Перерос: сначала Fly или Nomad. Kubernetes когда нужна его гибкость, не строчка в резюме.
Цена перехода
Чтобы деплоить: 20-40 часов. Чтобы держать прод: 100-200. Архитектор: 500+.
Managed EKS/GKE/AKS: control plane $70-150, ноды $50-200, addons ваши. Self-hosted: софт бесплатный, команда нет.
Пять микросервисов, 20-30 подов, около 2000 RPS. GKE $250, 10-15 часов DevOps в месяц, две недели на онбординг. Окупается, если экономите на ручном масштабировании и простоях.
Не иду в k8s, если разработчиков меньше трёх, монолит на одном сервере, нет DevOps, это MVP или бюджет меньше $500. Managed дорог, свой кластер без экспертизы опасен.
Как переезжал
Неделя-две: туториал kubernetes.io, локальный kind, аудит сервисов и секретов, выбор managed или свой, registry в CI.
Дальше самый простой stateless.
api:
image: myapi:latest
ports:
- "8080:8080"
environment:
DATABASE_URL: postgresql://db:5432/mydbapiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapi:v1.0.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: api-secrets
key: database-url
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 80Базы в k8s не тороплю. RDS и Cloud SQL проще. Если всё же тащить: StatefulSet, PVC, бэкап, учебный restore. Redis можно чартом Bitnami. Файлы: S3 или PVC ReadWriteMany.
Потом kube-prometheus-stack и Loki. Трафик 10%, неделя метрик, 50%, 100%. Старый Compose живёт ещё две недели.
Я перешёл в 2021-м. Первые два месяца боль и грабли. Потом инфраструктура, которая масштабируется без меня, поднимается сама и даёт десять деплоев в день без окна. Это стоит времени, если задача достаточно сложная. Если нет, Compose и сон спокойнее.
См. также:

