Docker: управление вашими данными
В этой статье будет рассмотрен вопрос управления данными внутри контейнеров Docker, совместное использование данных разными контейнерами, а также резервное копирование этих данных.
- Docker тома (Docker volumes)
- Контейнеры Docker томов (Docker volume containers)
Но прежде чем рассказать о них чуть подробнее, необходимо еще раз обратить ваше внимание на то, как работает файловая система в контейнерах Docker. Образы Docker, из которых запускаются контейнеры, представляют собой последовательность неизменяемых (только чтение) слоев. Набор этих слоев, можно посмотреть при помощи команды docker history, например, для контейнера nginx версии latest это делается так:
$ docker pull nginx
$ docker history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
a39777a1a4a6 20 hours ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon 0 B
20 hours ago /bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp 0 B
20 hours ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx/ 22 B
20 hours ago /bin/sh -c apt-key adv --keyserver hkp://pgp. 58.58 MB
20 hours ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.11.8-1 0 B
20 hours ago /bin/sh -c #(nop) MAINTAINER NGINX Docker Ma 0 B
42 hours ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
42 hours ago /bin/sh -c #(nop) ADD file:89ecb642d662ee7edb 123 MB
Полный вывод истории образа можно получить, добавив к последней команде ключ —no-trunc. А еще детализацию любого образа можно посмотреть воспользовавшись проектом https://imagelayers.io.
При запуске из образа нового контейнера к этому набору неизменяемых слоев присоединяется слой, который можно изменять. С этого момента, если в контейнере попытаться изменить какой-нибудь существующий файл, то этот файл будет скопирован из одного из нижележащих неизменяемых слоев, в котором он был создан, в изменяемый слой, куда и запишутся все его изменения. Версия изменяемого слоя скрывает нижележащий файл, но не удаляет его. Когда вы удаляете запущенный контейнер и перезапускаете его из того же образа, все ваши изменения будут потеряны. Так работает UnionFS, которую использует Docker, для организации своей работы с данными.
Для того, чтобы иметь возможность сохранять ваши данные, а также использовать эти данные несколькими контейнерами, Docker предоставляем вам возможность использовать так называемые тома (Docker volumes). Если совсем просто, то тома — это обычные директории (или файлы) хостовой файловой системы сервера, на котором работает Docker, монтируемые в любую директорию контейнера.
Существует несколько способов инициализировать тома, у которых есть несколько важных отличий, которые необходимо понимать. Самый прямой способ — это определить использование тома контейнером при его запуске при помощи флага -v:
$ docker run -it --name nginx_example -h nginx-container -v /opt/static nginx /bin/bash
root@nginx-container:/# ls /opt/static/
Данная команда запустит контейнер с именем nginx_example именем хоста контейнера (-h) nginx-container из образа nginx в интерактивном режиме работы (-it), передаст управление интерпретатору /bin/bash, предварительно создав безымянный том (-v) и смонтировав его в /opt/static. При этом в директория /opt/static будет создана автоматически, она будет жить вне UnionFS и будет доступна для записи внутри контейнера, т.е. все файлы, которые вы сохраните в этой директории будут помещены внутрь тома. Вы можете определить расположение тома на файловой системе хостового сервера, на котором работает Docker, при помощи команды docker inspect:
$ docker inspect -f "" nginx_example | jq .
[
{
"Name": "e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d",
"Source": "/var/lib/docker/volumes/e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d/_data",
"Destination": "/opt/static",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
В этом примере используется фильтр (-f) всего вывода docker inspect nginx_example, показывающий только часть содержимого, которое относится к блоку Mounts. При этом вывод форматируется утилитой jq.
После того, как мы знаем имя тома (ключ Name), мы можем получить ту же информацию при помощи команды
docker volume inspect
$ docker volume inspect e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d
[
{
"Name": "e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d/_data",
"Labels": null,
"Scope": "local"
}
]
В обоих случаях вывод показывает вам, что Docker смонтировал /opt/static внутрь контейнера как директорию /var/lib/docker/volumes/e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d/_data. Чтобы убедиться в этом, создайте, например, файл test на хостовом сервере, на котором запущен Docker, внутри этой директории.
$ sudo touch /var/lib/docker/volumes/e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d/_data/test
А затем посмотрите на содержимое /opt/static внутри контейнера.
root@nginx-container:/# ls /opt/static
test
Если вы отключились от интерактивного режима работы с контейнером, выполните команду docker start для запуска контейнера с интерактивным (-ai) к нему подключением:
$ docker start -ai nginx_example
Вы увидели этот результат только потому что /opt/static — это всего лишь директория на хостовом сервере, которая была смонтирована внутрь контейнера.
Тот же самый эффект будет достигнут при использовании инструкции VOLUME в Dockerfile:
FROM nginx
VOLUME /opt/static
Еще один способ создавать тома — это использовать команду docker volume create:
$ docker volume create --name nginx_static_data
nginx_static_data
Который в последствие может быть подключен к вашему контейнеру. Обратите внимание, том может быть подключен только к новому контейнеру, поэтому старый контейнер предварительно придется удалить:
$ docker rm nginx_example
nginx_example
$ docker run -it --name nginx_example -h nginx-container -v nginx_static_data:/opt/static nginx /bin/bash
root@nginx-container:/# ls /opt/static
Есть еще один часто используемый способ создания тома, реализуемый только при помощи параметра -v, монтирующий указанную директорию хостового сервера внутрь контейнера. Для этого создайте на хостовом сервере директорию folder_test, внутри этой директории файл test_file, а затем пересоздайте контейнер, указав полный путь до директории folder_test:
$ mkdir folder_test
$ touch folder_test/test_file
$ docker rm nginx_example
nginx_example
$ docker run -it --name nginx_example -h nginx-container -v /home/docker/folder_test:/opt/static nginx /bin/bash
root@nginx-container:/# ls /opt/static/
test_file
Как видите, содержимое директории folder_test стало доступно внутри запущенного контейнера. Этот способ работы с томами очень удобно использовать, например, в процессе разработке приложений. В целях сохранения свойства переносимости контейнеров, директория, использующаяся для тома на хостовом сервере, не может быть указана в Dockerfile (ведь эта директория совершенно не обязательно должна быть доступна во всех контейнерах). В случае использования этой формы работы с томами (монтирование тома при помощи флага -v в процессе создания контейнера) файлы, возможно содержащиеся внутри директории образа, не копируются внутрь тома (в данном случае в директорию на хостовом сервере). Docker не управляет такого рода томами как показано в прошлых примерах, и поэтому такие тома не появятся в выводе команды docker volume ls и никогда не будут удалены демоном Docker.
Совместное использование данных контейнерами
Для того чтобы дать доступ одному контейнеру к томам другого, вы должны указать аргумент —volumes-from для команды docker run. Например:
$ docker run -it --name nginx_example1 -h nginx-container1 --volumes-from=nginx_example nginx /bin/bash
root@nginx-container1:/# ls /opt/static/
test_file
Это сработает вне зависимости от того, запущен контейнер nginx_example или нет. Причем, том не будет удален до тех пор, пока хотя бы один контейнер к нему подключен. Вы можете также монтировать том указывая в качестве значения аргумента -v имя тома, например:
$ docker rm nginx_example1
nginx_example1
$ docker run -it --name nginx_example1 -h nginx-container1 -v e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d:/opt/static nginx /bin/bash
root@nginx-container1:/# ls /opt/static/
test_file
Контейнеры Docker томов (docker volume containers)
До момента появления в Docker команд docker volume широко использовались «контейнеры томов» для хранения и организации совместной работы с данными. Этот подход означает, что контейнер по своей сути становился «пространством имен» (namespace) для данных. Тем не менее в современных версиях Docker этот подход никогда не нужно использовать. Вместо этого просто создайте том с нужным вам именем при помощи команды docker volume create —name.
Права доступа
Часто вам может требоваться выставить привилегии и владельца содержимого Docker тома или поместить внутрь тома данные по-умолчанию или конфигурационные файлы. Ключевой важный момент в процессе создания образа вашего контейнера это то, что после инструкции VOLUME в Dockerfile Docker не сможет выполнить никаких изменений внутри тома, например:
FROM nginx:latest
RUN useradd foo
VOLUME /opt/static
RUN touch /opt/static/some_file
RUN chown -R foo:foo /opt/static
Не будет работать, как ожидается. Нам необходимо выполнить команду touch на файловой системе будущего образа, но она будет исполнена в томе временного слоя, использующегося при сборке. Работать будет немного по-другому:
FROM nginx:latest
RUN useradd foo
RUN mkdir /opt/static && touch /opt/static/static_file
RUN chown -R foo:foo /opt/static
VOLUME /opt/static
Docker достаточно интеллектуален, чтобы скопировать любые файлы, существующие в образе в нижележащих слоях в точку монтирования тома и выставить правильные привилегии. Этого не произойдет, если вы смонтируете директорию на хостовом сервере в качестве тома.
Удаление тома
Если вы всегда использовали команду docker rm для удаления ваших контейнеров, велики шансы, что у вас есть большое количество не удаленных томов, занимающих место. Тома автоматически удаляются только в том случае, если родительский контейнер был удален при помощи команды docker rm -v (использование флага -v очень важно) или же если флаг -rm был использован во время выполнения команды docker run. И даже после этого том будет удален только тогда, когда ни один другой контейнер не ссылается на него. Тома в виде директорий, смонтированные внутрь контейнера из хостового сервера, никогда не удаляются.
Чтобы посмотреть на все тома, существующие на хостовом сервере, выполните команду docker volume ls:
$ docker volume ls
DRIVER VOLUME NAME
local e01cb937734c02790c94c14339bc563159a88c025e266d298d48398352ea477d
local nginx_static_data
Для удаления всех не использующихся томов, выполните команду:
$ docker volume rm $(docker volume ls -q)
Резервное копирование, восстановление и миграция томов
Еще одна функция, которую можно выполнять с томами — это их резервное копирование, восстановление и использование этих операций для осуществления миграции данных. Резервное копирование выполняется при помощи использования —volumes-from аргумента в процессе создания нового контейнера, который будет архивировать данные уже существующего:
$ docker run --rm --volumes-from nginx_example -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /opt/static
Эта команда запустит новый контейнер из образа ubuntu и запустит внутри него команду архивирования директории /opt/static (команда для исполнения внутри контейнера tar cvf /backup/backup.tar /opt/static), предварительно смонтировав текущую директорию $(pwd) в директорию /backup контейнера. Восстановить данные тома в тот же самый контейнер или в другой контейнер в любом другом месте (backup.tar легко передается при помощи scp) можно, например, командой:
$ docker volume create --name new_volume_for_static_data
$ docker run -v new_volume_for_static_data:/opt/static --name nginx_restored nginx /bin/bash
$ docker run --rm --volumes-from nginx_restored -v $(pwd):/backup ubuntu bash -c "cd /opt/static && tar xvf /backup/backup.tar --strip 1"
Эти команды могут быть использованы вами для автоматизации резервного копирования, восстановления или миграции данных томов ваших контейнеров.
Резервное копирование, восстановление и миграция контейнеров
Если же в процессе работы контейнера изменялись данные не тома, а непосредственно контейнера, то эти изменения тоже можно сохранить, например в образ при помощи команды docker commit:
$ docker commit nginx_example nginx_example_backup:19.01.2017
sha256:0dcad379614806b0485f2cad48fcd5208e413363e7a04819ca7edf81b92cf280
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx_example_backup 19.01.2017 0dcad3796148 4 seconds ago 181.5 MB
Далее вы можете сохранить этот образ в публичный или частный Docker реестр при помощи команды docker push, или же вы можете сохранить образ в tar-файл для передачи на другой удаленный сервер:
$ docker save -o nginx_example_backup_19.01.2017.tar nginx_example_backup:19.01.2017
Чтобы впоследствие создать из него образ, но уже на другом удаленном сервере, где установлен Docker:
$ docker load -i nginx_example_backup_19.01.2017.tar
По материалам: