Як реалізувати масштабування сервісів у 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 коригує лише те, що змінилося.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів