Skip to main content

Як реалізувати масштабування сервісів у 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 або подібне.

Швидкий приклад

yaml
# 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'ить)
bash
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

bash
# Збільшити до 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-записами:

bash
$ 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-клієнтів так.

Чому фіксовані порти ламаються з репліками

yaml
services: api: image: myapi ports: - "3000:3000" # ← проблема при scale deploy: replicas: 3
bash
$ docker compose up -d ERROR: only one of api can be running at port 3000

Два container не можуть bind на той самий host-порт. Рішення:

  1. Випадкові host-порти (ports: ["3000"] без : mapping), Docker обирає вільні порти.
  2. expose: only, тільки внутрішній доступ; reverse proxy обробляє external.
  3. Range mapping, ports: ["3000-3009:3000"] алокує діапазон.

Прод-відповідь майже завжди #2: reverse proxy перед, репліки внутрішні.

Reverse-proxy патерн

yaml
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)

yaml
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:ro

Traefik стежить за 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-сервісів

yaml
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

bash
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

yaml
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
bash
$ 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, що читають з черги

yaml
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

bash
# Звичайне навантаження $ 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 коригує лише те, що змінилося.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?