Skip to main content

Як поділитись даними між контейнерами?

Поділ даних між Docker container має три хороші відповіді залежно від того, що ти маєш на увазі під «дані». Файли? Shared volume. Повідомлення? Network-виклики. Broadcast? Черга. Правильний вибір важить, бо неправильний дає concurrency-баги на масштабі.

Теорія

TL;DR

  • Файли (read-mostly або single-writer): named volume, змонтований у кілька container.
  • Service-to-service (RPC, HTTP): Docker-мережа. Container викликають одне одного по імені.
  • Багато writer, queue-семантика: message-queue (Redis, RabbitMQ, Kafka).
  • Read-only config: bind-mount того самого файлу у кожен container, або Docker configs / Swarm secrets.
  • Уникай --volumes-from для нового коду, працює, але це legacy-форма. Бери named volume.

Патерн 1: shared named volume

bash
docker volume create shared docker run -d --name writer \ -v shared:/data \ alpine sh -c 'while true; do date >> /data/log.txt; sleep 5; done' docker run --rm -v shared:/data alpine cat /data/log.txt # Wed Apr 30 12:00:00 UTC 2026 # Wed Apr 30 12:00:05 UTC 2026 # ...

Обидва container бачать той самий /data/log.txt. Конкурентні записи потребують app-level координації (сам volume не lock'ить).

Патерн 2: network-комунікація

Часто правильна відповідь, коли «дані» це насправді сервіс або RPC, не файли.

yaml
services: api: image: myapp environment: WORKER_URL: http://worker:5000 worker: image: myworker expose: ["5000"]

api викликає http://worker:5000/.... Розшарені дані це API-контракт, не файли.

Коли краще, ніж shared volume:

  • Кілька writer з різних процесів (race condition у shared-файлах vs послідовні API-виклики).
  • Cross-host операції (volume локальні; мережі span'ять host через overlay).
  • Версіонування контракту (HTTP/gRPC мають явні schema; сирі файли ні).

Патерн 3: message-queue

Для broadcast, fan-out, async work-distribution:

yaml
services: redis: image: redis:7 producer: image: myproducer environment: { REDIS_URL: redis://redis:6379 } depends_on: [redis] worker: image: myworker deploy: { replicas: 5 } environment: { REDIS_URL: redis://redis:6379 } depends_on: [redis]

Producer push'ить повідомлення у Redis. П'ять workers pull'ять і обробляють. Дані течуть через чергу, не через shared filesystem.

Той самий патерн з RabbitMQ, Kafka, NATS. Обирай за гарантіями (at-least-once, at-most-once, ordering, persistence).

Патерн 4: read-only config-sharing

Кілька container потребують той самий config-файл:

yaml
services: web: image: nginx volumes: - ./shared.conf:/etc/myapp/config.conf:ro api: image: myapp volumes: - ./shared.conf:/etc/myapp/config.conf:ro

Bind-mount той самий host-файл в обидва. :ro робить read-only. Edit на host, restart container, обидва підхоплюють новий config.

Для Swarm або K8s бери configs (docker config create) і secrets (docker secret create), вони розподіляють файли через кластер.

--volumes-from (legacy)

bash
docker run -d --name data -v /shared alpine docker run --rm --volumes-from data alpine ls /shared

Другий container успадковує volume-mount першого. Спочатку використовувалося для «data-container», патерн з часів до named volume. Сьогодні named volume чистіше. Уникай --volumes-from у новому коді.

Підступи конкурентного доступу

Shared volume це просто директорія. OS магічно не серіалізує записи між container. Поширені gotcha:

bash
# Container A пише file.json # Container B читає file.json У ТОЙ ЖЕ ЧАС # B може прочитати partial-write → JSON-parse error

Mitigation:

  • Атомарні записи: пиши у tmp.json, потім mv tmp.json file.json (атомарно на тій самій filesystem).
  • File-locks: flock, але координація між container потребує shared-lock-файл.
  • Single-writer-конвенція: лише один container коли-небудь пише; інші читають.
  • Бери чергу або DB натомість, коли конкурентний доступ норма.

Типові помилки

Розшарювати volume для того, що має бути RPC

bash
# НЕПРАВИЛЬНО: api пише request-файл, worker стежить за директорією docker run -d --name api -v shared:/queue myapp docker run -d --name worker -v shared:/queue myworker # Race condition, file-locking жах. # ПРАВИЛЬНО: api викликає worker через HTTP # АБО producer push'ить у Redis, workers споживають

File-as-message це патерн, що змусив людей винайти message queue.

Bind-mount host-шляхів і припущення портативності

yaml
volumes: - /home/user/myapp/data:/data # Працює на host A. Падає на host B, якщо /home/user/myapp/data не існує.

Named volume портативні; bind mount прив'язує до конкретної host-filesystem. Для shared-даних на multi-host бери network volume-driver або network-сервіс.

Забути permission через container

Container A крутиться як UID 1000, пише файли. Container B як root, без проблем. Container C як UID 1001, не може прочитати, що A написав (perm 700, owner 1000).

Обери UID-стратегію: той самий UID між container, або файли пишуться з permissive-mode (umask 002).

Монтувати той самий volume read-write всюди

Корисно, коли багато reader не потребують писати. Mount writer RW, reader RO:

yaml
writer: volumes: [shared:/data] reader-1: volumes: [shared:/data:ro] # ← read-only reader-2: volumes: [shared:/data:ro]

Ловить випадкові записи від reader.

Реальне застосування

  • Web + reverse-proxy ділять серти: і nginx, і certbot монтують /etc/letsencrypt, тож certbot пише, nginx читає.
  • App + log-shipper sidecar: обидва монтують /var/log/app volume; застосунок пише, fluentd читає і шиппить.
  • Backup-container: read-only mount прод-volume, крутить tar у backup-destination.
  • Build-артефакти: CI білдить у container A, container B (publish) читає результат через shared volume, змонтований в обидва.
  • Init-container патерн (K8s, також можна у Compose з service_completed_successfully): init пише config, app його читає.

Питання для поглиблення

Q: Чи можуть два container писати у той самий файл одночасно?


A: Так, але OS не координує. Last-write-wins або partial-write ймовірні. App-level координація (lock, атомарні записи, append-only логи) це твоя відповідальність.

Q: Чи безпечні shared volume через host-reboot?


A: Так, volume персистентні. Reboot host, restart container з тими ж volume-mount, дані цілі.

Q: Яка різниця між shared volume і bind mount того самого host-шляху?


A: Функціонально схоже, обидва дають кільком container бачити ті самі файли. Volume Docker-managed (портативні, life-cycle контрольовано), bind mount пінується до host-шляху (не портативно).

Q: Як ділити дані між container на різних host?


A: Кілька опцій. (1) Network-сервіс (DB, Redis), доступний з обох host. (2) Network-filesystem (NFS, GlusterFS, EFS), змонтована на обох host і bind-mounted у container. (3) Object-storage (S3, Minio) з обома container як client.

Q: (Senior) Коли sharing volume стає архітектурним smell?


A: Коли два сервіси ділять стан без чіткого owner, обидва пишуть у ті самі файли. Цей coupling робить scaling їх незалежно неможливим: баг одного стає проблемою іншого. Smell супроводжується фіксом: introduce owning-сервіс (DB, queue), що стає єдиним source of truth, з обома «sharers» як клієнтами. Filesystem-as-IPC нормально для cache і immutable-даних; для живого mutable-стану бери сервіс.

Приклади

Sidecar log-shipper патерн

yaml
services: app: image: myapp volumes: - applogs:/var/log/app fluentd: image: fluent/fluentd volumes: - applogs:/fluentd/log:ro # read-only, fluentd не пише app-логи - ./fluent.conf:/fluentd/etc/fluent.conf:ro depends_on: [app] volumes: applogs:

App пише логи у volume, fluentd їх читає і шиппить у aggregator. Loose coupling, єдиний owner записів.

Build-and-publish pipeline

yaml
services: build: image: builder volumes: - artifact:/out command: build-script.sh restart: "no" publish: image: publisher volumes: - artifact:/in:ro # publisher не модифікує depends_on: build: condition: service_completed_successfully restart: "no" volumes: artifact:

Build-стейдж дає файли, publish-стейдж їх споживає. Volume це передача. service_completed_successfully робить порядок явним.

Cert-sharing між web і certbot

yaml
services: web: image: nginx:1.27-alpine volumes: - certs:/etc/letsencrypt:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: ["80:80", "443:443"] certbot: image: certbot/certbot volumes: - certs:/etc/letsencrypt # certbot пише - ./acme-challenge:/var/www/acme-challenge entrypoint: | sh -c "trap exit TERM; while :; do certbot renew --webroot -w /var/www/acme-challenge --quiet nginx -s reload sleep 12h & wait $${!}; done"

Certbot пише серти у volume; nginx їх читає. Renewal трапляється періодично; nginx reload підхоплює нові серти без рестарту container.

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

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

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

Коментарі

Ще немає коментарів