Понимание инструкции "VOLUME" в DockerFile

Ниже приведено содержание моего "Dockerfile"

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]

В этом файле я ожидаю, что инструкция "VOLUME./Usr/src/app" смонтирует содержимое текущего рабочего каталога на хосте, который будет монтироваться в папке /usr/src/app контейнера.

Пожалуйста, дайте мне знать, правильно ли это?

Ответ 1

Официальное руководство по докеру гласит:

Том данных - это специально назначенный каталог в одном или нескольких контейнерах, который обходит файловую систему Union. Тома данных предоставляют несколько полезных функций для постоянных или общих данных:

  • Тома инициализируются при создании контейнера. Если базовый образ контейнера содержит данные в указанной точке монтирования,
    что существующие данные копируются в новый том на том
    инициализация. (Обратите внимание, что это не относится к монтированию хоста
    каталог.)
  • Объемы данных могут быть разделены и повторно использованы между контейнерами.

  • Изменения в объеме данных производятся напрямую.

  • Изменения в объеме данных не будут включены при обновлении изображения.

  • Объем данных сохраняется, даже если сам контейнер удален.

В Dockerfile вы можете указать только назначение тома внутри контейнера. например, /usr/src/app.

При запуске ваш контейнер, например docker run --volume=/opt: /usr/src/app my_image вы можете, но не должны указывать свою точку крепления (/отказа) на хост - машине. Если вы не укажете аргумент --volume точка монтирования будет выбрана автоматически.

Ответ 2

Короче говоря: нет, ваша инструкция VOLUME неверна.

Dockerfile VOLUME указывает один или несколько томов с заданными путями на стороне контейнера. Но это не позволяет автору изображения указывать путь к хосту. На стороне хоста тома создаются с очень длинным ID-подобным именем внутри корня Docker. На моей машине это /var/lib/docker/volumes.

Примечание. Поскольку автоматически сгенерированное имя очень длинное и не имеет смысла с человеческой точки зрения, эти тома часто называют "безымянными" или "анонимными".

Ваш пример, который использует "." персонаж даже не запустится на моей машине, независимо от того, буду ли я ставить точку первым или вторым аргументом. Я получаю это сообщение об ошибке:

docker: Ответ об ошибке от демона: oci ошибка времени выполнения: container_linux.go: 265: запуск процесса контейнера вызвал "process_linux.go: 368: инициализация контейнера вызвала \" open/dev/ptmx: нет такого файла или каталога \"".

Я знаю, что то, что было сказано по этому вопросу, вероятно, не очень ценно для того, кто пытается понять VOLUME и -v и, конечно, не дает решения для того, чего вы пытаетесь достичь. Поэтому, надеюсь, следующие примеры позволят пролить свет на эти вопросы.

Minitutorial: указание томов

Учитывая этот Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(Для результата этого урока не имеет значения, если мы укажем vol1 vol2 или /vol1/vol2 - не спрашивайте меня, почему)

Построить это:

docker build -t my-openjdk

Бежать:

docker run --rm -it my-openjdk

Внутри контейнера запустите ls в командной строке, и вы увидите, что существуют два каталога; /vol1 и /vol2.

Запуск контейнера также создает две директории, или "тома", на стороне хоста.

Запустив контейнер, выполните команду docker volume ls на хост-машине, и вы увидите что-то вроде этого (для краткости я заменил среднюю часть имени тремя точками):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

Вернувшись в контейнер, выполните touch/vol1/weird-ass-file (создает пустой файл в указанном месте).

Этот файл теперь доступен на главном компьютере в одном из безымянных томов lol. Мне потребовалось две попытки, потому что я впервые попробовал первый из перечисленных томов, но в итоге я нашел свой файл во втором указанном томе, используя эту команду на хост-компьютере:

sudo ls /var/lib/docker/volumes/f670...49f0/_data

Точно так же вы можете попытаться удалить этот файл на хосте, и он также будет удален в контейнере.

Примечание. Папка _data также называется "точкой монтирования".

Выйдите из контейнера и перечислите тома на хосте. Они ушли. Мы использовали флаг --rm при запуске контейнера, и эта опция эффективно уничтожает не только контейнер при выходе, но и тома.

Запустите новый контейнер, но укажите том с помощью -v:

docker run --rm -it -v /vol3 my-openjdk

Это добавляет третий том, и вся система имеет три безымянных тома. Команда потерпела бы крах, если бы мы указали только -v vol3. Аргумент должен быть абсолютным путем внутри контейнера. На стороне хоста новый третий том является анонимным и находится вместе с двумя другими томами в /var/lib/docker/volumes/.

Ранее было заявлено, что Dockerfile не может отобразить путь к хосту, что создает нам проблему при попытке доставить файлы с хоста в контейнер во время выполнения. -v синтаксис -v решает эту проблему.

Представьте, что у меня есть подпапка в каталоге проекта ./src которую я хочу синхронизировать с /src внутри контейнера. Эта команда делает свое дело:

docker run -it -v $(pwd)/src:/src my-openjdk

Обе стороны : персонаж ожидает абсолютного пути. Левая сторона является абсолютным путем на хост-машине, а правая сторона - абсолютным путем внутри контейнера. pwd - это команда, которая "печатает текущий/рабочий каталог". Помещение команды в $() принимает команду в скобках, запускает ее в подоболочке и возвращает абсолютный путь к каталогу нашего проекта.

./src/Hello.java все это вместе, предположим, что у нас есть ./src/Hello.java в папке нашего проекта на хост-машине со следующим содержимым:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}

Мы создаем этот Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

Мы запускаем эту команду:

docker run -v $(pwd)/src:/src my-openjdk

Это печатает "Привет, мир!".

Самое приятное то, что мы полностью свободны для изменения файла .java новым сообщением для другого вывода при втором запуске - без необходимости перестраивать образ =)

Заключительные замечания

Я новичок в Docker, и вышеупомянутый "учебник" отражает информацию, которую я собрал из 3-х дневного хакатона командной строки. Мне почти стыдно, что я не смог предоставить ссылки на понятную англоязычную документацию, подкрепляющую мои заявления, но я искренне считаю, что это связано с отсутствием документации, а не с личными усилиями. Я знаю, что примеры работают так, как рекламируется, используя мою текущую настройку, которая называется "Windows 10 → Vagrant 2.0.0 → Docker 17.09.0-ce".

Учебное пособие не решает проблему "как указать путь к контейнеру в Dockerfile и разрешить команде run только указать путь к хосту". Там может быть способ, я просто не нашел его.

Наконец, у меня есть ощущение, что указание VOLUME в Dockerfile не просто необычно, но, вероятно, лучше не использовать VOLUME. По двум причинам. Первая причина, которую мы уже определили: мы не можем указать путь к хосту - это хорошо, потому что файлы Docker должны быть очень независимы от специфики хоста. Но вторая причина - люди могут забыть использовать --rm при запуске контейнера. Можно не забыть удалить контейнер, но забыть удалить объем. Кроме того, даже при наличии наилучшей человеческой памяти может оказаться сложной задачей выяснить, какие из всех анонимных томов можно безопасно удалить.

Ответ 3

Указание строки VOLUME в Dockerfile настраивает немного метаданных на вашем изображении, но важно, как эти метаданные используются.

Во-первых, что сделали эти две строки:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

Строка WORKDIR создает каталог, если он не существует, и обновляет некоторые метаданные изображения, чтобы указать все относительные пути, наряду с текущим каталогом для таких команд, как RUN, в этом месте. В строке VOLUME указаны два тома, один из которых - относительный путь . , а другой - /usr/src/app, оба оказались в одном каталоге. Чаще всего строка VOLUME содержит только один каталог, но может содержать несколько, как вы сделали, или это может быть массив в формате json.

Нельзя указывать источник тома в Dockerfile. Распространенный источник путаницы, когда при указании томов в Dockerfile пытается соответствовать синтаксису времени выполнения источника и места назначения во время построения образа, это не будет работать. Dockerfile может указывать только назначение тома. Было бы тривиальным способом обеспечения безопасности, если бы кто-то мог определить источник тома, так как он мог бы обновить общий образ в концентраторе докеров, чтобы смонтировать корневой каталог в контейнер, а затем запустить фоновый процесс внутри контейнера как часть точки входа, которая добавляет логины в /etc/passwd, настраивает systemd для запуска майнера биткойнов при следующей перезагрузке или ищет в файловой системе кредитные карты, номера SSN и закрытые ключи для отправки на удаленный сайт.

Что делает линия VOLUME? Как уже упоминалось, он устанавливает некоторые метаданные изображения, чтобы сказать, что каталог внутри изображения является томом. Как используются эти метаданные? Каждый раз, когда вы создаете контейнер из этого образа, Docker заставляет этот каталог быть томом. Если вы не предоставляете том в своей команде запуска или не создаете файл, единственным вариантом для докера является создание анонимного тома. Это локальный именованный том с длинным уникальным идентификатором для имени и без каких-либо других указаний на то, почему оно было создано или какие данные в нем содержатся (анонимные тома были отправлены для потери данных). Если вы переопределите том, указывая на именованный или хост-том, ваши данные будут туда перемещаться.

VOLUME ломает вещи: вы не можете отключить том, определенный в Dockerfile. И что более важно, команда RUN в Docker реализована с временными контейнерами. Эти временные контейнеры получат временный анонимный том. Этот анонимный том будет инициализирован с содержимым вашего изображения. Любые записи в контейнере от вашей команды RUN будут производиться в этот том. Когда команда RUN завершается, изменения в изображении сохраняются, а изменения анонимного тома отбрасываются. Из-за этого я настоятельно рекомендую не определять VOLUME в Dockerfile. Это приводит к неожиданному поведению нижестоящих пользователей вашего изображения, которые хотят расширить изображение начальными данными в томе.

Как вы должны указать объем? Чтобы указать, куда вы хотите включить тома с вашим изображением, предоставьте docker-compose.yml. Пользователи могут изменить это, чтобы приспособить расположение тома к своей локальной среде, и оно фиксирует другие параметры времени выполнения, такие как публикация портов и сети.

Кто-то должен документировать это! У них есть. Docker включает предупреждения об использовании VOLUME в свою документацию по Dockerfile вместе с рекомендациями по указанию источника во время выполнения:

  • Изменение тома в Dockerfile: если какие-либо шаги сборки изменят данные в томе после того, как он был объявлен, эти изменения будут отменены.

...

  • Каталог хоста объявляется во время выполнения контейнера: каталог хоста (точка монтирования) по своей природе зависит от хоста. Это необходимо для сохранения переносимости образа, поскольку нельзя гарантировать, что данный каталог хостов будет доступен на всех хостах. По этой причине вы не можете смонтировать каталог хоста из Dockerfile. Инструкция VOLUME не поддерживает указание параметра host-dir. Вы должны указать точку монтирования при создании или запуске контейнера.

Ответ 4

Команда VOLUME в Dockerfile является вполне законной, вполне обычной, абсолютно Dockerfile в использовании и в любом случае не рекомендуется использовать. Просто нужно это понять.

Мы используем его для указания на любые каталоги, в которые приложение в контейнере будет много писать. Мы не используем VOLUME только потому, что хотим использовать хост и контейнер как файл конфигурации.

Команде просто нужен один параметр; путь к папке относительно WORKDIR если он установлен, из контейнера. Затем docker создаст том на своем графике (/var/lib/docker) и подключит его к папке в контейнере. Теперь контейнеру будет куда писать с высокой производительностью. Без команды VOLUME скорость записи в указанную папку будет очень низкой, потому что теперь контейнер использует его для copy on write стратегии copy on write в самом контейнере. Стратегия copy on write является основной причиной существования томов.

Если вы монтируете папку, указанную командой VOLUME, команда никогда не запускается, потому что VOLUME выполняется только при запуске контейнера, что-то вроде ENV.

В основном, с помощью команды VOLUME вы получаете производительность без внешнего монтирования каких-либо томов. Данные будут сохраняться при выполнении всех контейнеров без каких-либо внешних подключений. Затем, когда будете готовы, просто наденьте на него что-нибудь.

Несколько хороших примеров использования:
- журналы
- временные папки

Некоторые плохие варианты использования:
- статические файлы
- конфиги
- код

Ответ 5

Чтобы лучше понять инструкцию для volume в dockerfile, давайте изучим типичное использование тома в реализации официального файла Docker для mysql.

VOLUME /var/lib/mysql

Ссылка: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

/var/lib/mysql является местоположением по умолчанию MySQL, в котором хранятся файлы данных.

Когда вы запускаете тестовый контейнер только для целей тестирования, вы не можете указать его точку монтирования, например

docker run mysql:8

тогда экземпляр контейнера mysql будет использовать путь монтирования по умолчанию, который указывается в инструкции volume в dockerfile. тома создаются с очень длинным ID-подобным именем внутри корня Docker, это называется "безымянный" или "анонимный" том. В папке базовой системы /var/lib/docker/volume.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Это очень удобно для целей быстрого тестирования без необходимости указывать точку монтирования, но все же можно добиться максимальной производительности, используя Volume для хранения данных, а не на уровне контейнера.

Для формального использования вам потребуется указать путь монтирования, переопределив точку монтирования для использования именованного тома, например

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

Команда монтирует каталог /my/own/datadir из базовой хост-системы как /var/lib/mysql внутри контейнера. Каталог данных /my/own/datadir не будет автоматически удален, даже контейнер будет удален.

Использование официального изображения MySQL: Ссылка: https://hub.docker.com/_/mysql/