Data Volume container.
В docker есть любопытный инструмент, который называется Data Volume сontainer. У меня он ассоциациируется с паттерном data transfer object.
Data Volume
Том (volume) - это внешний для контейнера каталог или файл, монтированный в контейнер. Есть три типа data volume:
- Пользовательский каталог или файл, который монтируется с указанием полного пути в host-системе, пример:
docker run -v "$(pwd)/log/:/log/" nginx
; - Том под управлением docker с заданным именем;
- Автоматически создаваемый том со служебным именем.
Второй тип получается, если имя каталога указать без пути. Пример: docker run -v test:/test/ nginx
. В этом случае в /var/lib/docker/volumes/ будет создан каталог с указанным названием, или будет подключен существующий каталог.
В Dockerfile директиве VOLUME можно указать имя каталога, который будет создан в /var/lib/docker/volumes/. Пример: VOLUME mysql_dump_v1.1:/docker-entrypoint-initdb.d/
. При создании первого контейнеров из такого образа будет создан каталог /var/lib/docker/volumes/test, а при создании второго и следующих контейнеров, этот каталог будет для них общим. Такая вот любопытная недокументированная возможность.
Если не указывать имя каталога, который вы монтируете - будет создан новый каталог в /var/lib/docker/volumes/ с названием из 64 цифр в 16-ричной нотации, это третий тип. Пример команды: docker create -v /shared_folder nginx
.
Относительно удаления монтируемых томов действуют следующие правила:
- При удалении контейнера командой rm, как в
docker rm drunk_ritchie
, volume-каталоги не удаляются; - Если контейнер создан с параметром --rm, например,
docker run --rm -v /var/lib/mysql mysql
, созданные volume-каталоги будут удалены при завершении работы контейнера; - Если команде удаления указать параметр -v, например,
docker rm -v drunk_ritchie
, будут удалены автоматически созданные контейнеры, в том числе с заданным именем; - Если подключить том в несколько контейнеров, а затем удалять их с параметром -v, каталог будет удален из /var/lib/docker/volumes/ при удалении последнего контейнера, к которому подключен этот том. Если один из контейнеров, в которые был монтирован том, удален без -v, каталог тома останется, даже если следующий контейнер будет удален с параметром -v;
- Пользовательские каталоги и файлы docker не удаляет.
Разобраться к чему относятся тома с автоматическими названиями в /var/lib/docker/volumes/ сложно, и при удалении контейнеров они остаются как мусор. В документации пишут, что разработчики работают над проблемой. В текущей реализации (1.8.2) я не вижу смысла использовать тома с автоматически генерируемыми именами. Самый удобный вариант - монтировать в контейнеры собственные, пользовательские каталоги. С файлами в них удобно работать, и удалить эти каталоги легко, когда станут не нужны.
Вот что еще нужно знать про тома в docker:
- В каталог /var/lib/docker/volumes/ доступ есть только для рута, так что просмотреть содержимое автоматически созданных томов можно только через
sudo
; - Данные из томов не экспортируются при выполнении
docker export
,docker save
и не сохранятся в образ командойdocker commit
; - Если при монтировании указать несуществующий каталог или если к нему нет доступа - docker не выдаст никакой ошибки;
- При подключении в контейнер внешнего каталога у него остаются uid/gid host-системы;
- В Dockerfile можно указать тома, которые будут созданы для контейнера, но только каталоги, файл можно подмонтировать только параметром команды run или create.
Перефразируя фильм "Адвокат дьявола", авторы docker дают нам тома в контейнерах, дарят этот экстраординарный подарок, а потом для своего ролика космических трюков устанавливает противоположные правила игры. Расшаривай каталог между контейнерами - но не удаляй. Монтируй свою папку - но не распространяй в образе. Записывай папки в образ и распространяй - но не расшаривай между контейнерами!
Несмотря на проблемы, использовать в контейнерах общие каталоги для хранения файлов приложения и базы данных очень удобно, а проблему можно подпереть костылем в виде пары shell-команд в Dockerfile.
UID/GID и Data Volume
В контейнерах обычно есть свои пользователи и группы со своими uid/gid, а значения uid/gid у монтируемых каталогов присвоены в host-системе с ее пользователями. У группы docker, которая создатся в host-системе, gid равен 999. Однако, в официальных образах php и python пользователя с uid 999 нет вообще, а рабочие процессы исполняются под www-data 33:33. Кроме того, процессы в контейнерах с uid 0 могут спокойно поменять владельца подключаемого каталога, выставив выставив значение, отсутствующее в host-сиситеме. С большой вероятностью возникнут проблемы с правами доступа к подключаемым каталогам.
Надеюсь, когда-нибудь для образов docker примут соглашение, по которому во всех образах будет стандартная группа docker с одним gid. Пока что придется создать специальную группу для томов и добавить в нее своего пользователя.
$ sudo groupadd -g 56789 docker_volumes
$ sudo usermod -a -G docker_volumes gri
Подготовка образа с приложением
В документации предлагают создавать volume container из образа операционной системы или базы данных. Если такой контейнер экспортировать, в файл пойдет вся операционная система. Наследование от образа mysql добавит к размеру архива порядка 66 мегабайт при сжатии xz. Чтобы этого избежать, я создаю контейнер от Busybox - он позволяет выполнять простые команды и открывать терминал в контейнере, при этом добавляет в архив менее мегабайта.
Я создам образ с пустым volume и файлами приложения. При создании контейнера будет проверяться пустой ли каталог, подключенный как volume. Если пустой - в него копируются файлы приложения. Если каталог не пустой - не копируются. При создании контейнера из образа я монтирую в него в качестве тома свой каталог, и в него копируются файлы приложения. Я могу редактировать файлы приложения и пересоздавать контейнер, подключая к нему свои файлы. При желании я могу вернуться к начальному состоянию из образа, создав новый контейнер и подключив пустой каталог.
Копирую скрипты приложения в каталоге source
и готовлю образ со скриптами:
$ mkdir data_volume
$ chgrp docker data_volume
$ cd data_volume/
$ cp ~/app source
$ vi Dockerfile
Вот содержимое Dockerfile, из которого я создам образ со своим приложением:
FROM busybox
VOLUME /scripts
RUN adduser -D -u 56789 docker_volumes
COPY source /source/
USER docker_volumes
CMD test "$(ls -A "/scripts/" 2>/dev/null)" || cp /source/* /scripts/
При запуске контейнера будет выполнятся команда из директивы CMD. Если каталог application/
пустой, в него будут скопированы скрипты приложения.
Если при запуске контейнера указать команду, директива CMD игнорируется, поэтому я не использую директиву ENTRYPOINT - так удобней дебажить, открыв консоль в контейнер.
Создаю образ и контейнер:
$ docker build -t grikdotnet/application .
$ cd ..
$ mkdir application
$ sudo chgrp docker_volumes application
$ docker run --name application -v "$(pwd)/application:/scripts" grikdotnet/application
Теперь у меня есть контейнер с data volume /scripts/
, в который монтирован мой каталог ./application/
, и в нем появилось мое приложение.
Этот data volume я могу подмонтировать в контейнер php.
Этот образ удобно экспортировать
~$ docker save grikdotnet/application | xz > application.image.tar.xz
$ ls -lh application.dc.tar.xz
-rw-rw-r-- 1 gri gri 858K Sep 8 14:53 application.dc.tar.xz
и импортировать
$ docker rm application
$ docker rmi grikdotnet/application
$ rm -rf application/*
$ docker load --input application.image.tar.xz
$ docker run --name application -v "$(pwd)/application:/scripts" grikdotnet/application
$ ls -Al application
total 4
-rw-r--r-- 1 56789 docker_volumes 28 Sep 9 09:22 test.php
Да, пользователя с gid 56789 в host-системе нет.
UID/GID процессов среды исполнения
Конечно, PHP/Python/etc тоже может исполняться под пользователем docker_volumes.
В случае PHP для этого надо расширить официальный образ, используя Dockerfile, подобный такому:
FROM php:5.6-fpm
RUN adduser --group --system --uid 56789 --disabled-password docker_volumes
RUN apt-get update && apt-get install -y \
libmcrypt-dev \
&& docker-php-ext-install mcrypt pdo_mysql
CMD ["php-fpm"]
и в подключаемом php-fpm.conf указать
user = docker_volumes
group = docker_volumes
Подключение приложения к контейнеру с PHP выполняется параметром --volumes-from
.
$ docker run -d --name=php56 \
--volumes-from application \
my_php_image >>log/docker.php.log 2>&1
Пример пошаговых инструкций по запуску php можно найти в третьей части.