Подтвердить что ты не робот

Несколько RUN против однократного RUN в Dockerfile, что лучше?

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 что-то добавляет, есть несколько моментов, которые нам нужно рассмотреть:

  • Слои должны просто добавить diff выше предыдущего, поэтому, если более поздний слой не удаляет что-то, добавленное в предыдущем, не должно быть большого дискового пространства, сохраняющего преимущества между обоими методами...

  • Слои вытягиваются параллельно от Docker Hub, поэтому Dockerfile.1, хотя, вероятно, немного больше, теоретически будет загружаться быстрее.

  • Если добавить четвертое предложение (т.е. echo This is the D > d) и локально перестроить, Dockerfile.1 будет увеличиваться быстрее благодаря кешу, но Dockerfile.2 придется снова запускать все 4 команды.

Итак, вопрос: Какой лучший способ сделать Dockerfile?

4b9b3361

Ответ 1

Когда это возможно, я всегда объединяю команды, которые создают файлы с командами, которые удаляют эти же файлы в одну строку RUN. Это связано с тем, что каждая строка RUN добавляет слой к изображению, выход - это буквально изменения файловой системы, которые вы можете просмотреть с помощью docker diff на docker diff им временном контейнере. Если вы удаляете файл, созданный на другом уровне, вся файловая система union регистрирует изменение файловой системы на новом уровне, файл все еще существует на предыдущем уровне и отправляется по сети и сохраняется на диске. Поэтому, если вы загружаете исходный код, извлекаете его, компилируете в двоичный код и затем удаляете tgz и исходные файлы в конце, вы действительно хотите, чтобы все это выполнялось на одном уровне, чтобы уменьшить размер изображения.

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

Заказ в файле Docker важно при просмотре повторного использования кеша изображений. Я смотрю на любые компоненты, которые будут обновляться очень редко, возможно, только когда базовое изображение обновится и поместит их в файл Docker. К концу файла Dockerfile я включаю любые команды, которые будут выполняться быстро и могут часто меняться, например, добавление пользователя с определенным UID узла или создание папок и изменение разрешений. Если контейнер включает в себя интерпретируемый код (например, JavaScript), который активно разрабатывается, он добавляется как можно дольше, так что перестройка запускает только одно изменение.

В каждой из этих групп изменений я консолидирую, насколько это возможно, чтобы свести к минимуму слои. Поэтому, если есть 4 разных папки исходного кода, они помещаются внутри одной папки, поэтому их можно добавить с помощью одной команды. Любой пакет, устанавливаемый из чего-то типа apt-get, объединяется в одно RUN, когда это возможно, чтобы минимизировать накладные расходы менеджера пакетов (обновление и очистка).


Обновление для многоступенчатой сборки:

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

Однако это не идеальное решение для раздавливания слоев, поскольку все, что вы копируете между этапами, это файлы, а не остальные метаданные изображения, такие как параметры переменной среды, входная точка и команда. И когда вы устанавливаете пакеты в дистрибутиве Linux, библиотеки и другие зависимости могут быть разбросаны по всей файловой системе, что затрудняет копирование всех зависимостей.

Из-за этого я использую многоэтапные сборки в качестве замены для создания двоичных файлов на сервере CI/CD, поэтому для моего сервера CI/CD требуется только оснастка для запуска docker build докеров и не иметь jdk, nodejs, go, а также любые другие инструменты компиляции.

Ответ 2

Официальный ответ, указанный в их лучших практиках (официальные изображения ДОЛЖНЫ придерживаться этих)

Минимизировать количество слоев

Вам необходимо найти баланс между читабельностью (и, следовательно, долгосрочной ремонтопригодностью) файла Docker и минимизацией количества слоев, которые он использует. Будьте стратегически и осторожны в отношении количества слоев, которые вы используете.

Начиная с докера 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 в вашем файле Docker. Каждое выражение FROM является этапом и может иметь собственное базовое изображение. На последнем этапе вы используете минимальное базовое изображение, например альпийское, копируете артефакты сборки с предыдущих этапов и устанавливаете требования времени выполнения. Конечным результатом этого этапа является ваш образ. Таким образом, здесь вы беспокоитесь о слоях, как описано выше.

Как обычно, докер имеет отличные документы на многоэтапных сборках. Вот краткий отрывок:

С многоступенчатыми сборками вы используете несколько операторов FROM в вашем файле Docker. Каждая инструкция FROM может использовать другую базу, и каждый из них начинает новый этап сборки. Вы можете выборочно копировать артефакты с одного этапа на другой, оставляя все, что вам не нужно в конечном изображении.

Большое сообщение в блоге об этом можно найти здесь: https://blog.alexellis.io/mutli-stage-docker-builds/

Чтобы ответить на ваши вопросы:

  1. Да, слои подобны различиям. Я не думаю, что добавлены слои, если абсолютно нулевые изменения. Проблема в том, что как только вы устанавливаете/загружаете что-то в слой # 2, вы не можете удалить его в слое # 3. Поэтому, когда что-то написано на слое, размер изображения больше не может быть уменьшен, удалив его.

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

  3. Да, кеширование полезно, если вы обновляете файл докеров. Но он работает в одном направлении. Если у вас 10 слоев, и вы меняете слой # 6, вам все равно придется перестроить все из уровня # 6- # 10. Поэтому не слишком часто, что это ускорит процесс сборки, но это гарантирует ненужное увеличение размера вашего изображения.


Спасибо @Mohan за то, что он напомнил мне об этом.

Ответ 3

Кажется, что ответы выше устарели. Документы отмечают:

До Docker 17.05 и даже больше, до Docker 1.10, было важно минимизировать количество слоев на вашем изображении. следующие улучшения смягчили эту потребность:

[...]

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

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

и

Обратите внимание, что этот пример также искусственно сжимает две команды RUN вместе с использованием Bash && оператора, чтобы избежать создания дополнительного слоя в изображении. Это неустойчиво и трудно поддерживать.

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

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

Ответ 4

Это зависит от того, как вы включаете в свои слои изображения.

Ключевым моментом является использование как можно большего количества слоев:

Плохой пример:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Хороший пример:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Другое предложение: удаление не так полезно, только если оно происходит на том же уровне, что и действие добавления/установки.