Volumes в Docker
Проброс папок и томов в Docker решает следующие проблемы, позволяя монтировать директории хоста внутри контейнера либо создавая централизованное хранилище. Таким образом мы получаем следующие преимущества:
- Мы не копируем данные, они хранятся в одно месте для всех контейнеров
- Раньше копирование отнимало время, а сейчас это делать не нужно, контейнеры запускаются быстрее
- У нас появляется больше возможностей для управления данными
Типы томов Docker
Есть два основных способа обмена данными с контейнером, которые часто называют томами:
- Перенаправление папки или файла с хоста в контейнер, называется
bind mount
- Создание специального объекта, который имеет больше возможностей управления данными
volume
Основное различие этих двух типов в том, что для volume
есть отдельные команды по его созданию, просмотру и удалению в самом Docker. Он так же представляет собой папку в файловой системе хоста, которая по умолчанию определена настройками Docker. Еще одно отличие, это поведение по умолчанию bind mount
и volume
. Для примера, внутри контейнера, по пути /usr/share/nginx/html/
лежит файл index.html
. В случае проброса томов в эту папку поведение будет разным:
- В случае
bind mount
файлindex.html
, внутри контейнера, будет удален. Это произойдет даже если папка хоста пустая - В случае
volume
при первом использовании файлindex.html
будет скопирован. При последующих удален
Создание Volumes через docker run
Для монтирования данных используются следующие параметры:
-v
или--volume
--mount
Их различие в том, что bind mount
более явно заставляет указывать источник и цель монтирования папки. Вы можете использовать эти параметры совместно, отдельно, повторяя несколько раз, ограничений нет.
Папки и файлы
Для примера, у меня есть следующая папка на хосте:
/home/alex/docker_data
В случае параметра -v
указывается два пути откуда:куда
. В случае --mount
это именованные параметры разделенные запятыми, пример работы обоих:
-v /home/alex/docker_data:/usr/share/nginx/html
--mount type=bind,source=/home/alex/docker_data,destination=/usr/share/nginx/html
В mount
мы используем следующие параметры:
type
со значениемbind
говорит, что мы монтируем папку или файлsource
источник, папка или файл, который мы хотим подключить к контейнеруdestination
папка или файл внутри контейнера
В обоих случаях мы можем монтировать данный доступные только для чтения read-only
добавив ro
в конце:
-v /home/alex/docker_data:/usr/share/nginx/html:ro
--mount type=bind,source=/home/alex/docker_data,destination=/usr/share/nginx/html,ro
Так выглядит запуск контейнера с проброшенной папкой:
docker run -d --name nginx_vol1 -v /home/alex/docker_data:/usr/share/nginx/html:ro nginx
docker run -d --name nginx_vol2 --mount type=bind,source=/home/alex/docker_data,destination=/usr/share/nginx/html,ro nginx
Можно задать следующие опции:
rprivate
private
rshared
shared
rslave
slave
ro
режим только для чтенияz
папка на хосте может быть использована несколькими контейнерамиZ
папка используется только одним контейнером
Первые шесть параметров позволяют управлять тем, как будут влиять изменения в одной точке монтирования тома на другие точки монтирования. По умолчанию используется rprivate
, что означает — никак.
Подключение volume
При монтировании тома нужно учитывать следующие моменты:
- Название тома указывается без слешей
- Если тома с этим названием нет, он будет создан
- В случае с
mount
, в параметреtype
, указываетсяvolume
При использовании docker run
использование томов будет выглядеть так:
docker run -d --name nginx_vol1 -v docker_volume:/usr/share/nginx/html nginx
docker run -d --name nginx_vol2 --mount type=volume,source=docker_volume,destination=/usr/share/nginx/html nginx
Так же как и с папками мы можем добавить :ro
или ,ro
в конец значения, что бы дать права только на чтение директорий.
В предыдущем примере один том был подключен к двум контейнерам. Их совместную работу можно проверить создав файл в одном контейнере, а вывести через другой:
# создаем файл в одном контейнере
docker exec nginx_vol1 touch /usr/share/nginx/html/file1
# проверяем файл через другой контейнер
docker exec nginx_vol2 ls /usr/share/nginx/html
Создание Volumes через Dockerfile
При создании образа через Dockerfile у вас есть возможность создать том, но не использовать существующий. Смонтировать папку, через Dockerfile, нельзя.
Создание тома будет иметь ряд ограничений:
- Вы не сможете указать имя тома или выбрать существующий. Имя будет сгенерировано автоматически
- В любом случае том будет создан во время запуска контейнера т.е. так же как и в случае использования
-v
- Каждое создание контейнера будет создавать новый том
Для создания тома есть инструкция VOLUME
, пример синтаксиса:
FROM nginx
# указываем точку монтирования внутри контейнера
VOLUME /somedata1
Примерный результат где запускаются 2 контейнера с одного образа:
Аналогичный результат можно получить используя одну из следующих команд:
docker run -v /somedata1 nginx
docker run -v $(docker volume create):/somedata1 nginx
Если вы используете инструкцию VOLUME
и параметр -v
указывающий на одну и ту же директорию, то -v
возьмет верх.
Создание Volumes через docker-compose
Docker-compose
позволяет запускать несколько контейнеров используя один файл инструкций. Синтаксис монтирования томов может быть ограниченным и расширенным так же как -v
и --mount
.
Для монтирования тома, кроме инструкции в самом контейнере, нужно указать дополнительную инструкцию volumes
в верхнем уровне. Для папки этого делать не нужно:
version: "3.8"
services:
web:
image: nginx:alpine
volumes:
# том
- somevol:/app
# папка
- /home/alex:/app2
# для тома
volumes:
somevol:
Том somevol
может использоваться совместно в нескольких контейнерах.
Если нам нужно дать права на том или папку, то мы просто добавляем ro
или rw
в конец пути:
...
volumes:
# том
- somevol:/app:ro
# папка
- /home/alex:/app2:rw
...
Для монтирования так же есть расширенный синтаксис, похожий на команду mount
в docker. Следующий пример аналогичен предыдущем по эффекту:
version: "3.8"
services:
web:
image: nginx:alpine
volumes:
# том
- type: volume
source: somevol
target: /app1
# папка
- type: bind
source: /home/alex
target: /app2
volumes:
somevol:
Есть еще инструкции, которые вы можете использовать. Ниже только их часть, но они используются редко:
volumes:
- type: volume
source: somevol
target: /app1
# папка только для чтения
read_only: true
# не будет копировать файлы в том, которые уже находятся в контейнере
volume:
nocopy: true
# папка
- type: bind
source: /home/alex
target: /app2
# папка только для чтения
read_only: true
# создаст папку на хосте если ее нет
create_host_path: true
Мы можем использовать один и тот же том в нескольких контейнерах (сервисах). Кроме этого есть инструкция volumes_from
, которая использует тома с указанного контейнера, ниже оба примера:
version: "3.8"
services:
container1:
image: nginx:alpine
volumes:
- somevol:/app1
- /home/alex:/app2
container2:
image: nginx:alpine
volumes:
# тот же том, но в другом контейнере
- somevol:/app2
container3:
image: nginx:alpine
# берем тома из сервиса container1
# с доступностью только на чтение
volumes_from:
- container1:ro
volumes:
somevol:
Использование внешних томов
Если вам нужно использовать том, который был создан не в текущем файле docker-compose.yml
, то вы можете его указать через параметр external
, автоматический такой том не создается:
...
volumes:
somevol:
external: true
Создание тома в другой директории
Через docker-compose.yml
мы можем указывать драйвера и опции. Например, создадим тома в другой директории по аналогии с тем, что делали выше:
...
volumes:
my_test_volume:
driver: local
driver_opts:
o: bind
type: none
device: /home/alex/compose_vol1
Монтирование через Tmpfs
Еще одним способом монтирования томов является tmpfs
. Данные этого тома хранятся в оперативной памяти. При остановке контейнера, в отличие от других томов, данные будут удалены. Эти данные просто не выгружаются из оперативной памяти. Такой тип тома вы можете создать только на одном контейнере и только в Linux.
Такие типы хранилищ редко используются. Их можно использовать для хранения чувствительных данных (для безопасности) или что бы ускорить работу какого-то приложения, но оба варианта, обычно, реализовываются на стороне приложения. Есть два способа создания tmpfs
.
Первый способ:
docker run \
--tmpfs /app \
nginx:latest
# или
docker run -d \
--mount type=tmpfs,destination=/app,tmpfs-size=400,tmpfs-mode=1777 \
nginx:latest
При использовании параметра --tmpfs
вы можете указать только директорию, которую планируете использовать.
При использовании mount
у вас появляются не обязательные параметры:
tmpfs-size
размер в байтах, по умолчанию не ограниченtmpfs-mode
права на файлы, по умолчанию 1777. Можно не указывать специальные разрешения (т.е. 700, например).
Второй способ через docker-compose.yml
:
volumes:
foo:
driver: local
driver_opts:
type: "tmpfs"
o: "o=size=100m,uid=1000"
device: "tmpfs"
Просмотр привязанных томов
Вы можете объявлять тома внутри смонтированных папок и наоборот. Это может создавать путаницу, но это требуется в определенных ситуациях. Например некоторые фреймворки используют следующую структуру хранение модулей и приложений:
docker inspect название_контейнера --format "'{{json .Mounts}}'"
sudo docker inspect название_контейнера | grep Mountpoint | awk '{ print $2 }'
Привязка томов из другого контейнера
С помощью параметра --volumes-from
мы можем скопировать тома у запущенного или остановившегося тома, в значении мы указываем контейнер:
# контейнер 1
docker run -v $(pwd)/app:/usr/src/app \
-v node_modules:/usr/src/app/node_modules \
--name node1 \
node
# контейнер 2
docker run --volumes-from node1 --name node2 node
Создание и удаление Volume
volume
это отдельны объект у docker есть команды, с помощью которых можно им управлять:
Выведет список томов:
docker volume ls
Покажет подробную информацию о томе, даже его расположение на хосте:
docker volume inspect
Создание нового тома:
docker volume create
Удалит все тома, которые не используются контейнерами:
docker volume prune
Удалит один том:
docker volume rm