Руководство по томам Docker (Docker Volumes)
Контейнеры — это изолированные среды в контексте системы Linux, которые могут выделять заранее определенные объемы конкретных ресурсов. Чаще всего контейнеры Docker используются для запуска приложений в изоляции. По умолчанию все изменения внутри контейнера теряются, когда контейнер останавливается. Если мы хотим сохранять данные между запусками, могут помочь тома Docker и bind mounts.
В этом руководстве мы узнаем о томах Docker и о том, как управлять ими и подключать к контейнерам.
1. Файловая система Docker
Контейнеры Docker запускают программный стек, определенный в образе Docker. Образы состоят из набора слоев только для чтения, которые работают на файловой системе, называемой Union Filesystem. Когда мы запускаем новый контейнер, Docker добавляет слой с правами на запись поверх слоев образа, позволяя контейнеру работать так, как если бы он находился на стандартной файловой системе Linux.
Таким образом, любое изменение файла внутри контейнера создает рабочую копию в слое с правами на запись. Однако, когда контейнер останавливается или удаляется, этот слой с правами на запись теряется.
Мы можем это проверить, выполнив команду, которая записывает файл, а затем читает его:
$ docker run bash:latest \
bash -c "echo hello > file.txt && cat file.txt"
В результате:
hello
Однако, если мы запустим тот же образ с командой, которая просто выводит содержимое того же файла, мы получим ошибку:
$ docker run bash:latest bash -c "cat file.txt"
cat: can't open 'file.txt': No such file or directory
Второй запуск контейнера снова начинается с чистой файловой системы, поэтому файл не найден. Чтобы сохранить файлы между запусками контейнера, мы можем использовать тома.
2. Bind mounts против томов Docker
Поскольку как bind mounts, так и тома Docker используются как средства для обеспечения постоянного хранения.
Тем не менее, многие реализации предлагают bind mounts:
- Ядро Linux
- Виртуализационное программное обеспечение
- Фреймворки управления контейнерами
- Решения для резервного копирования
В каждом случае они выполняют одну и ту же функцию: bind mounts монтируют файл или каталог внутри другого каталога. Фактически, результат можно рассматривать как так называемую junction-директорию. Однако монтирования используют другой механизм, который работает с удаленными местоположениями и различными протоколами. Когда речь идет о контейнерах, bind mounts открывают файл или каталог хоста для контейнера.
С другой стороны, том Docker использует специальный каталог хранения внутри дерева каталогов Docker на хосте. Каждый том является подкаталогом, который управляется Docker. По сути, внутренние реализации имеют решающее значение для этой процедуры.
Многие преимущества томов по сравнению с bind mounts вытекают из этого факта:
- Поскольку они являются частью развертывания, тома часто легче мигрировать.
- Существуют команды для управления томами.
- В отличие от mounts, тома доступны как в средах Microsoft Windows, так и в Linux.
- Совместное использование томов между контейнерами безопаснее благодаря контролируемому доступу.
В общем, bind mounts обычно гораздо более производительны и проще в настройке, но зависят от специфических функций ОС.
3. Тома в Docker
В Docker тома — это способы постоянного хранения данных между перезапусками контейнеров. Они могут быть нативными реализациями или простыми привязками.
3.1. Bind mounts
Bind mounts Docker — это высокопроизводительное соединение между контейнером и каталогом на хост-машине. Оно позволяет хосту делиться своей файловой системой с контейнером, который может быть настроен как только для чтения, так и для чтения и записи.
Это позволяет нам использовать контейнер для запуска инструментов, которые мы не хотим устанавливать на хосте, и при этом работать с файлами хоста. Например, если мы хотим использовать пользовательскую версию bash для конкретного скрипта, мы можем выполнить этот скрипт внутри контейнера bash, смонтированного в текущий рабочий каталог:
$ docker run -v $(pwd):/var/opt/project bash:latest \
bash -c "echo Hello > /var/opt/project/file.txt"
Опция –v одинакова для всех форм монтирования. В данном случае привязка указывает источник как рабочий каталог хоста из вывода команды $(pwd), а целевую точку монтирования — как /var/opt/project внутри контейнера.
После выполнения этой команды мы должны найти файл file.txt в рабочем каталоге хост-машины, так как контейнер его создает. Это простой способ обеспечить постоянное хранение файлов между вызовами контейнера Docker. Тем не менее, это часто наиболее полезно, когда контейнер выполняет работу от имени хоста.
Одним из хороших примеров использования этого подхода является выполнение различных версий инструментов сборки языка в Docker, чтобы избежать конфликтующих установок на машине разработчика.
3.2. Тома Docker
Bind mounts использует файловую систему хоста, но тома Docker являются нативными для Docker. Данные хранятся где-то на хранилище, подключенном к хосту, чаще всего на локальной файловой системе. Сам том имеет жизненный цикл, который длиннее, чем у контейнера, что позволяет ему сохраняться до тех пор, пока он не станет ненужным. Тома могут быть общими между контейнерами.
В некоторых случаях том имеет форму, которая не может быть использована хостом напрямую.
4. Управление томами
Docker позволяет нам управлять томами с помощью набора команд docker volume. Например, мы можем дать тому явное имя, что приведет к созданию именованных томов, или сказать Docker сгенерировать случайное имя для анонимных томов.
4.1. Создание томов
Для начала мы создаем том, используя подкоманду create и передавая имя в качестве аргумента:
$ docker volume create data_volume
data_volume
Если имя не указано, Docker генерирует случайное имя в виде хеш-идентификатора:
$ docker volume create
d7fb659f9b2f6c6fd7b2c796a47441fa77c8580a080e50fb0b1582c8f602ae2f
Важно отметить, что это отличается от внутренней идентификации тома.
4.2. Список томов
Подкоманда ls отображает все тома, известные Docker:
$ docker volume ls
DRIVER VOLUME NAME
local data_volume
local d7fb659f9b2f6c6fd7b2c796a47441fa77c8580a080e50fb0b1582c8f602ae2f
Кроме того, мы можем отфильтровать результаты, используя опцию -f или –filter и передавая параметры key=value для большей точности:
$ docker volume ls -f name=data
DRIVER VOLUME NAME
local data_volume
Таким образом, мы получаем только те тома, которые имеют имя data.
4.3. Просмотр томов
Чтобы отобразить подробную информацию о одном или нескольких томов, мы используем подкоманду inspect:
$ docker volume inspect ca808e6fd82590dd0858f8f2486d3fa5bdf7523ac61d525319742e892ef56f59
[
{
"CreatedAt": "2023-11-13T17:04:17Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/ca808e6fd82590dd0858f8f2486d3fa5bdf7523ac61d525319742e892ef56f59/_data",
"Name": "ca808e6fd82590dd0858f8f2486d3fa5bdf7523ac61d525319742e892ef56f59",
"Options": null,
"Scope": "local"
}
]
Важно отметить, что Драйвер тома описывает, как хост Docker находит том. Тома могут находиться на удаленном хранилище, например, через NFS. В данном случае том находится на локальном хранилище.
4.4. Удаление томов
Чтобы удалить один или несколько томов по отдельности, мы можем использовать подкоманду rm:
$ docker volume rm data_volume
data_volume
Это похоже на то, как мы управляем целыми контейнерами.
4.5. Очистка томов
Конечно, мы можем удалить все неиспользуемые тома с помощью подкоманды prune:
$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
data_volume
Как и ожидалось, появляется запрос, подтверждающий действие.
5. Запуск контейнера с томом
Чтобы создать и запустить контейнер с томом, мы используем опцию -v с соответствующими значениями.
5.1. Использование -v
Опция -v состоит из трех компонентов, разделенных двоеточиями:
- исходный каталог или имя тома
- точка монтирования внутри контейнера
- (необязательно) ro, если монтирование должно быть только для чтения
Как мы видели в предыдущем примере, мы можем запустить контейнер с привязанным монтированием через опцию -v:
$ docker run -v $(pwd):/var/opt/project bash:latest \
bash -c "ls /var/opt/project"
Этот синтаксис также поддерживает монтирование нативного тома Docker:
$ docker run -v data-volume:/var/opt/project bash:latest \
bash -c "ls /var/opt/project"
Поскольку этот конкретный том пуст, команда не выводит никаких данных.
Тем не менее, мы можем записать файл в том во время одного из вызовов контейнера:
$ docker run -v data-volume:/var/opt/project bash:latest \
bash -c "echo Rinat > /var/opt/project/Rinat.txt"
Затем последующие команды для доступа к этому же файлу должны завершиться успешно:
$ docker run -v data-volume:/var/opt/project bash -c "ls /var/opt/project"
Rinat.txt
Таким образом, мы получаем доступ к одним и тем же данным между перезапусками контейнера.
5.2. Использование опции –mount
В других случаях мы можем предпочесть использовать более понятную опцию –mount для указания тома, который мы хотим смонтировать:
$ docker run --mount \
'type=volume,src=data-volume,\
dst=/var/opt/project,volume-driver=local,\
readonly' \
bash -c "ls /var/opt/project"
Значения для –mount состоят из строки пар ключ-значение, разделенных запятыми.
Давайте посмотрим, что мы установили:
- type: тип монтирования, в данном случае том
- src: имя тома или исходный каталог, если создается привязка
- dst: точка назначения монтирования в контейнере
- volume-driver: какой драйвер тома использовать, при этом локальный драйвер работает с локальным хранилищем
- readonly: сделать монтирование только для чтения (rw позволяет чтение и запись)
Важно отметить, что вышеуказанная команда также создает соответствующий том, если он еще не существует.
5.3. Использование –volumes-from для совместного использования томов
Критически важно, что присоединение тома к контейнеру создает долгосрочное соединение между контейнером и этим томом. Даже когда контейнер завершил свою работу, это отношение все еще существует.
Из-за этого мы можем использовать завершенный контейнер в качестве шаблона для монтирования того же набора томов в новый контейнер.
Предположим, мы запускаем простой скрипт в контейнере с монтированием data-volume. Позже мы перечисляем все контейнеры, которые мы использовали:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4920602f8048 bash "docker-entrypoint.s…" 7 minutes ago Exited (0) 7 minutes ago exciting_payne
После этого мы можем запустить следующий контейнер, скопировав тома, используемые в уже существующем контейнере:
$ docker run --volumes-from 4920 \
bash:latest \
bash -c "ls /var/opt/project"
Rinat.txt
На практике –volumes-from обычно связывает тома между работающими контейнерами. Например, Jenkins использует это для совместного использования данных между агентами, работающими как контейнеры Docker.
6. Заключение
В этой статье мы рассмотрели, как Docker обычно создает контейнер с новой файловой системой и как bind mounts и тома обеспечивают долгосрочное хранение данных за пределами жизненного цикла контейнера.
Кроме того, мы изучили способы перечисления и управления томами Docker, а также как подключать тома к работающему контейнеру через командную строку.
В общем, знание того, как работать с постоянным хранилищем в Docker, имеет решающее значение для многих приложений, которые требуют согласованности.