Dockerfile.1
выполняет несколько RUN
:
FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c
Dockerfile.2
присоединяется к ним:
FROM busybox
RUN echo This is the A > a &&\
echo This is the B > b &&\
echo This is the C > c
Каждый RUN
создает слой, поэтому я всегда предполагал, что чем меньше слоев, тем лучше и, следовательно Dockerfile.2
, лучше.
Это, очевидно, верно, когда a RUN
удаляет что-то добавленное предыдущим RUN
(т.е. yum install nano && yum clean all
), но в случаях, когда каждый RUN
добавляет что-то, есть несколько моментов, которые нам необходимо учитывать:
Слои должны просто добавлять различие над предыдущим, поэтому, если более поздний слой не удаляет что-то добавленное в предыдущем, между обоими методами не должно быть большого преимущества в экономии места на диске ...
Слои
Dockerfile.1
загружаются из Docker Hub параллельно, поэтому , хотя они, вероятно, немного больше, теоретически они будут загружаться быстрее.Если добавить 4-е предложение (т.е.
echo This is the D > d
) и локально перестроить,Dockerfile.1
сборка будет быстрее благодаря кешу, ноDockerfile.2
придется снова запустить все 4 команды.
Итак, вопрос: как лучше сделать Dockerfile?
источник
Ответы:
По возможности я всегда объединяю команды, создающие файлы, с командами, удаляющими те же файлы, в одну
RUN
строку. Это связано с тем, что каждаяRUN
строка добавляет слой к изображению, вывод - это буквально изменения файловой системы, которые вы можете просматриватьdocker diff
во временном контейнере, который она создает. Если вы удаляете файл, который был создан на другом уровне, все, что делает файловая система union, - это регистрирует изменение файловой системы на новом уровне, файл все еще существует на предыдущем уровне, доставляется по сети и сохраняется на диске. Поэтому, если вы загружаете исходный код, извлекаете его, компилируете в двоичный файл, а затем удаляете tgz и исходные файлы в конце, вы действительно хотите, чтобы все это было сделано в одном слое, чтобы уменьшить размер изображения.Затем я лично разделил слои на основе их потенциала для повторного использования в других изображениях и ожидаемого использования кеширования. Если у меня есть 4 образа, все с одним и тем же базовым образом (например, debian), я могу вытащить набор общих утилит для большинства этих образов в команду первого запуска, чтобы другие образы выиграли от кэширования.
Порядок в Dockerfile важен при рассмотрении повторного использования кэша изображений. Я смотрю на любые компоненты, которые будут обновляться очень редко, возможно, только когда обновится базовый образ и поместит их наверху в Dockerfile. Ближе к концу Dockerfile я включаю любые команды, которые будут выполняться быстро и могут часто меняться, например, добавление пользователя с определенным UID хоста или создание папок и изменение разрешений. Если контейнер включает интерпретируемый код (например, JavaScript), который активно разрабатывается, он добавляется как можно позже, так что при перестроении выполняется только это единственное изменение.
В каждой из этих групп изменений я максимально консолидирую их, чтобы минимизировать слои. Итак, если есть 4 разных папки с исходным кодом, они помещаются в одну папку, поэтому ее можно добавить с помощью одной команды. Любые пакеты, устанавливаемые из чего-то вроде apt-get, по возможности объединяются в один RUN, чтобы свести к минимуму накладные расходы менеджера пакетов (обновление и очистка).
Обновление для многоэтапных сборок:
Я гораздо меньше беспокоюсь об уменьшении размера изображения на незавершенных этапах многоступенчатой сборки. Если эти этапы не помечены и не отправлены на другие узлы, вы можете максимизировать вероятность повторного использования кеша, разделив каждую команду на отдельную
RUN
строку.Однако это не идеальное решение для сжатия слоев, поскольку все, что вы копируете между этапами, - это файлы, а не остальные метаданные изображения, такие как настройки переменных среды, точки входа и команды. И когда вы устанавливаете пакеты в дистрибутиве Linux, библиотеки и другие зависимости могут быть разбросаны по файловой системе, что затрудняет копирование всех зависимостей.
Из-за этого я использую многоступенчатые сборки в качестве замены для сборки двоичных файлов на сервере CI / CD, так что моему серверу CI / CD требуется только инструмент для запуска
docker build
, а не jdk, nodejs, go и любые другие установленные инструменты компиляции.источник
Официальный ответ, указанный в их лучших практиках (официальные изображения ДОЛЖНЫ соответствовать этим)
Так как докер 1.10
COPY
,ADD
иRUN
утверждение добавить новый слой к изображению. Будьте осторожны при использовании этих утверждений. Попробуйте объединить команды в одинRUN
оператор. Разделяйте его, только если это требуется для удобочитаемости.Больше информации: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers
Обновление: несколько этапов в докере> 17.05
В многоэтапных сборках вы можете использовать несколько
FROM
операторов в своем Dockerfile. КаждыйFROM
оператор является этапом и может иметь собственное базовое изображение. На последнем этапе вы используете минимальный базовый образ, например alpine, копируете артефакты сборки с предыдущих этапов и устанавливаете требования времени выполнения. Конечный результат этого этапа - ваш имидж. Итак, здесь вы беспокоитесь о слоях, как описано ранее.Как обычно, в docker есть отличная документация по многоступенчатым сборкам. Вот небольшой отрывок:
Отличное сообщение в блоге об этом можно найти здесь: https://blog.alexellis.io/mutli-stage-docker-builds/
Чтобы ответить на ваши вопросы:
Да, слои похожи на различия. Я не думаю, что добавляются слои, если абсолютно нулевых изменений. Проблема в том, что после того, как вы установили / загрузили что-то в слое №2, вы не можете удалить это в слое №3. Итак, как только что-то написано на слое, размер изображения больше не может быть уменьшен путем его удаления.
Хотя слои можно тянуть параллельно, что потенциально ускоряет процесс, каждый слой, несомненно, увеличивает размер изображения, даже если они удаляют файлы.
Да, кеширование полезно, если вы обновляете файл докера. Но работает в одном направлении. Если у вас есть 10 слоев, и вы меняете слой №6, вам все равно придется перестраивать все, начиная со слоя №6- №10. Так что не так часто это ускоряет процесс сборки, но гарантированно без надобности увеличивает размер вашего изображения.
Спасибо @Mohan за напоминание мне обновить этот ответ.
источник
Кажется, что ответы выше устарели. Примечание в документации:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers
и
https://docs.docker.com/engine/userguide/eng-image/multistage-build/
Похоже, что лучшая практика изменилась на использование многоступенчатых сборок и сохранение
Dockerfile
читабельности.источник
docker image build --squash
вариант выйдет за рамки экспериментального.squash
экспериментов. В нем много уловок, и он имел смысл только до многоэтапных сборок. В многоэтапных сборках вам нужно оптимизировать только заключительный этап, что очень просто.Это зависит от того, что вы включаете в свои слои изображения.
Ключевой момент - разделить как можно больше слоев:
Плохой пример:
Dockerfile.1
Dockerfile.2
Хороший пример:
Dockerfile.1
Dockerfile.2
Другое предложение: удаление не так полезно, только если оно происходит на том же уровне, что и действие добавления / установки.
источник
RUN yum install big-package
из кеша?