Если полистать какой-нибудь Докер гайдлайн, то скорее всего в нём скажут, что контейнеры должны быть маленькими, с одним процессом, легко удаляемыми и так же легко заменяемыми на более новый. Прекрасная концепция. О чём в гайдлайнах пишут немного реже, так это что же делать с данными внутри таких контейнеров. Я же не могу легко удалить тот же mysql контейнер и заменить его на новый, но пустой внутри. Он ведь мне только ради данных и нужен был.
Но, оказывается, проблема вполне решаема. Docker volumes, которые существовали в Докере с момента сотворения мира, начиная с версии 1.8 получили обновлённый API, и теперь справляются с хранением данных не только технически, но и эстетически прекрасно.
Что такое Docker volumes
Если немного упростить, то Docker volume — это просто папка хоста, примонтированная к файловой системе контейнера. Так как технически она больше не принадлежит контейнеру, то последний можно смело удалять, пересоздавать заново, снова прикручивать к нему хостовые папки, и ничего с данными внутри не случится. Если несколько способов, как этой функциональностью воспользоваться, и сегодня мы рассмотрим целых три из них.
1. Простое монтирование папки
Это вообще легко. Мы берём любую подходящую хостовую папку и подключаем её в нужное место файловой системы контейнера. Например, если мне нужно иногда обновлять mysql контейнер, или делать бэкап его данных, то просто монтируем папку к /var/lib/mysql
, и теперь mysql данные находятся в относительной безопасности хостовой файловой системы:
1 2 3 4 5 |
docker run -d \ -e MYSQL_ROOT_PASSWORD=my-secret-pw \ -v /home/docker/mysql-data:/var/lib/mysql \ --name mysqlserver \ mysql |
Если такой контейнер удалить, то его данными вообще ничего не случится.
1 2 3 4 5 6 |
docker stop mysqlserver docker rm mysqlserver ls /home/docker/mysql-data #auto.cnf client-cert.pem ib_logfile0 mysql/ public_key.pem sys/ #ca-key.pem client-key.pem ib_logfile1 performance_schema/ server-cert.pem #ca.pem ib_buffer_pool ibdata1 private_key.pem server-key.pem |
Теперь мы можем запустить новый mysql, подключить папку к тому же пути, и новый контейнер продолжит с того же места, где остановился старый.
Монтирование только для чтения
Иногда такие папки имеет смысл давать только на чтение. Например, для веб-сервера. Это очень легко сделать, добавив :ro
в конце пути:
1 2 3 |
docker run -v /home/docker/www:/usr/share/nginx/html:ro ... |
Просмотр уже подключённых папок
Запомнить, что же именно сейчас подключено к тому или иному контейнеру — нереально. Но это и не нужно. Есть команда docker inspect %container%
, которая кроме настроек сети, хоста и, собственно, контейнера, выведет и все volumes, которые к нему подключены:
1 2 3 4 5 6 7 8 9 10 11 |
docker inspect mysqlserver # ... # "Mounts": [ # { # "Source": "/home/docker/mysql-data", # "Destination": "/var/lib/mysql", # "RW": true, # ... # } # ], # ... |
2. Тома с данными (data volumes)
Давайте попробуем ещё одну штуку. Запустим первый пример ещё раз, но в этот раз без -v
параметра. Теперь, если мы проверим, подключены ли какие-нибудь volumes к контейнеру, то.. да, там кто-то снова есть:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
docker run -d \ -e MYSQL_ROOT_PASSWORD=my-secret-pw \ --name mysqlserver \ mysql docker inspect mysqlserver #... # "Mounts": [ # { # "Name": "aac828f46e6ff00fedaf45bc21e89523bd57663d3d58bf0e3c73e8cb4092d768", # "Source": "/mnt/sda1/var/lib/docker/volumes/aac828f46e6ff00fedaf45bc21e89523bd57663d3d58bf0e3c73e8cb4092d768/_data", # "Destination": "/var/lib/mysql", # ... # } # ], #... |
В этот раз со странным именем, и ещё более странным путём к хостовой папке, но он всё ещё указывает на /var/lib/mysql
. Как же так?
Намёк на причину можно найти в Dockerfile для mysql образа:
1 2 3 |
... VOLUME /var/lib/mysql ... |
VOLUME /var/lib/mysql
, оказывается, создаёт новый том, который привязывается к /var/lib/mysql
. Тома выглядят и работают практически так же, как и простые примонтированные папки, но есть и несколько отличий.
Во-первых, у томов есть собственное имя. По-умолчанию, Докер генерирует 64-символьное имя, но можно задать и своё.
Во-вторых, для каждого тома Docker создаёт папку в хостовой файловой системе вида /var/lib/docker/volumes/%имя%
.
В-третьих, когда контейнер запускается в первый раз, Докер копирует его содержимое из примонтированного пути (например, из /var/lib/mysql
) в только что созданный том, и потом уже берёт данные исключительно из последнего. Это может привести к несколько неожиданным эффектам. Например, если я заменю mysql контейнер на новый, созданный из более свежего образа, и в более свежем образе в папке /var/lib/mysql
будут обновлённые файлы, то они будут проигнорированы, если том уже был проинициализирован.
Создаём тома из командной строки
Создавать тома можно не только из Dockerfile. Из командной строки это тоже хорошо получается:
1 2 3 4 |
docker run \ -v /data \ ubuntu \ touch /data/README.md |
Эта команда создаст новый том, подключённый к /data
, затем выполнит touch /data/README.md
, которая создаст пустой файл внутри неё, и, наконец, контейнер завершится.
В отличие от обычных подключаемых папок, Докер знает, какие тома он когда либо создавал, и через команды docker volume ...
может этим знанием поделиться. Например, ls
выведет список всех известных томов. В том числе и тот, который мы только что создали:
1 2 3 |
docker volume ls #DRIVER VOLUME NAME #local b45436c7f8bab37c0bfe998f962001226470cbc1dfe4ac59cc0287276e3d7a64 |
Кстати, том можно подключить и к «чужим» контейнерам. Зная имя, его можно подключить, например, к случайно гулявшей мимо Убунте:
1 2 3 4 5 6 7 |
docker run -ti \ -v b45436c7f8bab37c0bfe998f962001226470cbc1dfe4ac59cc0287276e3d7a64:/data \ ubuntu bash root@21bd05dfa2dd:/# cd /data root@21bd05dfa2dd:/data# ls README.md |
Классический пример использования такой фичи — создание резервных копий тома. Мы просто запускаем контейнер, который умеет делать бэкапы, подключаем к нему том с исходными данными, затем ещё один, чтобы было куда складывать результат, и всё:
1 2 3 4 |
docker run --rm \ -v b45436c7f8bab37c0bfe998f962001226470cbc1dfe4ac59cc0287276e3d7a64:/data -v $(pwd):/backup \ ubuntu tar cvf /backup/backup.tar /data |
Создаём тома без контейнеров
Чтобы создать том, не обязательно даже создавать контейнер. Можно воспользоваться командой docker volume create
, чтобы подготовить том заранее, и потом уже подключать его по мере надобности. Прелесть этого подхода в том, что вместо нечитабельных 64 символов сгенерированного имени мы можем задать что-то более произносимое:
1 |
docker volume create mysql-data |
Создаём тома на внешнем хранилище
volume create
может нечто большее, чем просто задавать красивые имена. До сих пор мы создавали тома на локальной машине, что в мире облаков и распределённых приложение — не самый масштабируемый подход. Docker поддерживает целый зоопарк плагинов-драйверов, которые могут отделить том от конкретного хоста, и хранить его в каком-нибудь Azure, DigitalOcean, или просто размазать по облаку.
Установка и конфигурация такого плагина может быть сложноватой, но если уже получилось, то пользоваться им — одно удовольствие. Например, чтобы хранить том в Digital Ocean, нужна всего-лишь такая команда:
1 |
docker volume create --driver dostorage --name my-volume |
3. Тома-контейнеры
Был ещё такой старый паттерн — «контейнеры только с данными». И он делал именно то, как назывался: просто существовал, как правило, выключенный, и единственной целью его жизни было делиться своими томами с другими контейнерами.
Чтобы получить к ним доступ, нужно было указать параметр --volumes-from
, и магия получалась сама собой:
1 |
docker run -d --volumes-from mydatacontainer mysql |
Если честно, то я не вижу никакой выгоды от использования этого подхода, по сравнению с обычными томами данных. Может, до версии 1.8, когда появился нормальный АПИ для томов, том-контейнер был единственным вменяемым решением, то сейчас даже Гугл не уверен, почему они всё ещё существуют.
Итого
Сегодня мы посмотрели на несколько способов, как хранить данные в Docker контейнерах: примонтированные папки, тома с данными и тома-контейнеры. Все три официально поддерживаются, и даже встречаются примеры. Но обычные монтированные папки работают только на локальных хостах и не контролируются докером, тома-контейнеры просто похожи на коллекции именованных томов, и только тома с данными выглядят как «трушный» способ управлять данными. Они идут с понятным API, управляются Докером, могут работать как локально, так и удалённо. В общем, умеют всё, что нужно.
Спасибо, очень помогла статья.
Всегда пожалуйста
А тома с данными (data volumes) так-же доступны со всех узлов докера в swarm mode?
Насколько я помню — нет. Они же привязаны к локальной файловой системе, а swarm даже загруженными образами между хостами делиться не может. Чтобы тома были видны отовсюду, их надо хранить где-нибудь на стороне через volume plugin
Спасибо за статью.
К слову сказать из России ваш сайт не доступен.
Возможно, зависит от провайдера. Я только что посмотрел статистику, и большинство посещений как раз таки из России.
Статья топ, спасибо!
Все понятно стало
Спасибо за разъяснение нюансов, о которых почему-то мало информации даже на очень уважаемых сайтах.
Спасибо большое. Пересмотрел десятку видео но так и не мог понять и тут — 10 минут и все встало на свои места.