Як реалізувати масштабування сервісів у Docker Compose?
Масштабування сервісів у Docker Compose крутить кілька container одного сервісу на одному host. Реально корисно для паралельної роботи (workers, batch-processing), але обмежено порівняно з multi-host оркестраторами.
Теорія
TL;DR
docker compose up --scale <service>=Nкрутить N container того сервісу.deploy.replicasуcompose.yaml(Compose v2+) це декларативний еквівалент.- Усі репліки крутяться на тому самому host, у Compose немає scheduling між машинами.
- Репліки ділять project-мережу; DNS для service-імені резолвиться на усі репліки (round-robin).
- Не можна опублікувати фіксований host-порт з кількох реплік — port-конфлікт. Бери
expose:(тільки внутрішньо) і reverse proxy перед ними. - Для multi-host бери Swarm (
docker stack deploy), Kubernetes або подібне.
Швидкий приклад
# compose.yaml
services:
worker:
image: myworker
deploy:
replicas: 3
api:
image: myapi
expose:
- "3000" # тільки внутрішньо, без фіксованого host-порту
deploy:
replicas: 5
web:
image: nginx
ports:
- "80:80"
# nginx-config балансує на api:3000 (Docker DNS round-robin'ить)docker compose up -d
# 3 worker-репліки + 5 api-реплік + 1 web reverse proxy.
docker compose ps
# myapp-worker-1, myapp-worker-2, myapp-worker-3
# myapp-api-1 ... myapp-api-5
# myapp-web-1Три сервіси, один стек, масштабовано по-різному.
Імперативне масштабування у runtime
# Збільшити до 10 api-реплік без рестарту інших сервісів
docker compose up -d --scale api=10
# Зменшити назад
docker compose up -d --scale api=3
# Кілька сервісів одночасно
docker compose up -d --scale api=5 --scale worker=10Флаг --scale override'ить deploy.replicas для цього прогону. Compose зупиняє або стартує container, щоб збігалося з запитаною кількістю.
Як DNS резолвить кілька реплік
Embedded DNS Docker повертає УСІ IP для service-імені. Багато клієнтів (HTTP-бібліотеки, стандартні бібліотеки мов) round-robin'ять між повернутими A-записами:
$ docker compose exec web nslookup api
Name: api
Address 1: 172.18.0.5
Address 2: 172.18.0.6
Address 3: 172.18.0.7
# Три репліки; клієнти round-robin між ними.Чи реально твій клієнт розподіляє load, залежить від його DNS-resolution-поведінки. Більшість сучасних HTTP-клієнтів так.
Чому фіксовані порти ламаються з репліками
services:
api:
image: myapi
ports:
- "3000:3000" # ← проблема при scale
deploy:
replicas: 3$ docker compose up -d
ERROR: only one of api can be running at port 3000Два container не можуть bind на той самий host-порт. Рішення:
- Випадкові host-порти (
ports: ["3000"]без:mapping), Docker обирає вільні порти. - expose: only, тільки внутрішній доступ; reverse proxy обробляє external.
- Range mapping,
ports: ["3000-3009:3000"]алокує діапазон.
Прод-відповідь майже завжди #2: reverse proxy перед, репліки внутрішні.
Reverse-proxy патерн
services:
api:
image: myapi
expose: ["3000"]
deploy: { replicas: 5 }
web:
image: nginx:1.27-alpine
ports: ["80:80"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on: [api]# nginx.conf
upstream api_backend {
server api:3000; # Docker DNS резолвить на усі IP реплік
}
server {
listen 80;
location / {
proxy_pass http://api_backend;
}
}Lookup upstream api:3000 б'є у Docker DNS, отримує кілька IP, nginx load-balance'ить. Репліки можуть scale up/down вільно.
Масштабування з Traefik (auto-discovery)
services:
api:
image: myapi
expose: ["3000"]
deploy: { replicas: 5 }
labels:
- "traefik.http.routers.api.rule=Host(`api.local`)"
- "traefik.http.services.api.loadbalancer.server.port=3000"
traefik:
image: traefik:v3
ports: ["80:80", "8080:8080"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:roTraefik стежить за Docker-event'ами і авто-оновлює свої routes, коли репліки приходять/йдуть. Чистіше, ніж nginx для динамічного scale.
Що Compose-масштабування НЕ робить
- Health-aware load balancing. Docker DNS повертає IP усіх реплік, healthy чи ні.
- Multi-host scheduling. Усі репліки на одній машині. CPU/пам'ять цього host це стеля.
- Auto-scaling. Жодних
min: 3, max: 20, target_cpu: 70%. Кількість ставиш руками. - Rolling-updates з контрольованим parallelism.
docker compose upперестворює container паралельно.
Для будь-чого з цього потрібен Swarm або Kubernetes.
Типові помилки
Спроба масштабувати сервіс з опублікованим портом
Згадано вище. Фікс: expose: + reverse proxy.
Масштабування stateful-сервісів
services:
db:
image: postgres:16
deploy: { replicas: 3 } # ← ПОГАНОТри postgres-container на одному host = три незалежні бази, що намагаються використати той самий volume = корупція. Stateful-сервіси не масштабуються так; replication і HA це інші concerns.
Масштабування worker-сервісів без queue-ідемпотентності
Якщо workers обробляють повідомлення з черги, кілька workers паралельно це win. Але workers мають бути ідемпотентними (повідомлення, оброблене двічі, не має шкідливого ефекту). Інакше scale = баги.
Забути, що --scale per-run
docker compose up -d --scale api=5
# api на 5 реплік
docker compose up -d # наступний прогін, без --scale
# api назад до deploy.replicas (дефолт 1, якщо не задано)--scale не персистить. Для постійної кількості постав deploy.replicas у YAML.
Реальне застосування
- Worker pool (image-processing, background-job): scale
workerдо N, черга годує task'ами, ідемпотентна обробка. - Stateless API за proxy: api-репліки + nginx/Traefik для load balancing. Легке horizontal-scale на одному host.
- CI test-parallelism: scale
runner-сервіс до CPU-ядер для паралельного test-виконання. - Міграції на Swarm/K8s: Compose
deploy.replicasце той самий ключ, що Swarm використовує, тож синтаксис переноситься, коли graduate'нешся.
Питання для поглиблення
Q: Яка різниця між docker compose up --scale і docker service scale (Swarm)?
A: compose --scale single-host. Swarm service scale розподіляє між cluster-node. Та сама ідея, інший scope.
Q: Як репліки ділять стан?
A: За замовчуванням ніяк. Кожна репліка має свій writable-шар. Для shared-стану направ усі репліки на той самий external-сервіс (DB, Redis) або бери named volume.
Q: Чи Compose scale production-ready?
A: Для single-host навантажень з відповідним front-end (reverse proxy) так. Для HA ні, один host-fail зносить усі репліки. Multi-host означає Swarm або K8s.
Q: Чи можна scale до нуля?
A: Так: docker compose up -d --scale api=0. Зупиняє усі api-container. Корисно для тимчасового вимкнення сервісу.
Q: (Senior) Як спроектувати single-host Compose-стек для навантаження, що зрідка burst'ить до 10x норми?
A: Три опції. (1) Pre-scale до peak (втрата під час норми). (2) Ручний scale-up на відомі burst-часи через cron-trigger'ний docker compose up --scale. (3) Перехід на Swarm або K8s з HPA. Для single-host опція 2 (cron-driven) реалістична відповідь; для справжньої елестичності маєш покинути Compose.
Приклади
Worker-pool з auto load balancing через Traefik
services:
api:
image: myorg/api:1.0
expose: ["3000"]
deploy:
replicas: 4
labels:
- "traefik.http.routers.api.rule=Host(`api.local`)"
- "traefik.http.services.api.loadbalancer.server.port=3000"
- "traefik.http.services.api.loadbalancer.healthcheck.path=/health"
traefik:
image: traefik:v3
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.web.address=:80
ports:
- "80:80"
- "8080:8080" # Traefik-дашборд
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro$ docker compose up -d
$ curl -H 'Host: api.local' http://localhost/
# Round-robin між 4 api-репліками; Traefik чекає /health
$ docker compose up -d --scale api=8
# Traefik автоматично підхоплює нові реплікиBackground-workers, що читають з черги
services:
redis:
image: redis:7
worker:
image: myorg/worker:1.0
deploy:
replicas: 5
environment:
REDIS_URL: redis://redis:6379
depends_on: [redis]П'ять worker-container з'єднуються з тією самою Redis-чергою. Кожен тягне job незалежно. Лінійний speedup при додаванні workers, до пропускної здатності черги.
Scaling у runtime
# Звичайне навантаження
$ docker compose up -d
# 1 api за замовчуванням
# Black Friday
$ docker compose up -d --scale api=20
# 20 api-реплік крутяться
# Після пікового періоду
$ docker compose up -d --scale api=3
# Назад до 3Без рестарту непов'язаних сервісів. Compose коригує лише те, що змінилося.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.