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

136

Ниже представлено содержимое моего "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 контейнера.

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

рефакторинг
источник

Ответы:

88

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

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

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

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

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

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

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

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

Бухаров Сергей
источник
278

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

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

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

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

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

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

Минутучебник: Определение объемов

Учитывая этот 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(создает пустой файл в указанном месте).

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

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Эту проблему решает другой синтаксис.

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

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

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

Собирая все вместе, предположим, что у нас есть ./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

Это напечатает «Hello, World!».

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

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

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

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

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

Мартин Андерссон
источник
2
Когда мы должны использовать безымянные / анонимные тома?
Searene 06
10
@ Мартин, большое спасибо. Ваш хакатон и его итоговое руководство очень ценятся.
Beezer
6
«Я не смог предоставить ссылки на понятную документацию на английском языке ... Я искренне думаю, что это из-за отсутствия документации». Я могу подтвердить. Это самая полная и последняя документация, которую я нашел, и я искал ее часами.
user697576 03
4
docker volume pruneможет использоваться для очистки оставшихся томов, которые не прикреплены к работающим контейнерам. Не сказать, что можно будет легко отличить потенциально важные по id ...
Джереми
4
«Что касается результата этого мини-руководства, не имеет значения, укажем мы vol1 vol2 или / vol1 / vol2 - не спрашивайте меня, почему». @MartinAndersson, потому что текущий рабочий каталог /, vol1относительно /, который разрешается в /vol1. Если вы используете, WORKDIRчтобы указать рабочий каталог, отличный от /, vol1и /vol1больше не будет указывать на тот же каталог.
sebastian
41

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

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

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

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

...

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

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

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

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

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

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

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

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

мистер убежище
источник
2
Что касается хороших и плохих примеров использования, на странице Dockerfile «Лучшие практики» говорится: «Вам настоятельно рекомендуется использовать VOLUME для любых изменяемых и / или обслуживаемых пользователем частей вашего образа». Думаю, конфиги там есть.
OmerSch
2
Это нормально, если указывать VOLUMEдиректории для конфигов. Однако после того, как вы действительно смонтируете конфигурацию, вам придется монтировать ее поверх этого каталога, и поэтому VOLUMEкоманда не запускается. Поэтому бессмысленно использовать VOLUMEкоманду в каталоге, указанном для конфигурации. Кроме того, инициализация графика объема одним статическим файлом, доступным только для чтения, является серьезным излишеством. Так что я придерживаюсь того, что сказал, не нужно VOLUMEкомандовать конфигами.
mr haven
Объемы могут иметь разные характеристики производительности из-за деталей реализации. Файлы данных базы данных подходят для этого варианта использования, но в любом случае, какой смысл хранить данные вместе с (временным) контейнерным хранилищем? Т.е. приписывать наличие томов производительности некорректно.
Андре Верланг,
33

Чтобы лучше понять volumeинструкцию в файле dockerfile, давайте изучим типичное использование тома в официальной реализации файла докеров 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/

Li-Tian
источник
2
Мне очень нравится ваше объяснение.
LukaszTaraszka
Но докер все равно сохраняет изменения. Также вы можете установить путь монтирования, -vиспользуя его, не устанавливая громкость в Dockerfile
Alex78191
1

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

На меня негативно повлияло то, что VOLUME был показан в базовых изображениях, которые я расширил, и узнал о проблеме только после того, как изображение уже было запущено, например wordpress, который объявляет /var/www/htmlпапку как VOLUME , и это означало, что любые файлы, добавленные или измененные во время этап сборки не учитывается, и текущие изменения сохраняются, даже если вы не знаете. Существует уродливый обходной путь для определения веб-каталога в другом месте, но это просто плохое решение для более простого: просто удалите директиву VOLUME.

Вы можете легко достичь цели объема, используя эту -vопцию, это не только проясняет, какими будут тома контейнера (без необходимости смотреть на Dockerfile и родительские Dockerfiles), но также дает потребителю возможность использовать громкость или нет.

Как сказано в этом ответе, использовать VOLUMES в основном плохо по следующим причинам :

Однако инструкция VOLUME имеет свою цену.

  • Пользователи могут не знать о создаваемых безымянных томах и продолжать занимать место для хранения на своем хосте Docker после удаления контейнеров.
  • Невозможно удалить том, объявленный в Dockerfile. Нисходящие образы не могут добавлять данные в пути, где существуют тома.

Последняя проблема приводит к подобным проблемам.

Возможность отмены объявления тома может помочь, но только если вы знаете тома, определенные в файле докеров, который сгенерировал образ (и в родительских файлах докеров!). Кроме того, VOLUME может быть добавлен в более новые версии файла Dockerfile, что может неожиданно нарушить работу потребителей изображения.

Еще одно хорошее объяснение ( об образе оракула с VOLUME , который был удален ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Еще больше случаев, когда VOLUME сломал что-то для людей:

Запрос тянуть , чтобы добавить опции для сброса Свойства родительского образа (включая объем), был закрыт , и в настоящее время обсуждается здесь (и вы можете увидеть несколько случаев из людей , пострадавших отрицательно из - за объемы , определенных в dockerfiles), который имеет комментарий с хорошим объяснение против VOLUME:

Использование VOLUME в Dockerfile бесполезно. Если пользователю требуется постоянство, он обязательно предоставит сопоставление тома при запуске указанного контейнера. Было очень трудно отследить, что моя проблема с невозможностью установить право собственности на каталог (/ var / lib / Infxdb) была связана с объявлением VOLUME в Dockerfile InfluxDB. Без параметра типа UNVOLUME или полного отказа от него я не могу изменить что-либо, связанное с указанной папкой. Это далеко не идеально, особенно когда вы осведомлены о безопасности и желаете указать определенный UID, под которым изображение должно запускаться, чтобы избежать случайного пользователя с большим количеством разрешений, чем необходимо, для запуска программного обеспечения на вашем хосте.

Я тоже считаю EXPOSE плохим, но у него меньше побочных эффектов. Единственное хорошее, что я вижу в VOLUME и EXPOSE, - это документация, и я бы счел их хорошими, если бы они служили только для этого (без каких-либо побочных эффектов).

TL; DR

Я считаю, что лучше всего отказаться от VOLUME.

Лукас Баскеротто
источник