Хранилище Docker-контейнеров
Введение
Виртуализация на основе процессов Docker имеет много преимуществ, особенно в сочетании с преимуществами слоев образов. Это позволяет невероятно быстро производить генерацию контейнеров и использовать легкий вес ресурсов. Однако одним из побочных эффектов эфемерной модели процесса Docker является то, что вы должны обязательно планировать, когда и как вы хотите сохранить постоянные данные. В этом руководстве мы познакомим вас с собственным решением Docker для этой проблемы: Docker volumes (тома Docker).
Куда отправляются данные, когда они записываются в контейнер?
Предположим, что мы заходим в оболочку внутри busybox контейнера:
docker run -it --rm busybox
Затем, давайте запишем некоторые данные, скажем, в /tmp:
echo "Data!" > /tmp/data
cat /tmp/data
Data!
Мы видим, что данные определенно записываются. Но куда же на самом деле идут эти данные? Как мы узнали ранее, образы Docker состоят из слоев, уложенных друг на друга, чтобы привести к окончательному образу. Каждый из этих слоев содержит данные, измененные в такой операции, как установка инструмента, добавление исходного кода и т.д. Каждый из этих слоев становится доступным только для чтения после его создания. Когда контейнер создается из образа, тонкий R/W слой добавляется поверх предыдущих слоев образа. Этот слой обрабатывает все вызовы записи из контейнера, которые, в противном случае, были бы направлены на слои ниже, доступные только для чтения. Помните, что контейнеры эфемерны по своей природе. Они предназначены для того, чтобы иметь определенную продолжительность жизни и умереть в какой-то момент, как и любой процесс. Тонкий слой чтения/записи также эфемерен — он исчезает вместе с контейнером. Таким образом, любые записи, которые мы выполняем в контейнере, ограничены временем жизни этого контейнера. Они исчезнут, когда контейнер будет уничтожен. Это очевидное ограничение, которое не способствует хранению статусной информации. Итак, как разработчики и администраторы работают с этим? Они используют Тома Docker.
Тома Docker
Тома Docker - это способ создания постоянного хранилища для контейнеров Docker. Тома Docker не привязаны к времени жизни контейнера, поэтому сделанные в них записи не исчезнут, как это произойдет с контейнером. Они также могут быть повторно подключены к одному или к нескольким контейнерам, чтобы можно было обмениваться данными и подключать новые контейнеры к существующему хранилищу. Тома Docker работают путем создания каталога на главной машине и последующего монтирования этого каталога в контейнер (или в несколько контейнеров). Этот каталог существует вне многослойного образа, который обычно содержит контейнер Docker, поэтому он не подчиняется тем же правилам (только для чтения и т. д.).
Давайте создадим том Docker и посмотрим его в действии:
docker volume create
1d358c3fc3750f98345713eee5c294dee52...
Простой вызов docker volume create позволит создать новый том. Если мы проверим этот том, мы можем увидеть, где он живет на хост-файловой системе:
docker volume inspect 1d358c3fc3750f98345713eee5c294dee526a3f5d0bd41a0ff4d117218c4af73
[
{
"CreatedAt": "2020-05-17T16:25:30Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/1d358c3fc3750f98345713eee5c294dee526a3f5d0bd41a0ff4d117218c4af73/_data",
"Name": "1d358c3fc3750f98345713eee5c294dee526a3f5d0bd41a0ff4d117218c4af73",
"Options": {},
"Scope": "local"
}
]
С вызовом inspect приходит много информации ,но все, что нас действительно беспокоит прямо сейчас, это Mountpoint. Обратите внимание, что здесь указан путь, начинающийся с /var/lib/docker.... Если вы откроете этот путь на своем компьютере, на котором работает Docker, вы можете просмотреть данные, хранящиеся внутри этого тома. Метод, который мы только что использовали для создания тома, не является единственным способом. При запуске контейнера вы можете указать -v, чтобы создать новый том на лету:
docker run -it --rm -v testdata:/data busybox
Как вы можете видеть, мы добавили новый аргумент к нашей docker run команде: -v. Существует специальный синтаксис для этого аргумента, с полями, разделенными двоеточиями. Первое поле - это название тома, в данном случае, testdata. Второе поле - это путь в контейнере, куда том будет примонтирован, в нашем случае, /data. Давайте запишем данные на том из контейнера:
echo "Hello!" > /data/hello
Эти данные видны снаружи контейнера, в пути монтирования тома на хосте:
pwd
cat hello
/var/lib/docker/volumes/testdata/_data
Hello!
Вы должны были заметить, что в пути название тома больше не является случайной строкой - это теперь имя тома, которое мы указали при использовании -v аргумента. Тома Docker могут иметь либо рандомизированные имена, инициализированные Docker Engine, или имена, назначенные пользователем самостоятельно. Имена должны быть уникальными для каждого хоста. Созданный во время выполнения том Docker теперь становится доступным в команде docker volume ls :
docker volume ls
DRIVER VOLUME NAME
local 1d358c3fc3750...
local c63a0af5c8282...
local testdata
Это означает, что мы можем использовать этот том снова с другим контейнером или даже несколькими контейнерами. Давайте проверим это прямо сейчас. Во-первых, подключите том к busybox контейнеру:
docker run -it --rm -v testdata:/data busybox
Внутри контейнера давайте напечатаем информацию о системе, а затем запишем на том:
uname -a
echo "Hello 2" > /data/hello
Linux 7e299450b997 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux
Теперь запустите второй busybox контейнер, работающий одновременно с первым:
docker run -it --rm -v testdata:/data busybox
Нам доступны данные, которые были записаны в первом контейнере:
uname -a
cat /data/hello
Linux c5bf9ca04d3a 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux
Hello 2
Это подчеркивает еще одно из преимуществ томов Docker: совместное использование данных между контейнерами.
Варианты Использования Тома Docker
Есть много допустимых вариантов использования для контейнеров Docker, но здесь мы рассмотрим два наиболее распространенных. Для каждого из этих примеров давайте представим, что у нас есть простое приложение, которое работает и собирает данные с некоторых датчиков погоды. Мы хотим собрать кучу метеорологических показателей, сохранить их, а затем использовать их снова в будущем. Давайте назовем наше тестовое приложение WeatherMon.
Сохранение данных за пределами срока службы контейнера
Если мы выполняем WeatherMon без использования томов Docker, все данные, которые мы собираем, будут уничтожены при исчезновении контейнера. Нам это не сильно поможет, если наша цель - собрать данные и сделать их доступными для дальнейшего использования. Тома Docker удобны тем, что мы можем сохранить наши данные в томе и оставить их за пределами срока службы контейнера. Допустим, мы создаем наш контейнер, вызывая docker run с аргументом -v weathermon:/opt/weathermon. Тогда наше приложение может хранить свои метрики погоды в каталоге /opt/weathermon непосредственно на эфемерном слое чтения/записи, предоставленном контейнером. Мы также могли бы настроить удаленную базу данных для хранения этой информации, но тома Docker предоставляют альтернативу для хранения локально используемых данных.
Совместное Использование Данных Между Контейнерами
Предположим, что мы уже используем контейнер WeatherMon некоторое время и собрали довольно много данных. Мы хотим проанализировать эти данные, чтобы определить информацию, такую как средняя температура за день, или какая неделя в месяце имела самую высокую среднюю влажность. Используя тома Docker, мы можем смонтировать существующий том в новый контейнер, WeatherMon-Analytics. Этот новый контейнер может считывать данные, не прерывая их сбор контейнером WeatherMon. Затем он может выполнять аналитику, которую мы хотим, и хранить эту информацию в том же томе или в другом томе, если это необходимо.
Другие Типы Монтирования
Есть два других типа томов Docker, которые мы еще не обсуждали: bind mount и tempfs mount.
Bind Mount
Bind mount используются для монтирования существующего пути на хосте в контейнер. Используя --mount совместно с <host path>:<container path>, вы можете указать существующие каталоги, которые будут монтироваться в контейнер. Это очень удобно при использовании информации о конфигурации, такой как каталоги внутри /etc. Это также полезно, когда у вас есть информация, которую вы хотите использовать в контейнере, например, существующие наборы данных или статические файлы веб-сайта.
Tempfs Mount
Задача tempfs монтирования состоит в том, чтобы обеспечить доступное для записи расположение, которое специально не сохраняет информацию после окончания срока службы контейнера. Возможно, вы думаете: "зачем это нужно?” В контейнере, который не имеет подключенного тома, все записи идут в тонкий слой R/W, вставленный во время выполнения. Любая запись, направленная на этот слой, влияет на файловую систему, поскольку эти записи выполняются на базовом хосте. Обычно это не является проблемой, если вы не пишете значительные объемы одноразовых данных (таких как журналы). В этом случае вы можете наблюдать снижение производительности, так как файловая система должна обрабатывать все эти вызовы write. tempfs монтирование было создано для предоставления контейнерам временного пути записи, который не влияет на операции файловой системы. В частности, tempfs это эфемерное монтирование, которое записывается непосредственно в память. Этот том можно создать с помощью --tempfs аргумента.
Драйвера Томов
По умолчанию тома хранят информацию о базовой хост-системе. Docker также имеет концепцию, называемую драйверами томов, которая позволяет указать, как и где хранить тома. Например, вы можете хранить том Docker внутри корзины Amazon S3. Это может быть удобно, если вы хотите, чтобы информация сохранялась не только за пределами срока службы контейнера, но и за пределами срока службы хоста.
Вывод
Все обсуждаемые здесь понятия разбиты на гораздо более подробные части в документации Docker о хранилище. Примеры использования каждого типа монтирования и более подробное введение в концепцию драйверов томов доступны по этой ссылке.