Понимание слоев Docker

27

У нас есть следующий блок в нашем Dockerfile:

RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

Мне сказали, что мы должны объединить эти RUNкоманды, чтобы сократить созданные слои докеров:

RUN yum -y update \
    && yum -y install epel-release \
    && yum -y groupinstall "Development Tools" \
    && yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

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

alecxe
источник

Ответы:

35

Образ Docker на самом деле является связанным списком слоев файловой системы. Каждая инструкция в Dockerfile создает уровень файловой системы, который описывает различия в файловой системе до и после выполнения соответствующей инструкции. docker inspectСубкоманда может быть использована на Docker изображение , чтобы раскрыть его природу бытия связанного списка файловых слоев.

Количество слоев, используемых в изображении, важно

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

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

Совет № 1 Убедитесь, что шаги сборки, в которых задействован ваш исходный код, выполняются в Dockerfile как можно позже и не привязаны к предыдущим командам, использующим a &&или a ;.

Причина этого заключается в том, что все предыдущие шаги будут кэшироваться, и соответствующие слои не нужно будет загружать снова и снова. Это означает более быстрые сборки и более быстрые выпуски, что, вероятно, то, что вы хотите. Интересно, что на удивление сложно оптимально использовать докер кеш.

Мой второй совет менее важен, но я считаю его очень полезным с точки зрения обслуживания:

Совет № 2 Не пишите сложные команды в Dockerfile, а используйте сценарии, которые нужно скопировать и выполнить.

Dockerfile следуя этот совет будет выглядеть

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh

и так далее. Совет по связыванию нескольких команд &&имеет ограниченную область действия. Гораздо проще писать с помощью скриптов, где вы можете использовать функции и т. Д., Чтобы избежать избыточности или для целей документирования.

Люди, заинтересованные в препроцессорах и желающие избежать небольших накладных расходов, вызванных COPYшагами, и на самом деле генерируют Dockerfile, где

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh

последовательности заменяются

RUN base64 --decode … | sh -x

где - версия в кодировке base64 apt_setup.sh.

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

Совет № 3 Используйте with-idiom, чтобы избежать файлов, присутствующих в промежуточных слоях, но не в результирующей файловой системе.

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

Например, предположим, что нам временно нужен компилятор C и некоторое изображение, и рассмотрим

# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc

(Более реалистичным примером будет создание некоторого программного обеспечения с помощью компилятора, а не просто подтверждение его присутствия с --versionфлагом.)

Фрагмент Dockerfile создает три слоя, первый из которых содержит полный пакет gcc, так что даже если он не присутствует в конечной файловой системе, соответствующие данные все еще остаются частью образа таким же образом, и их необходимо загружать, выгружать и распаковывать всякий раз, когда Окончательное изображение.

with-Idiom является распространенной формой в функциональном программировании , чтобы изолировать собственность ресурсов и ресурсов высвобождения из логики его использования. Эту идиому легко перенести в сценарии оболочки, и мы можем перефразировать предыдущие команды как следующий сценарий, который будет использоваться COPY & RUNкак в Совете № 2.

# with_c_compiler SIMPLE-COMMAND
#  Execute SIMPLE-COMMAND in a sub-shell with gcc being available.

with_c_compiler()
(
    set -e
    apt-get install -y gcc
    "$@"
    trap 'apt-get --purge autoremove -y gcc' EXIT
)

with_c_compiler\
    gcc --version

Сложные команды можно превратить в функцию, чтобы их можно было передавать в with_c_compiler. Также возможно связывать вызовы нескольких with_whateverфункций, но, возможно, не очень желательно. (Используя более эзотерические особенности оболочки, можно, конечно, with_c_compilerпринимать сложные команды принятия, но во всех аспектах предпочтительнее заключать эти сложные команды в функции.)

Если мы хотим игнорировать Совет № 2, полученный фрагмент Dockerfile будет

RUN apt-get install -y gcc\
 && gcc --version\
 && apt-get --purge autoremove -y gcc

который не так легко читать и поддерживать из-за запутывания. Посмотрите, как вариант сценария оболочки делает акцент на важной части, в gcc --versionто время как &&вариант цепочки скрывает эту часть в середине шума.

Михаэль Ле Барбье Грюневальд
источник
1
Не могли бы вы указать размер блока после сборки с использованием сценария и использования нескольких команд в одном операторе RUN?
030
1
Мне кажется плохой идеей смешивать конфигурацию базы изображений (то есть содержимого ОС) и даже библиотеки с настройками исходного кода, который вы написали. Вы говорите: «Убедитесь, что этапы сборки, где задействован ваш исходный код, выполняются как можно позже». Есть ли проблема в том, чтобы сделать эту часть совершенно независимым артефактом?
JimmyJames
1
@ 030 Что ты имеешь в виду под размером «коробки»? Я понятия не имею, на какую коробку вы ссылаетесь.
Михаэль Ле Барбье Грюневальд,
1
Я имел в виду размер изображения докера
030
1
@JimmyJames Это в значительной степени зависит от вашего сценария развертывания. Если мы примем скомпилированную программу, «правильным решением» будет упаковать ее и установить зависимости пакета и сам пакет как два отдельных этапа, близких к завершению. Это позволяет максимизировать полезность кэша докера и избежать загрузки многократных слоев с одними и теми же файлами. Мне легче делиться рецептами сборки для создания образов докеров, чем строить длинные цепочки зависимостей изображений, потому что последний затрудняет перестройку.
Михаэль Ле Барбье Грюневальд,
13

Каждая инструкция, которую вы создаете в своем Dockerfile, приводит к созданию нового слоя изображения. Каждый слой приносит дополнительные данные, которые не всегда являются частью результирующего изображения. Например, если вы добавляете файл в один слой, но позже удаляете его в другом слое, размер окончательного изображения будет включать в себя добавленный размер файла в виде специального «белого» файла, хотя вы его удалили.

Допустим, у вас есть следующий Dockerfile:

FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release

Полученный размер изображения будет

bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB

Напротив, с «похожим» Dockerfile:

FROM centos:6

RUN yum -y update  && yum -y install epel-release

Полученный размер изображения будет

smallimage     latest        7edeafc01ffe        3 minutes ago    384MB

Вы получите еще меньший размер, если очистите кэш yum одним оператором RUN.

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

oryades
источник
4

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

030
источник