Перейти к содержанию

Подробно об использовании томов в docker

Тома - это постоянные хранилища данных для контейнеров, создаваемые и управляемые Docker. Вы можете создать том явно, используя команду docker volume create, или Docker может создать том во время создания контейнера или сервиса.

Когда вы создаете том, он сохраняется в каталоге на хосте Docker. Когда вы монтируете том в контейнер, именно этот каталог монтируется в контейнер. Это похоже на способ подключения bind (привязки), за исключением того, что тома управляются Docker и изолированы от основных функций хост-компьютера.

Когда использовать тома

Тома являются предпочтительным механизмом хранения данных, создаваемых и используемых контейнерами Docker. В то время когда подключение при помощи привязок зависит от структуры каталогов и операционной системы хост-компьютера, тома полностью управляются Docker и являются хорошим выбором для следующих случаев использования:

  • Резервное копирование томов проще, чем привязок.
  • Управлять томами можно с помощью команд Docker CLI или Docker API.
  • Тома можно использовать как с контейнерами Linux, так и Windows.
  • Тома могут быть более безопасно распределены между несколькими контейнерами для совместного доступа.
  • Содержимое новых томов может быть предварительно заполнено из контейнера при запуске.
  • Более высокая производительность ввода-вывода в сравнении с привязками.

Однако, тома не являются хорошим выбором, если вам нужно получить доступ к файлам с хоста, поскольку том полностью управляется Docker. Используйте подключение каталогов, если вам нужно получить доступ к файлам или каталогам как из контейнеров, так и из хоста.

Тома часто являются лучшим выбором, чем запись данных непосредственно в контейнер, поскольку том не увеличивает размер контейнера. Использование тома также ускоряет работу, в то время как при записи в контейнер требуется драйвер хранилища. Этот драйвер предоставляет объединенную файловую систему, использующую функции ядра Linux. Такая дополнительная абстракция снижает производительность по сравнению с использованием томов, которые записывают данные непосредственно в файловую систему хоста.

Если ваш контейнер создает временные файлы, рассмотрите возможность использования tmpfs, чтобы избежать постоянного хранения таких файлов и повысить производительность, избегая записи внутрь контейнера.

Тома используют rprivate (рекурсивное подключение).

Жизненный цикл тома

Содержимое тома существует вне жизненного цикла контейнера. Когда контейнер уничтожается, вместе с ним уничтожается и его слой, доступный для записи. Использование тома гарантирует сохранение данных даже если контейнер, в котором он использовался, будет удален.

Том может быть подключен к нескольким контейнерам одновременно. Если том не используется ни одним из запущенных контейнеров, он по-прежнему доступен для Docker и не удаляется автоматически. Вы можете удалить неиспользуемые тома с помощью docker volume prune.

Монтирование тома поверх существующих данных

Если вы монтируете непустой том в каталог контейнера, в котором находятся файлы или каталоги, ранее существовавшие файлы будут скрыты при монтировании. Это похоже на то, как если бы вы сохранили файлы в /mnt на хосте Linux, а затем подключили USB-накопитель к /mnt. Содержимое /mnt было бы скрыто содержимым USB-накопителя, пока USB-накопитель не был бы отключен.

В случае с контейнерами нет простого способа удалить точку монтирования, чтобы снова получить доступ к скрытым ранее файлам. Лучший вариант - воссоздать контейнер без монтирования.

Если вы монтируете пустой том в каталог контейнера, в котором существуют файлы или каталоги, эти файлы или каталоги по умолчанию распространяются (копируются) в том. Аналогично, если вы запускаете контейнер и указываете том, который еще не существует, для вас создается пустой том. Это хороший способ предварительно заполнить данные, которые нужны другому контейнеру.

Чтобы запретить Docker копировать ранее существовавшие файлы контейнера на пустой том, используйте параметр volume-nocopy, см. раздел Options for --mount.

Именованные и анонимные тома

Том может быть именованным или анонимным. Анонимным томам присваивается случайное имя, которое гарантированно будет уникальным для данного узла Docker. Как и именованные тома, анонимные тома сохраняются, даже если вы удаляете контейнер, в котором они используются, за исключением случаев, когда вы используете флаг --rm при создании контейнера, и в этом случае анонимный том, связанный с контейнером, уничтожается. см. раздел Remove anonymous volumes.

При последовательном создании нескольких контейнеров, в каждом из которых используются анонимные тома, каждый контейнер создает свой собственный том. Анонимные тома не используются повторно и не используются совместно между контейнерами автоматически. Для совместного использования анонимного тома двумя или более контейнерами необходимо подключить анонимный том, используя идентификатор тома (в данном случае он имеет случайное название).

Синтаксис

Чтобы смонтировать том с помощью команды docker run, вы можете использовать флаг --mount или --volume.

docker run --mount type=volume,src=<volume-name>,dst=<mount-path>
docker run --volume <volume-name>:<mount-path>

Как правило, предпочтение отдается --mount. Основное отличие заключается в том, что флаг --mount является более явным и поддерживает все доступные опции.

Вы должны использовать --mount, если хотите:

Параметры для --mount

Флаг --mount содержит несколько пар ключ-значение, разделенных запятыми, и каждая из них состоит из кортежа <ключ>=<значение>. Порядок следования ключей не имеет значения.

docker run --mount type=volume[,src=<volume-name>],dst=<mount-path>[,<key>=<value>...]

Допустимые параметры для --mount type=volume включают:

Описание Опции
source, src Источник подключения. Для именованных томов это имя тома. Для анонимных томов это поле опущено.
destination, dst, target Путь, по которому файл или каталог монтируется в контейнер.
volume-subpath Путь к подкаталогу в томе, который необходимо подключить к контейнеру. Подкаталог должен существовать в томе, прежде чем том будет подключен к контейнеру. См. раздел Mount a volume subdirectory.
readonly, ro Если присутствует, приводит к тому, что том монтируется в контейнер как доступный только для чтения.
volume-nocopy Если он присутствует, данные в целевом хранилище не копируются в том, если том пуст. По умолчанию содержимое в целевом хранилище копируется в подключенный том, если он пуст.
volume-opt Может быть задан более одного раза, принимает пару ключ-значение, состоящую из названия опции и ее значения.

Пример:

docker run --mount type=volume,src=myvolume,dst=/data,ro,volume-subpath=/foo

Параметры для --volume

Флажок --volume или -v состоит из трех полей, разделенных двоеточием (:). Поля должны располагаться в правильном порядке.

docker run -v [<volume-name>:]<mount-path>[:opts]

В случае именованных томов первое поле представляет собой имя тома и является уникальным для данного хост-компьютера. Для анонимных томов первое поле опущено. Второе поле - это путь, по которому файл или каталог монтируется в контейнер.

Третье поле является необязательным и представляет собой список параметров, разделенных запятыми. Допустимые параметры для --volume включают:

Описание Опции
readonly, ro Если присутствует, приводит к тому, что том монтируется в контейнер как доступный только для чтения.
volume-nocopy Если он присутствует, данные в целевом хранилище не копируются в том, если он пуст. По умолчанию содержимое в целевом хранилище копируется в подключенный том, если он пуст.

Пример:

docker run -v myvolume:/data:ro

Создание томов и управление ими

В отличие от подключения с привязкой, вы можете создавать тома и управлять ими за пределами любого контейнера.

Создайте том:

docker volume create my-vol

Выведите список томов:

docker volume ls

Выведите информацию о томе:

docker volume inspect my-vol

Удалите том:

docker volume rm my-vol

Запуск контейнера с томом

Если вы запускаете контейнер с томом, который еще не существует, Docker создает том его. В следующем примере том myvol2 монтируется в /app/ в контейнере.

Следующие примеры -v и --mount дают тот же результат. Вы не сможете запустить их оба, если не удалите контейнер devtest и том myvol2 после запуска первого.

docker run -d \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest
docker run -d \
  --name devtest \
  -v myvol2:/app \
  nginx:latest

Используйте docker inspect devtest, чтобы убедиться, что Docker создал том и он правильно подключен. Обратите внимание на секцию "Mounts".:

"Mounts": [
    {
        "Type": "volume",
        "Name": "myvol2",
        "Source": "/var/lib/docker/volumes/myvol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

Это показывает, что подключен том, указаны правильные источник и место назначения, а также что подключен режим чтения-записи.

Остановите контейнер и извлеките том. Обратите внимание, что удаление тома - это отдельный шаг.

docker container stop devtest
docker container rm devtest
docker volume rm myvol2

Использование тома с помощью Docker Compose

В следующем примере показан один сервис Docker Compose с томом:

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:

При первом запуске docker compose up создается том. Docker повторно использует тот же том при последующем запуске команды.

Вы можете создать том непосредственно вне Compose, используя docker volume create, а затем сослаться на него внутри compose.yaml следующим образом:

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    external: true

Для получения дополнительной информации об использовании томов с Compose обратитесь к разделу Volumes в спецификации Compose.

Запуск сервиса с помощью volumes

Когда вы запускаете сервис и определяете том, каждый контейнер сервиса использует свой собственный локальный том. Ни один из контейнеров не может совместно использовать эти данные, если вы используете драйвер локального тома. Однако некоторые драйверы томов поддерживают совместное хранилище.

В следующем примере сервис nginx запускается с четырьмя репликами, каждая из которых использует локальный том с именем myvol2.

docker service create -d \
  --replicas=4 \
  --name devtest-service \
  --mount source=myvol2,target=/app \
  nginx:latest

Используйте docker service ps devtest-service, чтобы убедиться, что сервис запущен:

docker service ps devtest-service

Вы можете удалить сервис, чтобы остановить запущенные задачи:

docker service rm devtest-service

Удаление сервиса не приводит к удалению томов, созданных сервисом. Удаление тома - это отдельный шаг.

Заполнение тома с помощью контейнера

Если вы запускаете контейнер, который создает новый том, и в контейнере есть файлы или каталоги в каталоге, который нужно смонтировать, например /app/, Docker копирует содержимое каталога в том. Затем контейнер монтирует и использует том, а другие контейнеры, которые используют этот том, также получают доступ к предварительно заполненному содержимому.

Чтобы показать это, в следующем примере запускается контейнер nginx и новый том nginx-vol заполняется содержимым каталога контейнера /usr/share/nginx/html. Именно здесь Nginx хранит свое HTML-содержимое по умолчанию.

Следующие примеры --mount и -v имеют одинаковый конечный результат.

docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html \
  nginx:latest
docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html \
  nginx:latest

После выполнения любого из этих примеров выполните следующие команды для очистки контейнеров и томов. Обратите внимание, что удаление томов - это отдельный шаг.

docker container stop nginxtest

docker container rm nginxtest

docker volume rm nginx-vol

Использование тома, доступного только для чтения

Некоторым приложениям контейнеров необходимо выполнить запись, чтобы изменения передавались на хост Docker. В других случаях контейнеру требуется доступ к данным только для чтения. Несколько контейнеров могут монтировать один и тот же том. Вы можете одновременно монтировать один том как доступный для чтения и записи для одних контейнеров и как доступный только для чтения для других.

Следующий пример меняет предыдущий. В нем каталог монтируется как том, доступный только для чтения, путем добавления ro в список параметров после точки монтирования внутри контейнера. Если имеется несколько параметров, вы можете разделить их запятыми.

Примеры --mount и -v дают одинаковый результат.

docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest
docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html:ro \
  nginx:latest

Используйте docker inspect nginxtest, чтобы убедиться, что Docker правильно создал монтирование только для чтения. Найдите раздел Mounts:

"Mounts": [
    {
        "Type": "volume",
        "Name": "nginx-vol",
        "Source": "/var/lib/docker/volumes/nginx-vol/_data",
        "Destination": "/usr/share/nginx/html",
        "Driver": "local",
        "Mode": "",
        "RW": false,
        "Propagation": ""
    }
],

Закройте и удалите контейнер, а также удалите том. Удаление тома - это отдельный этап.

docker container stop nginxtest

docker container rm nginxtest

docker volume rm nginx-vol

Подключение подкаталога тома

Когда вы подключаете том к контейнеру, вы можете указать подкаталог используемого тома, указав параметр volume-subpath для флага --mount. Указанный вами подкаталог должен существовать в томе, прежде чем вы попытаетесь смонтировать его в контейнер. Если он не существует, то при монтировании произойдет сбой.

Указание volume-subpath полезно, если вы хотите совместно использовать только определенную часть тома с контейнером. Допустим, например, что у вас запущено несколько контейнеров и вы хотите хранить журналы из каждого контейнера в общем томе. Вы можете создать подкаталог для каждого контейнера в общем томе и подключить этот подкаталог к контейнеру.

В следующем примере создается том logs и инициализируются подкаталоги app1 и app2 в томе. Затем запускаются два контейнера и подключается один из подкаталогов тома logs к каждому контейнеру. В этом примере предполагается, что процессы в контейнерах записывают свои журналы в /var/log/app1 и /var/log/app2.

docker volume create logs

docker run --rm \
  --mount src=logs,dst=/logs \
  alpine mkdir -p /logs/app1 /logs/app2

docker run -d \
  --name=app1 \
  --mount src=logs,dst=/var/log/app1,volume-subpath=app1 \
  app1:latest

docker run -d \
  --name=app2 \
  --mount src=logs,dst=/var/log/app2,volume-subpath=app2 \
  app2:latest

При такой настройке контейнеры записывают свои журналы в отдельные подкаталоги тома logs. Контейнеры не могут получить доступ к журналам другого контейнера.

Обмен данными между компьютерами

При создании отказоустойчивых приложений может потребоваться настроить несколько реплик одного и того же сервиса для доступа к одним и тем же файлам.

shared storage

При разработке приложений есть несколько способов добиться этого. Один из них - добавить в приложение логику для хранения файлов в облачной системе хранения объектов, такой как Amazon S3. Другой - создать тома с драйвером, поддерживающим запись файлов во внешнюю систему хранения, такую как NFS или Amazon S3.

Драйверы томов позволяют абстрагировать базовую систему хранения от логики приложения. Например, если ваши сервисы используют том с драйвером NFS, вы можете обновить сервисы, чтобы они использовали другой драйвер. Например, для хранения данных в облаке без изменения логики приложения.

Использование драйвера тома

Когда вы создаете том с помощью docker volume create или когда вы запускаете контейнер, который использует еще не созданный том, вы можете указать драйвер тома. В следующих примерах используется драйвер тома rclone/docker-volume-rclone, сначала при создании автономного тома, а затем при запуске контейнера, который создает новый том.

Примечание

Если ваш драйвер тома принимает список опций, разделенный запятыми, вы должны экранировать значение из внешнего CSV-парсера. Чтобы экранировать параметр volume-opt, заключите его в двойные кавычки (""), а весь параметр mount - в одинарные кавычки ('').

Например, драйвер local принимает параметры монтирования в виде списка, разделенного запятой, в параметре o. В следующем примере показан правильный способ.

docker service create \
--mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
--name myservice \
<IMAGE>

Первоначальная настройка

В следующем примере предполагается, что у вас есть два узла, первый из которых является узлом Docker и может подключаться ко второму узлу по SSH.

На узле Docker установите плагин rclone/docker-volume-rclone:

$ docker plugin install --grant-all-permissions rclone/docker-volume-rclone --aliases rclone

Создание тома, используя драйвер тома

В этом примере каталог /remote на хосте 1.2.3.4 монтируется в том с именем rclonevolume. Каждый драйвер тома может иметь ноль или более настраиваемых параметров, вы указываете каждый из них с помощью флажка -o.

docker volume create \
  -d rclone \
  --name rclonevolume \
  -o type=sftp \
  -o path=remote \
  -o sftp-host=1.2.3.4 \
  -o sftp-user=user \
  -o "sftp-password=$(cat file_containing_password_for_remote_host)"

Теперь этот том можно монтировать в контейнеры.

Запуск контейнера, который создает том с помощью драйвера тома

Примечание

Если драйвер тома требует, чтобы вы передавали какие-либо параметры, вы должны использовать флаг --mount для подключения тома, а не -v.

docker run -d \
  --name rclone-container \
  --mount type=volume,volume-driver=rclone,src=rclonevolume,target=/app,volume-opt=type=sftp,volume-opt=path=remote, volume-opt=sftp-host=1.2.3.4,volume-opt=sftp-user=user,volume-opt=-o "sftp-password=$(cat file_containing_password_for_remote_host)" \
  nginx:latest

Создание сервиса, который создает том NFS

В следующем примере показано, как можно создать том NFS при создании сервиса. В качестве сервера NFS используется 10.0.0.10, а в качестве экспортируемого каталога на сервере NFS - /var/docker-nfs. Обратите внимание, что указанный драйвер тома является локальным.

NFSv3

docker service create -d \
  --name nfs-service \
  --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
  nginx:latest

NFSv4

docker service create -d \
    --name nfs-service \
    --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest

Создание томов CIFS/Samba

Вы можете подключить общий ресурс Samba непосредственно в Docker, не настраивая точку монтирования на вашем хосте.

docker volume create \
    --driver local \
    --opt type=cifs \
    --opt device=//uxxxxx.your-server.de/backup \
    --opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \
    --name cifs-volume

Параметр addr необходим, если вы указываете имя хоста вместо IP. Это позволяет Docker выполнять поиск по имени хоста.

Блочные устройства хранения данных

Блочное устройство хранения данных, такое как внешний диск или раздел диска, можно подключить к контейнеру. В следующем примере показано, как создать и использовать файл в качестве блочного устройства хранения данных и как подключить блочное устройство в качестве тома контейнера.

Важно

Следующая процедура является лишь примером. Решение, показанное здесь, не рекомендуется использовать в качестве общепринятой практики. Не пытайтесь использовать этот подход, если вы не уверены в том, что делаете.

Как работает подключение блочных устройств

По сути, флаг --mount с использованием драйвера локального хранилища вызывает системный вызов Linux mount и перенаправляет параметры, которые вы ему передаете, без изменений. Docker не реализует никаких дополнительных функций в дополнение к встроенным функциям монтирования, поддерживаемым ядром Linux.

Если вы знакомы с командой Linux mount, вы можете представить, что параметры --mount перенаправляются в команду mount следующим образом:

mount -t <mount.volume-opt.type> <mount.volume-opt.device> <mount.dst> -o <mount.volume-opts.o>

Чтобы объяснить это подробнее, рассмотрим следующий пример команды mount. Эта команда подключает устройство /dev/loop5 к пути /external-drive в системе.

mount -t ext4 /dev/loop5 /external-drive

Следующая команда docker run дает аналогичный результат с точки зрения запускаемого контейнера. Запуск контейнера с параметром --mount приводит к монтированию таким же образом, как если бы вы выполнили команду mount из предыдущего примера.

docker run \
  --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4'

Вы не можете запустить команду mount непосредственно внутри контейнера, потому что контейнер не может получить доступ к устройству /dev/loop5. Вот почему команда docker run использует параметр --mount.

Пример: Подключение блочного устройства в контейнер

На следующих этапах создается файловая система ext4 и монтируется в контейнер. Поддержка файловой системы в вашей системе зависит от используемой версии ядра Linux.

Создаем файл определенного размера:

fallocate -l 1G disk.raw

Форматируем содержимое файла:

mkfs.ext4 disk.raw

Создаем устройство:

losetup -f --show disk.raw /dev/loop5

Примечание

losetup создает временное устройство, которое удаляется после перезагрузки системы или вручную с помощью losetup -d.

Запустим контейнер, который монтирует устройство как том:

docker run -it --rm \
--mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' \
ubuntu bash

Когда контейнер запускается, путь /external-drive монтирует файл disk.raw из файловой системы хоста в качестве блочного устройства.

Когда вы закончите и устройство будет отсоединено от контейнера, отсоедините устройство, чтобы удалить его из основной системы:

losetup -d /dev/loop5

Создание резервных копий, восстанавление или перенос тома

Тома полезны с точки зрения резервного копирования, восстановления и миграции. Используйте флажок --volumes-from, чтобы создать новый контейнер, который подключит этот том.

Создание резервной копии тома

Например, создайте новый контейнер с именем dbstore:

docker run -v /dbdata --name dbstore ubuntu /bin/bash

В следующей команде:

  • Запускается новый контейнер и подключается том из контейнера dbstore
  • Подключается локальный каталог хоста как /backup
  • Передается команда, которая преобразует содержимое тома dbdata в файл backup.tar в каталоге /backup.
docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

Когда команда завершается и контейнер останавливается, создается резервная копия тома dbdata.

Восстановление тома из резервной копии

Используя только что созданную резервную копию, вы можете восстановить ее в том же контейнере или в другом контейнере, созданном в другом месте.

Например, создайте новый контейнер с именем dbstore2:

docker run -v /dbdata --name dbstore2 ubuntu /bin/bash

Затем распакуйте файл резервной копии в том нового контейнера:

docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

Вы можете использовать эти методы для автоматизации резервного копирования, миграции и тестирования восстановления с помощью предпочитаемых вами инструментов.

Удаление томов

Том с данными Docker сохраняется после удаления контейнера. Следует учитывать два типа томов:

  • У именованных томов есть определенный источник, находящийся за пределами контейнера, например, awesome:/bar.
  • У анонимных томов нет определенного источника. Поэтому, когда контейнер будет удален, вы можете дать указание демону Docker Engine удалить их.

Удаление анонимных томов

Чтобы автоматически удалить анонимные тома, используйте параметр --rm. Например, эта команда создает анонимный том /foo. Когда вы удаляете контейнер, Docker Engine удаляет том /foo, но не том awesome.

docker run --rm -v /foo -v awesome:/bar busybox top

Примечание

Если другой контейнер связывает тома с помощью --volumes-from, определения томов копируются, и анонимный том также остается после удаления первого контейнера.

Удаление всех томов

Чтобы удалить все неиспользуемые тома и освободить место, выполните:

docker volume prune