Як поділитись даними між контейнерами?
Поділ даних між 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
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, не файли.
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:
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-файл:
services:
web:
image: nginx
volumes:
- ./shared.conf:/etc/myapp/config.conf:ro
api:
image: myapp
volumes:
- ./shared.conf:/etc/myapp/config.conf:roBind-mount той самий host-файл в обидва. :ro робить read-only. Edit на host, restart container, обидва підхоплюють новий config.
Для Swarm або K8s бери configs (docker config create) і secrets (docker secret create), вони розподіляють файли через кластер.
--volumes-from (legacy)
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:
# Container A пише file.json
# Container B читає file.json У ТОЙ ЖЕ ЧАС
# B може прочитати partial-write → JSON-parse errorMitigation:
- Атомарні записи: пиши у
tmp.json, потімmv tmp.json file.json(атомарно на тій самій filesystem). - File-locks:
flock, але координація між container потребує shared-lock-файл. - Single-writer-конвенція: лише один container коли-небудь пише; інші читають.
- Бери чергу або DB натомість, коли конкурентний доступ норма.
Типові помилки
Розшарювати volume для того, що має бути RPC
# НЕПРАВИЛЬНО: 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-шляхів і припущення портативності
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:
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/appvolume; застосунок пише, 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 патерн
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
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
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів