Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як реалізувати масштабування сервісів у Docker Compose?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Бери `docker compose up --scale <service>=N`** або постав `deploy.replicas` у Compose-файлі. Compose крутить N container того сервісу на тому самому host. ```bash docker compose up -d --scale api=5 # АБО у compose.yaml services: api: deploy: replicas: 5 ``` **Головне:** масштабування у Compose це **single-host only**. Кілька реплік одного сервісу ділять порт → бери `expose:` (без `ports:`) і load balancer перед ними. Для multi-host масштабування переходь на Swarm або Kubernetes.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Масштабування сервісів у 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 коригує лише те, що змінилося.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.